MateConv:4、MateConv全量指令微调
- 从补全模型到对话模型
全量指令微调(Full Instruction Tuning)在现代大规模预训练模型的应用中具有重要的意义。虽然预训练模型在大量的无监督数据上获得了强大的语言理解和生成能力,但这些模型通常缺乏处理具体任务的针对性表现。全量指令微调的核心作用在于将预训练模型进一步调整,使其能够根据明确的指令执行特定任务。


- 全量指令微调的必要性
- 增强模型的任务适应能力:
- 预训练阶段的语言模型往往是通用的,并没有针对某个特定任务进行优化。虽然它们具备强大的生成和理解能力,但在实际应用中,我们常常需要模型执行某些具体的任务,例如对话生成、问答、文本摘要、翻译等。全量指令微调通过在明确的指令或任务定义下对模型进行进一步优化,提升了模型对这些任务的适应能力。
- 提升模型对多样化任务的执行能力:
- 指令微调的一个关键特征是它能够帮助模型学会根据不同的输入指令生成相应的输出。通过微调,模型能够处理广泛的任务类型,并根据指令灵活应对不同的任务需求。这个过程让模型从一个纯语言生成器转变为一个能够执行多种任务的通用人工智能系统。
- 缩小预训练模型与应用场景的差距:
- 预训练阶段的数据大多来自通用的文本数据,而模型实际应用中的任务和数据形式可能与预训练数据差异较大。通过全量指令微调,模型可以学习到如何在特定任务下调整其生成和决策方式,确保在实际应用中的表现更加精准和高效。这种微调过程弥补了预训练模型和实际应用场景之间的差距。
- 减少下游任务的数据需求:
- 全量指令微调使得模型在接受指令后,能够直接执行多个任务,而不再需要为每个任务单独进行微调或标注大量数据。通过在多任务、多领域的指令数据上进行微调,模型能够以较少的数据应对大量的任务,提升了其广泛的适用性。
- 提高用户交互的可控性:
- 通过指令微调,模型可以更好地理解和执行用户的明确需求,这使得模型的输出更加符合用户预期。用户可以通过简单明确的指令控制模型行为,而不再依赖复杂的上下文提示。这种可控性对于提供高质量的自动化服务和用户交互尤为重要。
全量指令微调是使预训练模型更加实用和高效的关键步骤。它不仅提升了模型的任务执行能力,还使得模型更加灵活、可控和广泛适应多样化的应用场景。这使得指令微调成为在现代大规模语言模型开发和应用中不可或缺的一部分。


