1. 环境准备:从零搭建你的多卡微调工作站

想在一台机器上用好几张显卡来微调GLM-4-9b-chat这样的大模型,第一步就是把环境给整利索了。这就像你要在家里搞个木工坊,总得先把电锯、刨子、工作台这些家伙事儿备齐,并且确保它们都能通上电、转得起来。我见过不少朋友,模型和数据都准备好了,结果卡在环境配置上,一折腾就是好几天,非常影响心情。所以咱们这部分得稳扎稳打,一步一个脚印。

首先,你得有一台带有多张NVIDIA GPU的服务器。这个“多卡”可以是两张,也可以是四张、八张,取决于你的需求和预算。我这次实战用的是两张32GB显存的卡,内存配了160GB。为什么强调内存要大?因为GLM-4-9b-chat模型本身参数就有90亿,加载进来就需要不小的内存,再加上训练过程中的各种中间变量和数据加载,内存小了很容易就爆掉。如果你用的是云服务器,像阿里云、腾讯云这些平台都有提供多卡实例,直接按需购买就行,省去了自己攒硬件的麻烦。自己有机器的朋友,记得在BIOS里把PCIe通道的带宽模式调好,比如改成Gen4 x16,确保显卡和CPU之间数据传输没有瓶颈。

拿到机器后,第一件事不是急着装框架,而是先把系统的基础环境搞好。我强烈推荐使用Ubuntu 20.04或22.04 LTS版本,稳定性有保障,社区支持也好。然后,安装合适版本的NVIDIA驱动和CUDA工具包。这里有个小坑,ms-swift对CUDA版本有要求,我实测下来,CUDA 11.8和12.1的兼容性都不错。你可以用 nvidia-smi 命令先查看一下驱动版本,然后用 nvcc -V 看看CUDA版本是否匹配。如果不匹配,去NVIDIA官网下载对应版本的驱动和CUDA Toolkit安装包,按照官方指引安装就行,记得安装完后要更新环境变量。

接下来就是Python环境的管理。我强烈建议使用虚拟环境,这能把你这个项目的依赖和系统里其他项目的依赖完全隔离开,避免版本冲突。用 conda 或者 venv 都行,我个人习惯用 venv,更轻量。操作很简单,打开终端,执行 python3 -m venv glm4_finetune_env 就能创建一个名为 glm4_finetune_env 的虚拟环境。然后激活它:source glm4_finetune_env/bin/activate。你会看到命令行前面多了个环境名的提示,这就表示你已经在这个“沙箱”里了,接下来所有操作都不会影响到系统全局。

1.1 安装ms-swift:两种方法,哪种更适合你?

环境激活后,就可以安装我们今天的主角——ms-swift了。这是魔搭社区出品的一个大模型微调部署框架,你可以把它理解成一个功能强大的“模型改装车间”。它把模型训练、推理、评测这些复杂流程都封装成了简单的命令,还支持超过450种大模型和150多种多模态模型,生态非常丰富。安装ms-swift主要有两种方式,各有优劣。

第一种是从源代码安装。这种方法适合喜欢折腾、想用最新特性或者需要深度定制的开发者。操作步骤是先用 git clone 把ms-swift的仓库拉取到本地,然后进入目录用 pip install -e . 以“可编辑”模式安装。-e 参数的意思是,安装后你修改源代码,效果会直接生效,不用重新安装。这对于你想研究框架内部机制或者打一些临时补丁非常方便。我刚开始用的时候,就喜欢用这种方式,可以随时看看某个函数具体是怎么实现的。

第二种是直接用pip安装。这是最省事、最推荐新手使用的方法。只需要一条命令:pip install ms-swift -U。这里的 -U 参数代表升级到最新版本。pip会自动从PyPI仓库下载ms-swift及其所有依赖项,包括torch、transformers、datasets等等。这种方式安装的版本是官方发布的稳定版,兼容性最有保障。我后来做项目为了求稳,基本都是用pip安装。无论用哪种方式,安装完成后,你都可以在Python环境里 import swift 试试,不报错就说明安装成功了。

