基于 SWIFT(魔搭社区)训练 DeepSeek 模型完整代码示例:从 config.toml 骨架到推理验证 1. 为什么我建议你用 SWIFT 微调 DeepSeek如果你手上有一张 24G 显存的卡想给 DeepSeek 系列模型喂一点自己的业务数据让它回答问题时更贴合你的场景那么 ms-swift魔搭社区出品的训练框架是目前门槛比较低的一条路。它把模型加载、LoRA 注入、数据对齐、训练循环、权重保存这些环节都封装成了命令行参数你不需要自己写 Trainer也不需要手写 collator一个swift sft命令就能跑起来。这篇内容聚焦的是工程落地先给你一份可以直接复制的config.toml训练配置骨架再讲数据集怎么注册、训练怎么启动、权重存到哪、最后怎么用推理命令验证效果。整套流程我在单卡 4090 上跑通过一次最小可用训练数据量不大但链路是完整的。你跟着走一遍就能理解 SWIFT 微调 DeepSeek 的每个环节在干什么后面换成自己的数据也只是改路径和字段。适合谁看做过一点 Python、装过 PyTorch、知道 LoRA 是什么但没实际跑过微调的人或者跑过别的框架但想换到 SWIFT 的人。不适合完全没碰过命令行的纯小白因为中间涉及环境变量和路径配置。2. 前置准备环境、模型与 TaoToken 接入2.1 安装 ms-swift 和依赖SWIFT 的安装本身不复杂但版本要对齐。我实测下来Python 3.10 torch 2.1 以上比较稳。先建一个干净的环境conda create -n swift_ds python3.10 -y conda activate swift_ds pip install ms-swift -U pip install modelscope torch transformers datasets如果你要用 4-bit 量化加载来省显存再加一个pip install bitsandbytes装完之后用swift --help验证一下能打印出子命令列表就说明装好了。SWIFT 的命令行入口有swift sft、swift infer、swift export等我们主要用前两个。2.2 模型下载与 TaoToken 的作用DeepSeek 的权重在魔搭社区上有镜像直接指定模型 ID 就会自动拉取。但训练过程中你可能会遇到需要调用外部模型做数据清洗、或者训练完想对比一下基座模型和微调模型的输出差异这时候如果本地没有部署大参数模型可以走 API 的方式。TaoToken 在这里的角色是提供一个统一的模型调用入口你可以在它的控制台里创建 API Key然后用兼容 OpenAI 的接口去请求模型对话。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。注意 API 地址后面不加 UTM 参数直接写就行。具体来说训练前你可以用它快速验证数据格式对不对——把一条样本拼成 prompt 发给模型看返回是否符合预期训练后可以用它做 A/B 对比。创建 Key 的入口在控制台的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。如果你只是想先试试模型对话效果不写代码可以直接用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。注意TaoToken 是模型调用入口不是训练框架训练本身还是在本地或云端用 SWIFT 完成。两者配合使用一个负责训练一个负责推理验证和对比。3. 可复制的 config.toml 训练配置骨架SWIFT 支持两种启动方式纯命令行参数或者用config.toml文件。后者更适合工程落地因为参数多了以后命令行会很长配置文件方便版本管理和复用。下面这份骨架你可以直接存成ds_lora_sft.toml。3.1 完整配置文件片段# ds_lora_sft.toml # DeepSeek LoRA 微调配置骨架 [model] model_type deepseek-r1-7b-chat # 如果用本地权重改成 model_id_or_path /your/path/to/model model_id_or_path deepseek-ai/DeepSeek-R1-Distill-Qwen-7B torch_dtype bfloat16 load_in_4bit false # 显存紧张时改 true [train] output_dir ./output/deepseek_lora per_device_train_batch_size 2 gradient_accumulation_steps 8 learning_rate 2e-5 num_train_epochs 3 lr_scheduler_type cosine warmup_ratio 0.05 logging_steps 10 save_steps 100 save_total_limit 2 fp16 false bf16 true gradient_checkpointing true max_length 2048 report_to [tensorboard] [lora] lora_rank 8 lora_alpha 32 lora_dropout 0.1 target_modules [q_proj, k_proj, v_proj, o_proj] [dataset] dataset [/root/data/my_sft_data.jsonl] dataset_sample 2000 split_dataset_ratio 0.05几个参数我解释一下。model_type要和 SWIFT 内置的模型注册名对上DeepSeek-R1-Distill-Qwen-7B 是蒸馏版7B 参数在 24G 卡上 LoRA 微调比较舒服。load_in_4bit打开后显存能压到 12G 以下但训练速度会慢一些精度也有轻微损失看你取舍。target_modules我列了 q/k/v/o 四个投影层比只调 q/v 效果更稳代价是参数量翻倍但 LoRA 本身参数量小影响不大。split_dataset_ratio 0.05表示从数据里切 5% 做验证集训练过程中会打印 eval loss方便你判断有没有过拟合。3.2 数据集注册方式SWIFT 的数据集有两种来源内置数据集和本地文件。本地文件用 JSONL 格式每行一个样本。它支持两种字段结构你选一种就行。单轮指令格式{instruction: 你是一个电商客服助手, input: 这件衣服能退货吗, output: 可以的签收后7天内支持无理由退货请保持吊牌完整。} {instruction: 你是一个电商客服助手, input: 发货要多久, output: 一般48小时内发出偏远地区可能延迟1到2天。}多轮对话格式{conversations: [{from: human, value: 帮我写一个Python函数计算斐波那契数列}, {from: gpt, value: def fib(n):\n a, b 0, 1\n for _ in range(n):\n a, b b, ab\n return a}]}我建议用多轮格式因为 DeepSeek 的 chat 模板对 conversations 字段处理得更自然。文件存成my_sft_data.jsonl路径写进 config 的dataset数组里。如果你有多个文件都塞进数组SWIFT 会自动合并。注意JSONL 每行必须是合法 JSON不能有尾逗号不能跨行。写完用python -c import json;[json.loads(l) for l in open(my_sft_data.jsonl)]检查一遍能跑通不报错就说明格式没问题。4. 启动训练与权重保存4.1 用配置文件启动SWIFT 的命令行支持直接读 tomlswift sft --config ds_lora_sft.toml如果你不想用配置文件等价的命令行是swift sft \ --model_type deepseek-r1-7b-chat \ --model_id_or_path deepseek-ai/DeepSeek-R1-Distill-Qwen-7B \ --dataset /root/data/my_sft_data.jsonl \ --dataset_sample 2000 \ --split_dataset_ratio 0.05 \ --output_dir ./output/deepseek_lora \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 8 \ --learning_rate 2e-5 \ --num_train_epochs 3 \ --lora_rank 8 \ --lora_alpha 32 \ --target_modules q_proj k_proj v_proj o_proj \ --bf16 true \ --gradient_checkpointing true \ --max_length 2048启动后你会看到日志里打印数据集样本数、训练步数、显存占用。第一次跑会先下载模型权重7B 的 bf16 大概 15G 左右下载时间看你网络。下载完开始训练每 10 步打印一次 loss。4.2 权重保存位置与结构训练结束后LoRA 权重默认存在output_dir下的checkpoint-xxx文件夹里同时最后一步会额外存一份到output_dir/vx-xxx/checkpoint-xxx。你需要的其实就两个文件adapter_model.safetensors和adapter_config.json。这两个文件加起来通常几十兆因为 LoRA 只存了低秩矩阵。如果你想合并成完整权重方便部署用 export 命令swift export \ --adapters ./output/deepseek_lora/v0-xxx/checkpoint-xxx \ --merge_lora true \ --output_dir ./merged_model合并后的merged_model就是一个标准的 HuggingFace 模型目录可以直接用 transformers 加载。4.3 训练过程观察我实测下来2000 条样本、batch size 等效 16、3 个 epoch在 4090 上大概跑 40 分钟左右。loss 从 1.8 左右降到 0.9 附近eval loss 没有明显反弹说明没有严重过拟合。如果你的 loss 一直不降先检查数据格式是不是被正确解析了——SWIFT 会在日志里打印第一条样本的 tokenize 结果你看一眼 input_ids 长度是不是合理。5. 推理验证确认微调真的生效训练完不验证等于白跑。SWIFT 自带 infer 命令可以直接加载 LoRA 权重做生成。5.1 用 swift infer 验证swift infer \ --adapters ./output/deepseek_lora/v0-xxx/checkpoint-xxx \ --model_type deepseek-r1-7b-chat \ --model_id_or_path deepseek-ai/DeepSeek-R1-Distill-Qwen-7B \ --max_new_tokens 256 \ --temperature 0.7进入交互模式后输入你训练数据里类似的问题看输出风格有没有变化。比如你训练的是电商客服数据就问「退货要多久」如果回答里出现了你数据里的那种话术说明 LoRA 生效了。5.2 用 TaoToken 做基座对比想更客观地看差异可以把同一个问题分别发给微调后的模型和基座模型。基座模型如果本地没部署可以走 TaoToken 的模型对话接口。在模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 里选一个 DeepSeek 系列模型输入同样的问题把两个回答并排看。微调后的模型应该更贴近你的业务话术基座模型则更通用。如果你要写脚本批量对比用 API 方式import requests url https://taotoken.net/api/v1/chat/completions headers { Authorization: Bearer YOUR_API_KEY, Content-Type: application/json } data { model: deepseek-chat, messages: [{role: user, content: 退货要多久}], temperature: 0.7 } resp requests.post(url, headersheaders, jsondata) print(resp.json()[choices][0][message][content])API Key 在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建。注意 API 端点后面不加 UTM直接写https://taotoken.net/api/v1/chat/completions。5.3 成功结果长什么样一次成功的验证应该满足三点第一模型能正常加载不报错第二输入训练领域内的问题输出符合预期话术第三输入领域外的问题模型不会胡言乱语说明没有灾难性遗忘。如果第三点不满足说明学习率太高或者 epoch 太多把 LoRA 权重覆盖太狠了降低学习率重跑。6. 本篇常见错排查6.1 报错KeyError: deepseek-r1-7b-chat这是 model_type 没对上 SWIFT 的注册表。SWIFT 每个版本支持的模型列表会变你先跑swift sft --help看有没有--model_type的说明或者直接不写 model_type只写model_id_or_path让 SWIFT 自己推断。如果还不行去 SWIFT 的 GitHub 看swift/llm/model/constant.py里的 MODEL_MAPPING找到 DeepSeek 对应的 key。6.2 显存 OOM按这个顺序降先把per_device_train_batch_size降到 1再把gradient_accumulation_steps提到 16然后开load_in_4bit true最后开gradient_checkpointing true。四个都开了还 OOM说明 7B 模型对你显卡确实太大换 1.5B 的蒸馏版先跑通流程。6.3 数据集加载后样本数为 0SWIFT 对字段名有要求。单轮格式必须同时有instruction、input、output三个字段缺一个就可能被过滤掉。多轮格式必须是conversations字段里面每个元素有from和value。检查你的 JSONL 是不是用了question、answer这种自定义字段名改成标准字段名即可。6.4 训练 loss 为 nan通常是 bf16 和某些显卡不兼容或者学习率太高。先把bf16关掉换fp16 true如果还 nan把学习率降到 1e-5。另外检查数据里有没有空字符串的 output空标签会导致 loss 计算异常。6.5 推理时输出重复或截断max_new_tokens设太小会截断设太大可能重复。先设 256 试如果重复把temperature从 0.7 降到 0.3或者加repetition_penalty 1.1。SWIFT 的 infer 命令支持这些参数直接加在命令行后面。7. 下一步把训练接进你的工作流跑通一次最小训练之后你可以做三件事让它真正有用。第一把数据量从 2000 提到 1 万以上LoRA 的效果会明显更稳但注意 eval loss过拟合就早停。第二把target_modules扩展到gate_proj、up_proj、down_proj覆盖 MLP 层效果通常比只调注意力层好代价是显存多占一点。第三训练完的 LoRA 权重可以用swift export合并然后用 vLLM 或 transformers 部署成服务对外提供接口。如果你后面要做更复杂的编码任务或者 Agent 场景需要模型长时间稳定调用可以了解一下 Coding Plan 相关的接入方式https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它适合那种需要持续对话、多轮工具调用的场景和单次微调是互补的。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的接口说明和参数列表遇到请求格式问题可以先翻这里。最后提醒一句微调不是万能药。如果你的任务只是让模型输出格式更规范用 prompt 工程可能就够了只有当你有大量领域数据、且通用模型确实答不好时微调才值得投入。先跑通这篇的流程再判断要不要上生产。