- 预训练模型和指令微调模型不同的适用场景
不过需要注意的是,其实预训练模型和指令微调模型各自都有不同的适用场景,尽管指令微调模型在特定任务上表现更好,但预训练模型也有其独特的优势和适用领域。接下来,我们可以比较它们各自适用的场景,并解释为什么在某些情况下预训练模型仍然有它的价值。
- 通用语言理解任务:
- 场景:预训练模型经过大量无监督文本数据的训练,具备广泛的语言理解能力,能够在没有明确任务定义的情况下生成文本或做出推理。
- 例子:生成文章、写作辅助、文本补全等需要较强语言理解和生成能力的任务。
- 原因:预训练模型没有被限制在特定任务上,因此它可以很好地处理广泛的语言生成需求,并且在一些开放领域的应用中表现出色。
- 低资源或无监督学习任务:
- 场景:在数据有限或者缺乏标注数据的场景下,预训练模型可以直接应用于一些无监督或少量数据的任务,如文本分类、情感分析等。
- 例子:当你没有足够的任务数据进行微调时,预训练模型可以通过自监督学习生成文本或进行特征提取。
- 原因:预训练模型的核心优势在于它从大量无标注数据中学到的广泛的语言模式,这让它即使在没有具体任务数据的情况下也能展现出良好的表现。
- 跨领域或未知任务的探索:
- 场景:当你需要在未知领域或者全新任务上探索模型的表现时,预训练模型可以作为一个强大的基础模型进行初步的探索和实验。
- 例子:如果你需要构建一个多领域的生成任务,预训练模型可以提供灵活性,允许你在不同任务之间切换,而不需要进行专门的任务微调。
- 原因:由于预训练模型没有被微调锁定在某一个任务上,它可以应用于不同领域、不同形式的输入,而不受任务特定的限制。
- 探索性的文本生成:
- 场景:当任务的目标是生成富有创造力、探索性或多样化的文本时,预训练模型由于其没有特定任务的约束,可能会生成更具多样性和创造力的文本。
- 例子:文学写作、诗歌创作、广告文案等需要高自由度生成的任务。
- 原因:预训练模型的多样化语言生成能力让它能够在这些没有明确限制的场景中表现得更加灵活。
- 明确任务驱动的场景:
- 场景:在指令微调模型中,模型经过了明确的任务定义和微调,能够很好地理解并执行用户给定的指令。
- 例子:问答系统、文本摘要、机器翻译等有明确指令的任务。
- 原因:指令微调模型能够根据特定任务中的指令生成高质量、符合任务需求的输出。它通过微调,在特定任务上表现更加精确。
- 多任务统一处理:
- 场景:在需要统一处理多个任务的情况下,指令微调模型非常适合。这些模型能够根据指令动态适应不同的任务需求,而无需为每个任务单独训练不同的模型。
- 例子:集成多种能力的智能助手,能够根据不同的指令生成对话、翻译文本、生成摘要等多种任务的结果。
- 原因:指令微调模型通过在多任务数据上的训练,具备根据不同指令处理不同任务的能力,这使得它可以灵活应对复杂的多任务场景。
- 高精度特定任务:
- 场景:当任务需要高精度的结果,例如法律文档分析、医疗问答等具有高专业性要求的任务时,指令微调模型可以通过微调后的知识提升在特定任务上的表现。
- 例子:法律问题解答、医疗诊断建议生成等任务。
- 原因:指令微调模型可以在这些任务特定的领域数据上进行进一步的优化,从而在输出结果的准确性和专业性上更有保障。
- 高效用户交互:
- 场景:在需要与用户进行高效交互的系统中,指令微调模型能够准确理解用户的意图,并根据具体指令生成用户期望的答案或结果。
- 例子:智能客服系统、虚拟助手、对话生成等任务。
- 原因:指令微调模型通过学习用户指令的意图,能够更好地理解并生成符合用户预期的结果,提升交互体验。
预训练模型 vs. 指令微调模型适用场景的比较
|
特点 |
预训练模型 |
指令微调模型 |
|
任务灵活性 |
适用于通用任务,能在没有特定任务数据的情况下进行探索性生成和任务处理 |
针对明确任务进行优化,能根据特定指令执行高效、精确的任务 |
|
任务精确性 |
生成的内容更加通用,适用于开放领域的任务 |
生成内容更加准确,特别适合需要精确输出的应用场景 |
|
多任务处理能力 |
可处理广泛的任务,但没有经过微调,在特定任务上的表现可能不如微调模型 |
能根据指令灵活处理多个任务,在多任务环境中表现优秀 |
|
资源要求 |
通常预训练模型在推理时资源需求较大,但在未微调的场景下灵活度较高 |
微调后模型在指定任务上表现出色,但可能在资源上有较高要求,尤其是多任务微调的情况下 |
|
无监督学习应用 |
在没有标注数据的场景下,预训练模型可以直接用于无监督任务 |
通常需要在标注数据上进行微调,因此在标注数据不足的情况下,微调模型不如预训练模型适用 |
|
用户交互 |
可以根据通用的用户输入生成合理的文本,但缺乏对特定指令的理解 |
能更好地根据用户的指令执行任务,提升用户交互体验 |
|
探索性和创造性 |
更加适合开放性任务,能生成探索性、创造性较强的文本 |
在任务明确时表现优异,但在过于开放的任务中,生成结果的多样性和创造力可能不如预训练模型 |
- 预训练模型:适用于开放领域任务、通用生成、低资源场景、跨领域探索和无监督学习任务。它在没有明确任务要求时非常灵活,可以直接应用于多个任务场景。
- 指令微调模型:适合有明确指令需求的任务、高精度的特定任务、多任务处理以及需要高效用户交互的应用。它在根据指令进行高效任务执行和准确性要求较高的任务中表现出色。
- 全量指令微调的一般流程
- 数据准备:
- 任务定义与指令设计:
- 在进行全量指令微调之前,必须明确微调模型需要执行的具体任务。这些任务可以是对话生成、问答系统、文本分类、机器翻译等。在数据准备阶段,需要为每个任务设计一组明确的指令,以指导模型的行为。例如,输入文本可以是 “给定一段话,请生成摘要”,而期望的输出是该段话的简洁概述。
- 多样化的指令数据集:
- 指令微调的数据集通常包括多任务、多领域的数据,以增强模型的广泛适应能力。这些数据集可以来自多个来源,比如自然语言处理(NLP)任务数据集或用户交互记录。数据集的多样性越高,模型在面对不同任务时的表现通常越好。
- 指令格式化:
- 在数据集中,输入的文本通常被格式化为“指令 + 输入”的形式,模型需要根据这组输入生成相应的输出。为了保持一致性,所有任务的数据都应遵循统一的格式标准,例如:
|
Plaintext |
- 模型配置:
- 预训练模型加载:
- 微调的基础是已经预训练好的大模型。这些模型通常具有通用的语言理解能力。加载预训练模型时,需确保模型具备良好的初始化权重,使其能够较快适应指令微调任务。
- 调整模型参数:
- 在模型配置阶段,你可能需要根据指令微调任务的复杂性调整模型参数。比如,增加 max_seq_len 以处理较长的指令输入,或调整 vocab_size 以处理新的领域词汇。确保模型结构足够灵活,能够适应不同的任务需求。
- 指令微调:
- 多任务微调:
- 全量指令微调的一个特点是它通常在多任务数据上同时进行训练。这意味着模型将同时接触来自不同任务的数据,并根据不同的指令生成相应的输出。训练时,模型会学习如何根据不同类型的输入指令作出正确的响应。
- 损失函数设计:
- 在微调阶段,损失函数的选择尤为重要。通常使用交叉熵损失函数来衡量模型输出与预期结果之间的差距。此外,如果任务之间的目标差异较大,可以对不同任务设置权重,从而引导模型重点关注某些特定的任务。
- 学习率和优化器:
- 选择合适的学习率和优化器对模型的微调至关重要。一般来说,可以使用较小的学习率(例如 1e-5 或 5e-5)来避免对预训练模型权重进行过度修改。AdamW 是较为常见的优化器,因为它在大规模语言模型微调任务中表现稳定。
- 训练与监控:
- 训练过程:
- 在训练过程中,模型会根据指令和输入生成对应的输出,优化器会根据损失函数不断更新模型的权重。在这个过程中,确保模型训练稳定并且逐渐提高在各个任务上的表现。
- 使用监控工具:
- 在训练过程中,建议使用监控工具(如 TensorBoard 或 W&B),实时监控损失曲线、训练进度等信息,以便及早发现潜在问题,例如模型过拟合或收敛不良。
- 验证集评估:
- 在训练的过程中,定期使用验证集来评估模型在未见过的数据上的表现。验证集的设计应当与训练集保持一致,且最好覆盖多个任务场景,以确保模型的泛化能力。
- 模型评估与测试:
- 评估模型性能:
- 一旦模型完成微调,需要对其在多个任务上的性能进行评估。常用的评估指标包括准确率(Accuracy)、精确率(Precision)、召回率(Recall)、F1 分数等,具体选择取决于任务的性质。
- 测试实际应用场景:
- 为了确保模型在真实场景中可以有效工作,可以通过实际的应用场景进行测试。比如,你可以通过模拟用户指令来测试模型在不同任务上的反应是否符合预期。
- 部署与应用:
- 模型导出与部署:
- 完成微调后,模型可以导出为可部署的格式(例如 TorchScript、ONNX 等),并部署到生产环境中。
- 持续优化与反馈:
- 在生产环境中,模型的表现会收到用户交互的影响,因此可以根据用户反馈进行进一步的微调和优化。
不使用明确的 instruction 会让模型在推理阶段更依赖上下文信息,而不是等待明确的指令提示。它可以使模型在连续对话生成任务中表现得更自然,但缺点是,模型可能在多任务环境中表现不如明确指令下那么灵活,因为它没有被明确告知应该做什么任务。
知识灌注(Knowledge Infusion)指的是将特定的知识明确灌输到模型中,在指令微调阶段也是可以进行知识灌注的,使得模型能够记住特定信息,并在任务中使用这些知识。例如,你想让模型知道自己的名字,或掌握一些特定领域的事实,这种知识可以在指令微调阶段注入。
知识灌注的可行性:
可以进行知识灌注:在指令微调阶段,确实可以通过有针对性的训练数据进行知识灌注。你可以提供包含这些知识的训练数据,通过上下文或明确指令提示模型,从而让模型学会特定的知识。例如,你可以在对话数据中加入这样的对话:
用户:你叫什么名字?
模型:我是 MateConv,一个智能对话助手。
通过这样的训练数据,模型会逐渐记住这些知识,并在推理时能够自发地根据这些提示进行正确的回答。
这其实是一个非常综合且重要的问题,尤其是当我们在大规模模型微调的场景下,追求平衡性能与资源消耗时。全量指令微调和高效微调(也叫参数高效微调)各自代表了不同的微调策略,它们有不同的应用场景和技术优点,但也存在一些相互联系。接下来我们将详细对比它们的区别和联系。
一、全量指令微调(Full Instruction Tuning)
全量指令微调指的是对整个模型的所有参数进行微调。这种方法最常用于当你有充足的数据和计算资源,并且希望模型在一个特定任务或多个任务上达到最佳性能。
- 微调所有模型参数:
- 全量微调意味着所有的预训练参数都会进行调整。模型在新的指令任务上逐渐适应并优化所有权重,从而能够高效地处理特定任务。
- 适用于大规模数据和明确任务:
- 适合处理大量的数据集,并且能通过多任务、多领域的指令数据微调模型。这种方法能够让模型最大限度地适应新的任务需求。
- 计算和存储开销大:
- 由于模型的所有参数都会被更新,全量指令微调通常需要非常高的计算资源和存储空间,尤其是当你处理的是数十亿参数规模的大模型时。
- 微调后效果精确:
- 全量微调可以保证模型在微调后的任务上有最好的表现,模型完全适应了新的指令数据,在目标任务上能够达到最高精度。
- 应用场景:
- 通常应用在有足够计算资源的环境中,特别是需要极高性能、处理特定任务(如法律文本分析、医疗诊断等)的模型开发。
- 高性能,任务适应能力强。
- 精确控制模型行为和生成效果。
- 计算资源和时间开销巨大。
- 训练耗费大量的显存和存储资源,部署和更新较为昂贵。
二、高效微调(Parameter-Efficient Tuning)
高效微调(Parameter-Efficient Tuning)是一种在不更新全部参数的情况下,选择性地对模型某些部分进行微调的策略。常见的高效微调技术包括 LoRA(Low-Rank Adaptation)、Prefix Tuning、Adapter Tuning 等。
- 微调部分参数:
- 高效微调通过引入一些小型模块(如 Adapter 层、前缀)或调整模型的某些子结构(如低秩矩阵),只对这些部分进行微调,而不调整整个模型的所有权重。
- 减少显存和计算需求:
- 因为只需要更新少量参数,高效微调显著降低了显存消耗和计算资源的需求。这使得即使在资源受限的环境下,也能微调大规模的预训练模型。
- 适用于低资源环境:
- 高效微调非常适合数据集较小、计算资源有限的场景。例如,在嵌入式系统或边缘计算设备上部署大模型时,高效微调可以让模型在这些环境中高效运行。
- 模块化和可重用性:
- 高效微调的另一个优点是其模块化。微调过程中引入的小型模块可以在不同的任务之间进行共享或转移,避免重复训练。例如,适用于某个领域的 Adapter 层可以在该领域的多个任务上重复使用。
- 微调后效果良好但不如全量微调:
- 虽然高效微调可以达到很好的效果,但与全量指令微调相比,性能上略逊一筹。尤其是在对任务精度要求极高的场景下,可能需要全量微调来最大化模型的效果。
- 计算资源、显存开销小,适合低资源环境。
- 训练效率高,适用于快速迭代和部署。
- 在特定高精度任务上,性能可能不如全量微调。
- 微调时引入的模块或层会增加模型的复杂度,需要额外管理。
- 参数调整范围:
- 全量指令微调:微调所有模型参数,更新整个网络的权重。
- 高效微调:只微调模型中的部分参数或增加的模块,如 LoRA、Adapter 层,原有的大部分权重保持不变。
- 计算和显存需求:
- 全量指令微调:显著增加计算和显存需求,尤其是对于数十亿参数规模的模型,要求非常高。
- 高效微调:显存需求低,适合在资源有限的环境中运行。只需要少量的参数更新,因此计算资源消耗更低。
- 性能表现:
- 全量指令微调:在任务上的表现往往最佳,因为模型所有参数都被精细优化,可以最大化适应任务需求。
- 高效微调:性能表现虽然不错,但通常不及全量微调,尤其是在一些复杂或精细的任务上。
- 部署和模块化:
- 全量指令微调:整个模型被微调,每次微调都需要重新部署整个模型,更新较为繁琐。
- 高效微调:只需要部署微调后的模块,模型的主体部分保持不变,方便快速迭代和更新。
- 训练时间:
- 全量指令微调:训练时间较长,特别是在大规模模型上,因为需要更新大量的参数。
- 高效微调:训练时间相对较短,因为只需要微调少部分参数。
- 相同的目标:
- 无论是全量指令微调还是高效微调,它们的最终目标都是让预训练模型适应特定任务,提高在目标任务上的表现。两者的区别更多在于实现的方式和资源的权衡。
- 可以结合使用:
- 在某些场景下,可以结合全量指令微调和高效微调。例如,你可以先对整个模型进行全量指令微调,以确保模型的高精度表现,然后在低资源环境中应用高效微调以进一步优化模型,或者扩展到新任务中而不需要微调整个模型。
- 知识共享:
- 高效微调中的一些方法(如 Adapter 模块)可以在不同任务之间共享,这些模块可以通过全量微调的模型基础上进一步进行高效微调,扩展到不同的任务领域。
- 全量指令微调是一个“昂贵”的但高效的微调方法,适合资源充足、需要高精度表现的场景,尤其是那些需要处理多个复杂任务的场景。
- 高效微调则是一个“轻量级”的方法,适合资源有限的场景,并且在需要快速部署或频繁更新时非常有用。它可以通过少量的参数更新,实现较好的模型性能。
1. 上下文处理
- 单轮对话微调:
- 模型只需要处理当前输入和输出之间的映射,不需要考虑上下文,也不需要保存或记住之前的对话内容。
- 微调数据可以是一些独立的指令,比如“生成一个回复”、“回答这个问题”等。
- 微调时,数据集中每个问题和回答都可以视作独立的样本,因此微调过程相对简单。
- 多轮对话微调:
- 模型需要在每一轮对话中跟踪和理解完整的对话历史,包括之前的问答信息。模型必须具备记忆和状态管理的能力。
- 微调时,数据集不仅需要包含每轮对话的输入和输出,还需要保留对话的上下文,使模型能够在生成回复时参考前文。
- 这类任务的微调更加复杂,因为模型需要学会如何在多个回合中保持对话的连贯性,避免前后矛盾。
2. 任务复杂性
- 单轮对话微调:
- 任务相对简单,通常用于一些固定回答的任务,例如问答系统、知识查询、单轮任务执行等。模型的输出只需对当前输入负责。
- 微调时,模型不需要处理复杂的对话逻辑,只需对每个输入生成最合适的响应。
- 多轮对话微调:
- 任务复杂度更高,尤其是在多回合对话中,模型需要处理上下文依赖、对话状态跟踪等问题。
- 微调时,数据集需要涵盖不同类型的对话情境,模型不仅要能生成单轮的合适回复,还要保证整个对话的流畅性和一致性。
3. 模型训练需求
- 单轮对话微调:
- 对训练数据的需求相对较少,数据集只需要包含独立的问题和回答。由于没有上下文依赖,数据样本的数量可以较小。
- 模型的生成质量主要取决于它在每次输入时生成单一答案的能力。
- 多轮对话微调:
- 多轮对话需要更丰富的训练数据,涵盖多种对话情境和长短不一的对话历史,以帮助模型学习如何处理复杂的上下文。
- 模型需要学习如何在多个回合中保持对话的上下文一致性,因此训练时间和资源需求更高。
- 两种主流的全量指令微调方法
从模型训练角度来说,指令微调远不如预训练复杂,在实际进行指令微调的过程中,可以选择使用transformer库的原生方法实现全量指令微调,也可以选择一些开源的项目如Llama-Factory进行快速指令微调。这里我们主要尝试使用transformer库的原生方法来执行全量指令微调。
- 指令微调数据集
不同于预训练数据集,需要海量、高质量、多样性的文本数据,才能训练得到一个流畅问答的大模型,全量指令微调对于数据集的要求相对较低,制作门槛也相对较低,其中Llama-Factory上有非常完整的目前可用于中英文指令微调的数据集:https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md#%E6%95%B0%E6%8D%AE%E9%9B%86