这里有个非常重要的点,就是依赖版本的协调。ms-swift会安装一整套依赖,但有时候它默认安装的版本可能和你已有的环境,或者和GLM-4-9b-chat模型要求的版本有细微冲突。比如原始文章最后提到的那个 AttributeError,就是因为transformers版本(4.49.0)不兼容导致的。所以我的经验是,在安装ms-swift后,如果遇到奇怪的报错,可以尝试手动指定一些核心库的版本。例如,针对GLM-4-9b-chat,可以试试 pip install transformers==4.48.3 来降级。当然,最好还是先按照ms-swift默认的来,出了问题再针对性解决。

2. 模型与数据:微调的两大基石

环境搭好了,相当于车间准备完毕。接下来就要把我们要加工的“原材料”——预训练模型,和“设计图纸”——微调数据集,给搬进来。这一步看似简单,就是下载和复制,但里面其实有不少门道可以让你事半功倍,或者避开一些隐形的坑。

先说模型。GLM-4-9b-chat是智谱AI开源的对话大模型,基于ChatGLM3架构,拥有90亿参数,在中文理解和生成上表现非常出色。我们微调就是在它已经具备的强大通用能力基础上,注入特定领域的知识或技能。通过ms-swift下载模型非常方便,因为它集成了modelscope这个模型仓库。你只需要执行 pip install modelscope 安装模型库,然后使用 modelscope download --model ZhipuAI/glm-4-9b-chat 命令。这个命令会自动从魔搭的镜像站下载模型文件,速度通常比直接从Hugging Face拉要快很多。

模型下载后,默认会存放在 ~/.cache/modelscope/hub/ZhipuAI/glm-4-9b-chat 这个目录下。我建议你不要直接在这个缓存目录里操作,而是把它复制到一个你专门为这个项目创建的工作目录里,比如 /home/your_project/glm-4-9b-chat。这样做有几个好处:一是路径清晰好管理;二是避免不小心清理缓存时把模型删了;三是方便你同时进行多个不同的微调实验,每个实验都可以有一份独立的模型副本。复制命令就是简单的 cp -r。

2.1 数据集配置:格式是灵魂,质量是生命

模型准备好了,另一半重中之重就是数据集。微调的效果,七八成取决于你的数据质量。ms-swift支持多种数据集格式,最常用、也最推荐的是与OpenAI Chat Completion API兼容的 messages 格式,也就是原始文章里展示的JSON Lines(.jsonl)格式。这种格式非常直观,每条数据都是一段完整的、多轮对话的JSON对象。

我带你仔细拆解一下这个格式。每个JSON对象里,核心是一个 "messages" 列表。列表里的每个元素都是一个字典,代表对话中的一轮,包含 "role" 和 "content" 两个键。"role" 只能是三种:"system"、"user"、"assistant"。"system" 消息用于设定AI助手的角色和背景,比如“你是一名专业的软件测试工程师”。这条消息通常只在对话开头出现一次,它为整个对话定下了基调。然后是交替出现的 "user"(用户提问)和 "assistant"(AI回答)。一段完整的微调数据,就是由这样一个多轮对话构成的。

为什么强调格式要对?因为ms-swift在读取数据时,会严格按照这个结构去解析。如果你的JSON里少了引号,或者键名拼写错误,比如把 "messages" 写成 "message",程序就会报错,告诉你数据格式不对。我建议你在准备数据时,先用一个小样本,比如10条数据,写成.jsonl文件,然后用 swift sft 命令的 --dataset 参数试读一下,确保能成功加载,再投入全量数据。

数据内容本身更是关键。如果你想微调出一个测试用例生成专家,那么你的每一条训练数据,都应该是高质量的“用户需求-测试用例”对话对。用户的问题要贴近真实场景,比如“为登录功能设计边界值测试用例”;助理的回答则必须是规范、完整、可执行的测试用例描述。切忌用网上随便爬的、质量低劣的问答对,那只会让模型学会“胡言乱语”。数据的数量方面,对于LoRA这种高效的微调方法,通常有几百到几千条高质量数据,就能看到明显的效果提升。记得把数据分成训练集(train.jsonl)和验证集(dev.jsonl),比例大概8:2或9:1,验证集用于在训练过程中监控模型是否过拟合。

3. 微调实战:编写与解析你的训练脚本

万事俱备,只欠东风。这个“东风”就是告诉ms-swift具体怎么进行微调的训练脚本。原始文章里给出了一个 lora_run.sh 的示例,非常棒,我们就在这个基础上,把每一个参数都掰开揉碎了讲明白,这样你不仅能照着做,还能理解为什么这么做,以后自己调参也有底气。

