生成式 AI 通过 推动众多 应用的创新和提高效率,正在改变企业。然而,采用大型 基础模型 会带来一些挑战,包括高成本、慢性能、以及数据隐私问题。许多企业不愿与外部 LLM 提供商共享敏感代码或数据。此外,虽然基础 LLM 擅长处理一般任务,但它们通常需要大量的提示工程,才能在以企业为中心的特定用例中实现高准确性。
微调 小语言模型 (SLMs) 通常利用知识蒸馏等技术,为应对这些挑战提供了极具吸引力的解决方案。这些较小的 LLM 可提供接近更大模型的性能,并且速度更快、成本效益更高。此外,SLMs 可以部署在本地或虚拟私有云 (VPCs) 中,使企业能够确保敏感数据的安全。然而,微调较小的模型需要高质量的标记数据,而创建这些数据既耗时又昂贵。
本文介绍了一种自动微调方法,该方法通过使用数据飞轮策略来应对这些挑战。数据飞轮策略是一种反馈驱动机制,可迭代地提高模型性能。该方法融合了 课程学习 ,这是一种受人类学习启发的技术,根据复杂性逐步引入训练数据。通过使用大型“教师”模型生成和结构化合成训练数据,此方法可优化微调过程,使较小的模型能够更有效地处理复杂任务,同时尽可能减少人工干预。
我们将介绍以下主题:
- 自动微调方法 概述:创建高效训练工作流程的师生范式 。
- 实施步骤:关键阶段,如考试生成、评估和微调。
- 代码 审查自动化中的应用 :严重程度评级和解释生成等真实示例,其中 自动微调 SLM (Llama 3 8B Instruct+ 低等级适应 (LoRA),或 Llama3-8b+LoRA) 将准确性提高了 18%,性能优于更大的模型,并提供了与专家匹配的解释,同时降低成本和延迟。
- 经验教训: 可扩展且经济高效的 AI 解决方案的最佳实践。
在本文结束时,您将了解微调 SLM 如何帮助企业在解决成本、延迟和可扩展性相关挑战的同时,实现具有竞争力的准确性。虽然这里的重点是代码辅助,但该方法适用于各种企业用例。
自动微调方法概述
开发的自动微调方法从教师如何调整课程以解决学生的特定改进领域中汲取灵感。它采用师生范式,融合了知识蒸馏的原则。
在此过程中,大型 LLM (教师) 使用五个迭代步骤为小型 LLM 或 SLM (学生) 组织和准备训练数据 (课程):
1、考试生成:教师 LLM 根据先前的成绩、用户反馈(数据飞轮)和先前的考试结果,为学生 SLM 创建测试 。
2、参加测试 :学生参加由教师生成的测试。
3、评估:教师评估学生的表现,突出学生的优势和改进领域。
4、课程生成:教师根据评估结果定制培训,并根据具体不足之处进行调整。
5、微调:学生使用 LoRA 等技术对更新的数据集进行微调。 与调整所有模型参数并需要大量计算资源的传统微调不同,LoRA 优化了一组较小的特定于任务的参数,从而提高了流程的成本和内存效率。
重复此过程,直到学生的表现稳定下来或达到计算预算,从而确保经济高效的训练。
实施微调
以下各节将深入探讨如图 1 所示的已开发自动微调工作流程中的五个迭代步骤。
1、生成考试
教师 LLM 使用下方的 EXAM_PROMPT
生成考试。提示输入包括:
- 任务数据 :与任务相关的特定数据,包括用户反馈或合成示例。
- 任务提示 :将任务数据转换为学生 LLM 的单个训练示例的提示。
- 当前的 LLM 熟练程度 :学生在之前评估中的 LLM 熟练程度。
- 反馈 :由教师生成的见解,凸显了学生的不足之处。
为生成试题,教师 LLM 会对 DATA_SOURCE
中的每个条目应用 TASK_PROMPT
,根据学生和需要改进的领域定制问题。
EXAM_PROMPT
的示例如下所示。
EXAM_PROMPT = """ [TASK] %s [DATA SOURCE] %s [PREVIOUS_EXAM_RESULTS] Proficiency: %s Feedback: %s Create an exam of %s questions based on the task after the [TASK] tag. You can use the data after [DATA_SOURCE] for creating the dataset. Modify the data in the data source appropriately to create questions for the exam (for example to balance the exam). If there is none, then create your own. Results for the expected proficiency and feedback from the previous exam (if any) are indicated after [PREVIOUS_EXAM_RESULTS]. You can use that information to create a better exam (in terms of difficulty, questions etc). The complete exam must appear as json after any thoughts you have and there must be no other text after it. Think about your answer carefully. Your output format must strictly be in the following format as a json. "exam": A list of json objects in the format below "question": A json with the information in the [INPUT_JSON] of the [TASK] "answer": A json with the response based on the [OUTPUT_JSON] of the [TASK] """ |
TASK_PROMPT
会生成任务特定的试题。以下是在代码审查期间预测代码更改严重程度的示例。
TASK_PROMPT = """Assign an issue type to the code below. [ISSUE_TYPES] critical: Security vulnerabilities, bugs that will cause a crash or code that can abruptly exit the execution. major: Severe bugs that will cause a system to produce incorrect results. minor: Results in some unexpected or undesired behavior, but not enough to disrupt system function. trivial: Issue won't result in any noticeable breakdown of the system. (e.g. docstring changes, comments etc) The code and review are formatted as json below. [INPUT_JSON] %s Your output must be in json with no other text. The format is below. {"issue_type": A value in [critical, major, minor, trivial]} """ INPUT_JSON = """ "code": The code snippet under review "review": A review of the code. """ |
生成考试步骤会生成 JSON 格式的问答对列表。以下是用于代码审查严重程度预测任务的问答对示例。问题包括代码更改和审核反馈,而答案则指定预期的严重程度。
{ "question" : { "code" : "<code snippet>" , "review" : "<code review>" }, "answer" : { "issue_type" : "major" } } |
2、参加测试
在此步骤中,我们将使用第 1 步中生成的问题来评估学生的 LLM。每个考试问题都与 TASK_PROMPT
结合,以创建提示列表。学生 LLM 会处理这些提示,并根据其理解生成答案。
例如,在代码审查严重程度预测中,学生 LLM 分析代码片段和审查反馈,将严重程度分类为 critical、major、minor 或 trivial。
3、评估
学生 LLM 参加考试后,教师 LLM 使用 EXAM_EVALUATION_PROMPT
评估其性能,其中包括:
- 任务提示 :用于生成试题。
- 考试结果 :第 2 步中学生的答案 (格式为问答对列表)。
- 数据源 :教师用于生成其他训练示例的可选数据源。如果未提供,则教师 LLM 会生成自己的示例。
- 训练样本数 :指定要创建的新训练样本数。这些示例会添加到现有训练数据中,以解决学生的不足之处。
教师 LLM 会分配能力分数 (1-10)、提供反馈并生成定制的训练数据集,以改善学生的不足之处。
EXAM_EVALUATION
示例如下所示。
EXAM_EVALUATION_PROMPT = """For the task specified by [TASK], an exam was administered for evaluating the model's current capabilities. The results appear after [EXAM_RESULTS] and is a json list where each element is a json consisting of fields: "question": The data for the task "answer": The correct answer "model_answer": The model's answer. If there is no answer here, then assume that the model could not answer the question correctly. [TASK] %s [EXAM_RESULTS] %s Your task is to evaluate these exam results to determine the models capabilities from a scale of 1-10 with 10 being proficient. Next, based on the models results on the exam, identify areas for improvement. Finally, please develop a curriculum for of %s examples for helping to train the model. The output is VALID JSON formatted as follows: "feedback": The feedback as a string "proficiency": The proficiency score of the model as an integer. "dataset": A list of json objects for training the model. "question": A question for training the model in the exact same format as the [TASK] asks "answer": The answer to the question in the same format as the [TASK] asks You must use the data after [DATA_SOURCE] for creating the dataset. You can modify the data as you see fit. If there is none, then create your own. [DATA_SOURCE] %s """ |
4、课程生成
将在第 3 步中生成的新训练示例与现有数据集相结合,以创建更新的课程。这种量身定制的课程专门针对学生的不足之处,从而实现更有效的微调。
5、微调
学生 LLM 使用更新的课程进行微调。微调利用 NVIDIA NeMo 框架 ,并利用 NeMo 框架 Docker 容器中提供的 megatron_gpt_finetuning.py 脚本进行高效的微调。
为简化此过程,微调工作流封装在 PEFTFineTuning
类中,可在 Python 中实现无缝集成和执行。以下是如何启动微调的示例。
import subprocess import pathlib import os import shutil def initialize_directory(directory, clean = True ): if os.path.exists(directory) and clean: shutil.rmtree(directory) os.makedirs(directory, exist_ok = True ) class PEFTFineTuning: MEGATRON_GPT_FINETUNING_SCRIPT = \ "/opt/Nemo/examples/nlp/language_modeling/tuning/megatron_gpt_finetuning.py" def __init__( self , scheme, dataset, model, adapter_name = None , output_dir = None , torchrun_nproc_per_node = 1 , devices = 1 , num_nodes = 1 , megatron_amp_O2 = True , mcore_gpt = True , tensor_size = 1 , pipeline_size = 1 , micro_batch_size = 1 , global_batch_size = 16 , ds_num_workers = 0 , train_sampling_probs = [ 1.0 ], adapter_restore_path = None , lr = 1e - 4 , adapter_dim = 32 ): self .nproc_per_node = torchrun_nproc_per_node self .megatron_gpt_params = { "trainer.devices" : devices, "trainer.num_nodes" : num_nodes, "model.megatron_amp_O2" : megatron_amp_O2, "++model.mcore_gpt" : mcore_gpt, "model.tensor_model_parallel_size" : tensor_size, "model.pipeline_model_parallel_size" : pipeline_size, "model.micro_batch_size" : micro_batch_size, "model.global_batch_size" : global_batch_size, "model.data.train_ds.num_workers" : ds_num_workers, "model.data.train_ds.concat_sampling_probabilities" : train_sampling_probs, "model.data.validation_ds.num_workers" : ds_num_workers, "model.peft.peft_scheme" : scheme, "model.optim.lr" : lr, "model.peft.lora_tuning.adapter_dim" : adapter_dim } if adapter_restore_path is not None : self .megatron_gpt_params[ "model.peft.restore_from_path" ] = \ adapter_restore_path self .model = model self .dataset = dataset self ._adapter_name = adapter_name if self ._adapter_name is None : self ._adapter_name = "%s_%s" % (scheme, dataset.name) self .output_dir = output_dir if self .output_dir is None : self .output_dir = "%s/%s" % ( self .model.model_dir, self ._adapter_name) @property def adapter_name( self ): return self ._adapter_name def _get_peft_cmd( self ): cmd = [ "torchrun" ] cmd.append( "--nproc_per_node=%s" % ( self .nproc_per_node)) cmd.append(PEFTFineTuning.MEGATRON_GPT_FINETUNING_SCRIPT) for key, value in self .megatron_gpt_params.items(): cmd.append( "%s=%s" % (key, value)) return cmd def finetune( self , clean = True , val_check_interval = 20 , max_steps = 8000 ): initialize_directory( self .output_dir, clean) cmd = self ._get_peft_cmd() cmd + = [ "exp_manager.exp_dir=%s" % ( self .output_dir), "exp_manager.explicit_log_dir=%s" % ( self .output_dir), "trainer.precision=%s" % ( self .model.precision), "trainer.val_check_interval=%s" % (val_check_interval), "trainer.max_steps=%s" % (max_steps), "model.restore_from_path=%s" % ( self .model.model_path), "model.data.train_ds.file_names=%s" % ( self .dataset.train_ds), "model.data.validation_ds.file_names=%s" % ( self .dataset.val_ds), ] subprocess.call(cmd) def get_nim_adapter_path( self , base_dir = ncodepro.NIM_STORE): nim_store_dir = "%s/%s" % (base_dir, self ._adapter_name) nemo_model_path = "%s/%s.nemo" % (nim_store_dir, self ._adapter_name) return nemo_model_path def save( self , base_dir = ncodepro.NIM_STORE, clean = True ): nim_store_dir = "%s/%s" % (base_dir, self ._adapter_name) nemo_model_path = "%s/%s.nemo" % (nim_store_dir, self ._adapter_name) file .initialize_directory(nim_store_dir, clean) peft_checkpoint = "%s/checkpoints/" \ "megatron_gpt_peft_lora_tuning.nemo" % ( self .output_dir) shutil.copyfile(peft_checkpoint, nemo_model_path) |
代码审查自动化中的实际应用
代码审查对于确保软件质量和性能至关重要,传统上由人工审查人员执行。典型的代码审查流程包括以下内容:
- 作者提交包含实现某项功能或问题修复的代码的合并请求 (Merge Request,MR)。
- 人工审阅者会评估 MR,提出更改建议或批准代码。
- 如果请求更改,作者会修改并重新提交 MR,重复此过程,直到代码被接受。
生成式 AI 的最新进展实现了代码审查流程的自动化,如图 3 所示。经过微调的 LLM 会评估 MR 以识别错误或问题,为每个 LLM 分配严重程度,并提供评级说明。该流程会过滤掉低于用户定义值的低严重程度问题,使开发者能够专注于安全漏洞等关键问题。
经过微调的 SLMs 增强了 NVIDIA 自动代码审查的以下两个关键领域:
- 严重程度评级 :提高 LLM 在分配严重程度时的准确性。
- 解释生成 :提高 LLM 的推理清晰度和质量。
性能评估:准确性和质量提升
我们使用自动微调技术微调了 Llama 3 8B Instruct 模型 ,从而生成了 Llama38B+LORA。我们评估了其在以下两项任务中的性能:
- 严重程度评级预测:测量严重程度预测的准确性。
- 严重程度解释生成:评估严重程度评级的解释质量。
严重程度等级预测
利用知识蒸馏,在 GPT-4 指导下进行微调,显著提高了较小模型的严重程度评级预测准确性。如图 4 所示,与基准模型 (无微调的 Llama 3 8B) 相比,经过微调的 Llama 3 8B+LoRA (以绿色突出显示) 实现了超过 18% 的改进。
值得注意的是,微调的 Llama 3 8B+LoRA (Llama3-8b+LORA) 也优于大型模型,例如 Llama 3 70B (大 8 倍) 和 Nemotron 4 340B Instruct (大 40 倍)。尽管准确性更高,但该模型保持了更低的延迟和更低的推理成本,这表明开发的微调方法对于优化较小的模型既高效又高效。
严重程度说明质量
为评估解释质量,我们使用教师 LLM(GPT-4)作为评委。这位教师将微调模型的解释与其他模型生成的解释进行了比较。图 5 展示了偏好差异,该差异用于测量 GPT-4 在微调模型的解释上优于其他模型的频率。偏好正差表示微调模型的表现优于其他模型,而负值表示恰恰相反。
结果表明,经过 LoRA 微调的 Llama 3 8B 模型 (llama3-8b+LORA) 的性能始终优于 Llama 70B、Nemotron 340B,甚至超越其基准 (Llama 3 8B)。在所有比较中,微调模型要么是首选,要么表现同样出色,这表明其与专家级解释质量标准高度一致。
微调 SLM 的优势:效率和性能提升
在代码审查自动化中应用微调 SLMs(Software Language Models)展示了两个主要优势:
- 经济高效的微调: 在代码审查任务中使用微调 SLMs 可以降低成本并缩短延迟。对于需要平衡预算限制和性能要求的企业工作流而言,这是一种理想的方法。
- 提高准确性和一致性: 使用微调的 SLMs 显著提升特定于任务的性能。通过提高严重程度评级并使解释符合专家级标准,经过微调的 SLMs 可提供可靠的评估,帮助开发团队专注于关键代码问题。
从使用 SLM 扩展 AI 中汲取的经验教训
通过使用自动化方法开发微调 SLMs,我们获得了宝贵的见解,有助于创建专为企业应用定制的经济高效且可扩展的 AI 解决方案。主要课程包括:
- 从有针对性的微调入手 :专注于较小的模型,在性能和资源利用率之间实现最佳平衡。这使企业能够在扩展之前有效评估权衡。
- 利用 参数高效微调(PEFT) 和 知识蒸馏 :将 LoRA 等 PEFT 方法与知识蒸馏相结合,可确保在最大限度地减少计算开销的同时实现高性能,使其成为资源受限环境的理想选择。
通过使用微调的小型 LLM,企业可以解决大型模型通常带来的高成本和低性能挑战。这一策略使企业能够实现竞争准确性,同时保持 AI 解决方案的成本效益、响应速度和针对特定需求定制。虽然本文重点介绍了代码辅助领域的应用,但这种方法具有高度通用性,适用于各种企业用例。
开始针对 AI 应用微调模型
了解 NVIDIA 生成式 AI 技术 如何帮助您根据特定需求微调和部署模型。如果您刚开始使用 NVIDIA NIM,请查看 Building Your First LLM Agent Application 和 Build Your First Human-in-the-Loop AI Agent with NVIDIA NIM,获取有关 NVIDIA 工具和方法的实践经验,以开发和部署 NVIDIA NIM LLM 微服务 。
致谢
我们衷心感谢 Rushang Karia、Agustin Rivera、Mark Philipp、Abhinav Kumar、Anbang Xu、Rama Akkiraju、Ashwin Poojary、Ahmad Daoud 和 Ashwin Jha 的宝贵贡献和大力支持。他们的专业知识和献身精神有助于这项工作取得成果。