这里我们选取数据规模相对较小的匠数科技的sft数据集。该数据集是一个是一个完整、格式统一、安全的大模型训练和研究资源。从网络上的公开数据源收集并整理了大量开源数据集,对其进行了格式统一,数据清洗,包含10M条数据的中文数据集和包含2M条数据的英文数据集。总量大约在3B token,适合小尺寸中文大语言模型进行指令微调:

匠数科技大模型sft数据集官方地址:https://www.modelscope.cn/datasets/deepctrl/deepctrl-sft-data 。
一、借助transformer原生方法完成指令指令微调
1.指令微调数据集获取与数据集清洗
- 借助魔搭社区下载数据集
|
Bash |

|
Bash |

下载完成后即可在dataset文件夹内看到该文件:

网盘地址如下:

- 查看指令微调的数据文件
|
Python |
|
Plaintext |
其中每条数据的数据格式如下:

而其中我们只需要提取对话的信息即可:将符合条件的部分保存到一个 CSV 文件中。整个过程涉及到数据的筛选、清洗、处理和写入。具体步骤如下:
- 选择输入文件:
- 根据参数 contain_history,确定要处理的数据集文件名为 'sft_data.csv' 或 'sft_data_single.csv'。
- 文本筛选函数:
- chinese_ratio(text):计算一个文本中中文字符的比例,用来筛选是否大部分内容是中文。
- 处理数据并写入 CSV:
- process_and_write_data(data):对数据进行筛选,检查每一条对话是否符合以下条件:
- 如果 contain_history 为真,则数据必须有对话历史。
- 对话的问句 (q) 和答句 (a) 都必须存在,并且它们的长度要在指定范围内(问句长度在 10 到 256 之间,答句长度在 5 到 256 之间)。
- 问句和答句的中文字符比例必须大于 90%。
- 如果数据符合条件,则将问句、答句和对话历史(如果有的话)添加到列表中,并写入 CSV 文件。
- 逐行读取 JSONL 数据集:
- sft_datasets 包含了一个要处理的 JSONL 数据集文件路径。
- 使用 jsonlines 库逐行读取数据,并将问答对保存在 data 列表中。每当 data 列表中累计了 1000 条记录(chunk_size),就将数据进行处理并写入到 CSV 文件中。
- 如果读取的行有格式问题,代码会跳过并继续处理下一行。
- 进度条:
- 使用 tqdm 库为整个处理过程添加进度条,显示处理数据的进度,并在每处理 chunk_size 条数据后更新进度。
- 输出结果:
- 生成的 CSV 文件包括三个列:history(对话历史)、q(问句)、a(答句)。
|
Python |
|
Python |
|
Python |
|
Plaintext |
|
Python |
|
Python |
|
Plaintext |
查看处理后的数据文件:
|
Python |
|
Plaintext |