首先,创建一个新的Shell脚本文件,比如叫 train_glm4_lora.sh。用任何文本编辑器(vim, nano, VSCode)都行。脚本开头通常是设置一些环境变量。nproc_per_node=2 这个变量很重要,它等于你使用的GPU数量。我用了两张卡,所以这里设为2。CUDA_VISIBLE_DEVICES=0,1 指定具体使用哪几张卡。如果你的机器有四张卡,但只想用第一和第三张,可以设为 0,2。MASTER_PORT=29501 是多卡分布式训练时通信用的端口号,如果默认的29500被占用了,就换一个,比如29501。

接下来就是核心的 swift sft 命令了。--model /your/path/to/glm-4-9b-chat 指定模型路径,就指向你之前复制出来的那个目录。--train_type lora 表示我们使用LoRA(Low-Rank Adaptation)方法进行微调。这是目前最流行的参数高效微调技术,它只训练模型里新增的一些小矩阵,而不是全部90亿参数,能节省大量显存和计算资源,效果却接近全参数微调。

--dataset 参数后面跟着你的训练集和验证集文件路径。--torch_dtype bfloat16 是模型计算的数据类型,bfloat16能在保持数值范围的同时减少显存占用,是多卡训练时的常用选择。--template default 表示使用模型默认的对话模板,对于GLM-4-9b-chat,这个模板会帮我们处理好对话历史格式。

训练过程相关的参数是调参的重点。--num_train_epochs 10 和 --max_steps 1000 定义了训练停止的条件,满足一个就会停止。通常我更关注 max_steps。--per_device_train_batch_size 1 是每张GPU每个训练步处理的样本数。因为模型很大,即使是用LoRA,单卡可能也只能放下batch size为1。但我们通过 --gradient_accumulation_steps 16 来实现“模拟”的大批量训练。梯度累积的意思是,先算16个batch的梯度,但不更新模型参数,等累积了16次后再一次性更新。这样等效的批次大小就是 1 * 2(GPU数) * 16 = 32。这个值对训练稳定性很重要。

--learning_rate 5e-4 是学习率,LoRA训练通常可以用比全微调大一点的学习率。--target_modules all-linear 是LoRA的一个关键参数,它指定将LoRA适配器加到哪些模块上。all-linear 意味着所有线性层(如Q, K, V, 输出投影层)都会添加,这是一个比较通用和有效的设置。

3.1 关键参数详解与避坑指南

除了上面提到的,还有一些参数对训练体验和结果影响巨大,我结合自己踩过的坑来说说。

--eval_steps 500 和 --save_steps 500 意味着每训练500步,就评估一次验证集上的表现,并保存一次模型检查点。这个频率需要根据你的总步数来调整。如果 max_steps 是1000,那么设置500就很合理,训练中点和结束都会保存。如果总步数有10000,那可以设为1000。保存检查点能让你在训练意外中断时,可以从最近的一个点恢复,而不是从头再来。

--output_dir output 指定所有输出(模型检查点、日志、配置文件)的存放目录。ms-swift会自动在这个目录下生成以时间戳命名的子文件夹,比如 v0-20250302-112152,里面再存放 checkpoint-500、checkpoint-1000 这样的文件夹,管理得非常清晰。

原始文章里提到的两个参数 --ddp_find_unused_parameters False 和 --max_length 2048 是解决实际问题的关键。第一个参数是为了解决多卡训练时的一个常见PyTorch DDP错误。当模型结构在训练中动态变化(某些参数在前向传播中未被使用)时,DDP会报错。设置 False 告诉框架不要检查未使用的参数,通常能解决这个问题。第二个参数 --max_length 定义了模型处理序列的最大长度。如果训练时你的数据都很长,但没设置这个参数,模型可能只会“看到”前面一部分内容,导致学得不全,推理时回答也就残缺不全。根据你的数据情况,可以设置为4096甚至更长。

最后,给脚本加上执行权限 chmod +x train_glm4_lora.sh,然后运行 ./train_glm4_lora.sh,就可以看到训练开始了。控制台会打印出损失(loss)下降的曲线,以及定期评估的指标。看到loss稳步下降,验证集上的指标(如生成内容的流畅度、准确性)在变好,说明你的微调正在正确轨道上。