注,课件网盘中还提供了一个同名的csv文件,该文件是包含了一部分英文对话的指令微调数据集,相对上述代码创建的数据集更大。课上实验选择其中一个即可。

2.指令微调代码编写与代码解释
- 指令微调代码
在准备好数据集之后,接下来即可开始指令微调。首先我们需要编写一个指令微调的脚本文件full_sft.py其中代码如下:
|
Python |
- 指令微调代码解释
接下来,我们分部分解释代码的主要功能和逻辑:
1. 导入依赖库
|
Python |
- 这部分导入了常见的库,如 os、time、pandas,以及用于深度学习训练的 PyTorch 库。
- 主要导入了用于加载分词器和模型的 transformers 库(Hugging Face 的工具),以及自定义的 Transformer 模型、配置文件 LMConfig 和数据集处理模块 SFTDataset。
2. 日志记录函数 Logger
|
Python |
- 功能:这个函数用于在分布式训练中,只在主进程上输出日志内容(因为在分布式环境中,可能会有多个进程执行同样的操作)。
- dist.get_rank():返回当前进程的 rank,只有 rank 为 0 的进程(主节点)才会输出日志。
3. 学习率调度函数 get_lr
|
Python |
- 功能:实现余弦退火学习率调度(Cosine Annealing Learning Rate)。在训练的早期,学习率先逐步升高(warmup),然后在训练后期逐步减小。
- 这种调度策略有助于模型在训练中快速收敛,同时避免后期训练时步长过大导致不稳定。
4. 模型训练函数 train_epoch
|
Python |
- 核心功能:执行模型的单轮训练,计算损失并更新模型参数。
- 重要步骤:
- 学习率更新:通过 get_lr 动态调整学习率。
- 损失计算:使用 cross_entropy 损失函数,忽略 ignore_index=0(常用于忽略填充的 token)。
- 梯度累积:通过 args.accumulation_steps 控制梯度累积,适用于较大模型的训练。
- 梯度裁剪:使用 clip_grad_norm_ 限制梯度的最大范数,防止梯度爆炸。
- 保存检查点:定期保存模型权重到 .pth 文件中,支持恢复训练。
5. 模型初始化 init_model
|
Python |
- 功能:加载模型和分词器,支持从不同源加载模型(如自定义模型权重或 Hugging Face 的 AutoModel)。
- 模型权重加载:从 ./out 目录中加载预训练权重(.pth 文件),并加载到 Transformer 模型中。
- 参数统计:计算模型的总参数量并输出。
6. 分布式训练初始化 init_distributed_mode
|
Python |
- 功能:初始化分布式训练环境,使用 NCCL 后端进行 GPU 通信。适用于多 GPU 或多节点训练。
7. 主流程
|
Python |
- 命令行参数解析:使用 argparse 解析训练相关参数(如学习率、
批次大小等)。
- 数据加载:使用 SFTDataset 处理数据集,DataLoader 负责将数据批量化用于训练。
- 自动混合精度训练:通过 torch.cuda.amp.GradScaler 实现半精度训练(如 float16),以减少显存占用。
- 训练循环:遍历训练的 epoch,调用 train_epoch 进行模型更新。
- 代码保存
编写完代码后,将其保存在项目主目录下:

3.全量指令微调训练流程
- 开启全量指令微调
需要注意的是,当前脚本是从固定路径下读取质量微调数据,所以在开始脚本之前,要再次确认指令微调数据是否已经创建完成:

|
Bash |

并且可以在wandb中查看训练效果:

- 中断后继续训练
和预训练一样,全量微调的代码也是支持中断后继续训练的,并且和预训练一样,默认情况下是训练1000步保存一次:

同时会保存模型参数如下:

此外,也可以继续使用上一小节介绍的screen工具来持久化会话。
- 训练结束

- 768模型全量指令微调
首先需要确认out文件夹内存在pretrain_768.pth预训练模型:

然后修改./model/LMConfig.py内容如下:

然后直接运行脚本即可:
|
Bash |

二、模型对话效果测试与高层模型调用API封装
在完成了模型指令微调之后,接下来即可测试模型对话效果。
1.使用transformer库进行模型推理
- 对话测试
|
Python |
|
Plaintext |
|
Python |
- 目的:设置随机数种子是为了确保模型运行时的确定性和复现性。
- random.seed、np.random.seed 和 torch.manual_seed 用于固定 Python、NumPy 和 PyTorch 的随机数生成器。
- 设备选择:优先选择 GPU 进行推理。如果 GPU 不可用,则使用 CPU。
- cudnn.deterministic = True 确保在使用 CUDA 加速时,结果可以复现,但会略微降低性能。
|
Python |
|
Python |
|
Plaintext |
这里确保"dim": 768或者512,才可以使用对应参数的模型。
|
Python |
|
Plaintext |
|
Python |
- 模型加载:
- LMConfig:初始化模型配置,例如设置 max_seq_len 表示模型的最大输入序列长度。
- 加载模型权重:从指定的 model_path 路径加载已经微调好的模型权重 (full_sft_512.pth)。
- 模型评估模式:model.eval() 切换模型为评估模式,禁用 dropout 和 batch normalization 的训练行为。
- 分词器加载:
- 分词器用于将输入的自然语言文本转换为模型所需的 token 序列,并可以将模型输出的 token 序列转回人类可读的文本。这里从 mateconv_tokenizer 路径加载分词器。
|
Python |
- 功能:这个函数用于生成对话回复,流程如下:
- 自定义 prompt:将用户输入(prompt)转换为自定义格式,这里使用了 apply_chat_template 生成对话的模板,将 messages 处理为可供模型生成的输入。
- 生成 input_ids:将 new_prompt 转换为 token 序列 (input_ids) 供模型使用,并且调整其形状以适应批处理(unsqueeze(0) 增加批次维度)。
- 模型生成:通过 model.generate 函数,使用贪心搜索或随机采样(受 temperature 和 top_k 控制)生成回复。
- temperature 控制生成时的随机性,值越小生成结果越确定,越大则越随机。
- top_k 限制采样的选择范围,取概率最高的 k 个 token。
- 逐步解码:模型生成结果会被逐步解码为可读文本,直到完成生成或遇到 eos_token。
- 返回结果:最终返回生成的完整回复文本。
- prompt:用户输入的对话文本,模型将根据该输入生成回复。
- temperature:采样时的随机性控制参数,较高的值会增加生成内容的多样性,较低的值会生成更确定的输出。
- top_k:采样时只从概率最高的前 k 个 token 中选择,限制生成的词汇选择范围,避免生成低概率的单词。
- stream:决定是否以流式方式逐步获取生成结果。
这里需要注意的是,大模型判断回复停止的主要依据是 eos_token(End of Sequence token),即序列结束标记。eos_token 是用于告诉模型生成任务已经完成的特殊标记。模型在生成文本时,一旦生成了 eos_token,它就知道应该停止输出。
大模型如何判断下一个单词(token)是 eos_token(序列结束标记),实际上是通过模型的生成机制,结合模型在预训练和指令微调阶段学到的语言模式来决定的。模型通过概率分布来选择下一个 token,而这个概率分布是在预训练和指令微调中学到的。
1. 生成机制的工作原理:
模型生成文本的过程通常基于自回归机制,也就是模型在生成每个 token 时,都会基于之前生成的所有 token 来预测下一个 token。它并不会明确知道下一个 token 是什么,而是通过计算每个可能 token 的概率分布,从中选择最可能的 token(包括 eos_token)。
- 输入上下文:给定一个输入(如用户问题或对话),模型会根据上下文生成一个概率分布,这个分布表示每个可能的 token 出现在该位置的概率。
- 选择下一个 token:模型通过贪心搜索、随机采样(基于 temperature)或其他策略,选择下一个 token。eos_token 作为候选之一,它的概率也由模型预测出来。
- 生成停止:当模型预测出 eos_token 并选择它作为下一个 token 时,生成过程停止。
2. 影响 eos_token 判断的因素:
(1) 预训练的影响:
预训练的过程中,模型会接触到大量的自然语言文本。通过自回归语言模型的方式,模型学会了如何生成自然语言,包括如何适时结束一段句子或文本。因此,预训练赋予了模型关于自然语言的结构、语法、句法模式的基础知识。
- 预训练阶段的 eos_token:在预训练的文本数据中,通常会在每个训练样本的末尾加上 eos_token,因此模型在预训练中学会了何时结束一段文本。在生成过程中,模型会根据上下文推测是否应该结束文本。
(2) 指令微调的影响:
指令微调专门针对任务的要求进行了微调,模型不仅学习了如何生成回答,还学习了如何在不同的任务和上下文中生成适合的结束标记。因此,指令微调对 eos_token 的判断进一步进行了优化,使得模型在实际任务中能够更好地判断何时结束输出。
- 指令微调阶段的 eos_token:指令微调阶段的训练数据中,通常会标注任务的起始和结束,并提供明确的输入和输出。这使得模型能更好地理解在特定任务(如对话、问答等)中,何时应该生成 eos_token 来结束对话或回答。
3. 模型如何具体判断 eos_token:
在每个生成步骤中,模型会基于已经生成的 token 来预测下一个 token。预测的过程通常如下:
- 概率分布计算:模型通过计算输入 token 序列的上下文信息,输出一个表示所有可能 token 的概率分布(Softmax)。这个概率分布表示每个 token 出现的可能性,包括 eos_token。
- 例如,假设模型在某个位置预测下一 token 时,给出了以下概率分布:
- the: 0.4
- a: 0.3
- eos_token: 0.2
- 其他 token:0.1
- 选择 token:根据生成策略(如贪心搜索、随机采样等),模型会选择概率最大的 token,或者根据设定的 temperature 或 top_k 进行采样。如果 eos_token 的概率最高或者通过采样选中,模型会生成 eos_token,表示生成过程结束。
- 贪心搜索:选择当前概率最大的 token。
- 随机采样:根据概率进行采样,temperature 控制采样的随机性。
- top-k 采样:只从概率最高的前 k 个 token 中选择。
- 测试问答
|
Python |
|
Plaintext |
|
Python |
|
Plaintext |
|
Python |
|
Plaintext |
|
Python |
|
Plaintext |
2.使用OpenAI风格API调用MateConv
- 创建OpenAI风格API中继服务脚本
使用 OpenAI 风格 调用私有化训练的大模型具有显著的优势和必要性,特别是在模型部署、集成和易用性方面:
1. 简化接口设计,提升易用性
OpenAI 的 API 风格以其简单、直观的设计著称,用户只需发送输入文本,API 便会返回生成的响应。使用这种风格调用私有化大模型能够极大降低用户的开发和集成成本,使非专业开发人员也能轻松调用模型。
- 无缝集成:通过统一的接口风格,开发者可以快速将模型集成到现有系统中,无需了解复杂的底层架构或实现细节。
- 易于操作:OpenAI 风格的 API 调用简洁明了,通常以 JSON 格式交互,标准化的接口便于维护和升级。
2. 灵活的多任务处理
使用 OpenAI 风格调用私有化模型能够灵活地适应多种任务场景,如对话生成、文本分类、问答系统等。API 接口允许开发者通过简单的指令输入,快速切换模型任务,从而提高开发效率和应用场景的灵活性。
- 多功能支持:开发者只需通过不同的 API 调用参数或指令,即可实现不同的功能,而无需重新设计接口。
- 任务适应性强:对于不同的自然语言处理任务,使用 OpenAI 风格的 API 允许模型在对话、内容生成、语言理解等多任务中快速切换和扩展。
3. 统一调用体验,提升用户和开发者体验
OpenAI 风格的 API 调用方式已经在业界广泛应用和认可。使用类似的风格调用私有化大模型,能够为开发者提供一致的使用体验,减少学习曲线和使用门槛。同时,企业能够保持与外部 API 调用的接口一致性,方便未来扩展和维护。
- 一致性和可移植性:开发者可以利用同样的调用方式无缝切换公共 API 和私有化模型,保持一致的代码风格。
- 用户体验提升:通过简洁的接口,最终用户能够更快速地与大模型进行交互,提升整体使用感受。
- 编写OpenAI风格API调用脚本
|
Python |
然后将其命名为openai_api.py并放在主目录下:

- 运行OpenAI API服务
然后在命令行中运行如下代码,以开启后端OpenAI API服务:
|
Bash |

- OpenAI风格API调用MateConv
|
Python |
|
Python |
|
Python |
|
Python |
|
Plaintext |
|
Python |
|
Plaintext |
|
Python |
|
Plaintext |
|
Python |
|
Plaintext |
- 创建基于前端的聊天机器人
将web_chat放在项目主目录下:

然后在命令行中运行:
|
Bash |

若未安装streamlit,则需要使用如下指令进行安装
|
Bash |
然后将8501映射到本地:

然后打开浏览器,即可在8501端口进行对话:

更多推荐
所有评论(0)