4. 模型合并与推理:让微调成果投入使用

训练顺利跑完,output_dir 里躺着一堆检查点,比如 checkpoint-1000。但这个文件夹里并不是一个完整的、独立的模型文件,它里面主要包含两部分:原始的GLM-4-9b-chat基础模型权重,和训练得到的LoRA适配器权重(通常是很小的几个文件)。要想得到一个方便部署和分享的单一模型文件,就需要进行“模型合并”操作。

合并操作非常简单,ms-swift提供了 swift export 命令来完成。你只需要指定训练好的检查点路径,并设置 --merge_lora true 即可。例如:swift export --adapters /root/output/v0-20250302-112152/checkpoint-1000 --merge_lora true。这个命令会做两件事:首先,它会读取检查点目录下的 args.json 配置文件,自动获取原始模型路径、对话模板等信息,所以你不需要再手动指定 --model;其次,它将LoRA适配器的权重合并到基础模型的权重中,生成一个全新的、完整的模型文件,保存在类似 checkpoint-1000-merged 的目录里。这个合并后的模型,就和原始的预训练模型一样,可以被任何支持Transformers库的代码加载和使用,完全脱离了ms-swift框架的依赖。

4.1 推理测试:验证微调效果的关键一步

模型合并后,或者即使不合并,直接用带适配器的检查点,我们都可以进行推理测试,看看微调后的模型到底表现如何。这是最激动人心的环节,也是检验我们之前所有工作成果的时候。

使用带适配器的检查点进行推理,命令如下:

CUDA_VISIBLE_DEVICES=0 swift infer --adapters /path/to/checkpoint-1000 --stream true --temperature 0 --max_new_tokens 2048

这里 --adapters 参数指向训练保存的检查点目录。--stream true 启用流式输出,你可以看到模型一个字一个字生成回答的过程,很有感觉。--temperature 0 将采样温度设为0,这意味着模型在生成每个词时,总是选择概率最高的那个,这样得到的输出是确定性的,最适合测试任务型回复的准确性。--max_new_tokens 限制生成内容的最大长度。

运行命令后,终端会进入交互模式,提示你 “<<<”,这时你就可以输入你想问的问题了。比如,如果你微调的是测试用例生成任务,就可以输入:“为手机号输入框设计测试用例,要求包括格式校验、长度校验和边界值。” 然后观察模型的回复是否专业、全面、符合你数据集中教导的格式。

如果你使用的是合并后的模型,推理命令稍有不同:

CUDA_VISIBLE_DEVICES=0 swift infer --model /path/to/checkpoint-1000-merged --infer_backend pt --temperature 0 --max_new_tokens 2048

这里用 --model 参数指向合并后的模型目录,并且需要显式指定推理后端为 pt(PyTorch)。因为合并后的模型就是一个标准的PyTorch模型,不再包含ms-swift特定的适配器结构。

在推理测试时,你可能会遇到原始文章末尾提到的问题:回答不完整,或者出现奇怪的报错。回答不完整,大概率是训练时 --max_length 设置不够大,模型没“看到”长文本的尾部。而 AttributeError: ‘ChatGLMForConditionalGeneration’ object has no attribute ‘_extract_past_from_model_output’ 这个错误,我踩过好几次,根本原因就是transformers库的版本兼容性问题。GLM-4-9b-chat在某个版本的transformers(如4.49.0)中,其模型类的内部方法可能有变动。最直接的解决办法就是按照提示,降级transformers到兼容的版本,比如执行 pip install transformers==4.48.3。所以,在项目开始时就固定关键依赖的版本,能避免很多这类麻烦。

经过这一整套流程——从环境搭建、数据准备,到编写训练脚本、启动多卡并行微调,再到最后的模型合并与推理测试——你应该已经成功赋予了GLM-4-9b-chat模型一项新的专业技能。这个过程里,最花时间的往往不是跑命令,而是准备高质量的数据和调试参数。多卡训练大大缩短了迭代周期,让你能更快地看到结果,进行调整。希望这份详细的解析能帮你扫清障碍,顺利踏上大模型定制化的实践之路。如果在实际操作中遇到新的问题,不妨去ms-swift的GitHub仓库看看Issues,或者魔搭社区的相关讨论,通常都能找到解决方案。

更多推荐