1. 微调 Qwen3-4b 时,Key 和配置为什么总在打架
如果你正在用 Swift(ms-swift)微调 Qwen3-4b,大概率会遇到这样一个尴尬局面:训练脚本、推理脚本、评测脚本、WebUI 启动脚本各写各的,模型路径散落在四五个文件里,而一旦接入外部大模型 API 做数据清洗、答案蒸馏或者自动评测,API Key 又开始到处复制粘贴。今天在train.sh里写一个 Key,明天在eval.py里再写一个,后天同事拉走代码发现跑不起来,因为 Key 没跟着走。
这个问题的本质不是 Swift 不好用,而是「训练框架」和「模型服务接入」这两件事被混在了一起。Swift 负责的是 Qwen3-4b 的 LoRA/QLoRA 微调、推理、合并、量化,它本身不关心你从哪里调用外部模型;而数据构造、自动打分、多模型对比这些环节又确实需要调用大模型 API。于是 Key 分散、base_url 不统一、模型名写错、超时参数各写一套,就成了工程化落地时最烦人的部分。
我这次的做法是把「模型服务接入」抽出来,统一走 TaoToken 的 Key,再用一份config.toml把训练、推理、评测、外部调用四类配置收口。这样 Swift 侧只关心--model、--dataset、--output_dir这些训练参数,而所有对外请求的地址、Key、模型名、超时、重试都从同一个配置文件读取。下面按「先接 Key,再写 config,再跑训练,再验证」的顺序走一遍,你可以直接照着改。
2. TaoToken 统一 Key 接入:把分散的凭证收成一处
TaoToken 在这里扮演的角色是「统一的大模型 API 入口」。你不需要在每台机器、每个脚本里维护不同的 Key,而是拿一个 Key,通过统一的 base_url 去调用不同模型。对 Swift 微调场景来说,最典型的用途有三个:一是用外部模型批量生成或清洗 SFT 数据;二是训练完成后做自动评测打分;三是把微调后的 Qwen3-4b 和基线模型放在一起对比。
第一步是拿到 Key。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建时建议按用途命名,比如swift-data-clean、swift-eval,方便后面排查是哪个环节在消耗额度。
拿到 Key 之后,不要直接写进训练脚本。正确做法是写进环境变量或本地配置文件,然后让config.toml去引用。API 的基础地址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为 OpenAI 兼容的 base_url 使用即可。如果你用的是 OpenAI SDK 或兼容 OpenAI 协议的客户端,把base_url指向它,api_key填你的 Key,就能调用。
这里有个容易踩的坑:很多人把 Key 写进train.sh后提交到 Git,结果 Key 泄露。我的建议是本地用.env或系统环境变量,config.toml里只写${TAOTOKEN_API_KEY}这样的占位符,运行时再注入。这样代码可以安全地分享给同事,Key 留在各自机器上。
3. 可复制的 config.toml 骨架与 Swift 训练配置
下面这份config.toml是我实际在用的骨架,分成[api]、[train]、[infer]、[eval]四段。你可以直接复制,把模型路径和数据集路径改成自己的。
# config.toml [api] base_url = "https://taotoken.net/api" api_key = "${TAOTOKEN_API_KEY}" default_model = "qwen3-4b" timeout = 120 max_retries = 3 [train] model = "Qwen/Qwen3-4b" train_type = "lora" dataset = "dataset/dataset-train.json" torch_dtype = "bfloat16" num_train_epochs = 3 per_device_train_batch_size = 1 per_device_eval_batch_size = 1 learning_rate = 1e-4 lora_rank = 8 lora_alpha = 32 target_modules = "all-linear" gradient_accumulation_steps = 16 eval_steps = 50 save_steps = 50 save_total_limit = 2 logging_steps = 5 max_length = 2048 output_dir = "output" warmup_ratio = 0.05 dataloader_num_workers = 4 model_author = "swift" model_name = "swift-robot" [infer] adapters = "output/vx-xxx/checkpoint-xxx" stream = true temperature = 0 max_new_tokens = 2048 infer_backend = "pt" [eval] judge_model = "qwen3-4b" judge_base_url = "https://taotoken.net/api" judge_api_key = "${TAOTOKEN_API_KEY}"这份配置的关键点是:[api]和[eval]里的base_url都指向 TaoToken,Key 用环境变量占位;[train]里的参数和 Swift 命令行一一对应,方便脚本读取。接下来把 Swift 训练命令和这份配置对齐。
安装 ms-swift 很简单:
pip install ms-swift -U然后基于 Qwen3-4b 做 LoRA 微调。下面这条命令在 Windows 下用^换行,Linux/macOS 换成\:
set CUDA_VISIBLE_DEVICES=0 swift sft ^ --model Qwen/Qwen3-4b ^ --train_type lora ^ --dataset "dataset/dataset-train.json" ^ --torch_dtype bfloat16 ^ --num_train_epochs 3 ^ --per_device_train_batch_size 1 ^ --per_device_eval_batch_size 1 ^ --learning_rate 1e-4 ^ --lora_rank 8 ^ --lora_alpha 32 ^ --target_modules all-linear ^ --gradient_accumulation_steps 16 ^ --eval_steps 50 ^ --save_steps 50 ^ --save_total_limit 2 ^ --logging_steps 5 ^ --max_length 2048 ^ --output_dir output ^ --warmup_ratio 0.05 ^ --dataloader_num_workers 4 ^ --model_author swift ^ --model_name swift-robot--model_author和--model_name只有在数据集包含swift/self-cognition时才生效,普通自定义数据集可以不加。默认从 ModelScope 下载模型和数据集,如果你想用 HuggingFace,加--use_hf true。
自定义数据集的格式是 JSONL,每行一个样本,结构如下:
{"messages": [{"role": "system", "content": "<system>"}, {"role": "user", "content": "<query1>"}, {"role": "assistant", "content": "<response1>"}]} {"messages": [{"role": "system", "content": "<system>"}, {"role": "user", "content": "<query1>"}, {"role": "assistant", "content": "<response1>"}]}如果你要用 TaoToken 上的模型来批量生成这些response,可以写一个 Python 脚本读取config.toml,用 OpenAI SDK 调用。这样数据构造和训练就通过同一份配置串起来了。
4. 验证请求:微调前后各跑一次,确认链路通
配置写完,先别急着开训。第一步是验证 TaoToken 的 Key 能不能正常调用。用 curl 或 Python 都行,Python 更贴近后面脚本的写法:
import os from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key=os.environ["TAOTOKEN_API_KEY"], ) resp = client.chat.completions.create( model="qwen3-4b", messages=[{"role": "user", "content": "用一句话说明什么是 LoRA 微调"}], temperature=0, ) print(resp.choices[0].message.content)如果返回正常文本,说明 Key 和 base_url 没问题。这一步失败的话,先检查环境变量是否真的注入,再检查 Key 是否复制完整。
第二步是验证 Swift 训练后的推理。训练完成后,output目录下会有类似vx-xxx/checkpoint-xxx的文件夹。用交互式命令行推理:
CUDA_VISIBLE_DEVICES=0 swift infer \ --adapters output/vx-xxx/checkpoint-xxx \ --stream true \ --temperature 0 \ --max_new_tokens 2048因为 adapters 文件夹里包含args.json,Swift 会自动读取模型和系统参数,不需要再指定--model。如果你想显式指定基础模型,可以这样:
CUDA_VISIBLE_DEVICES=0 swift infer \ --model Qwen/Qwen3-4b \ --adapters output/vx-xxx/checkpoint-xxx \ --stream true \ --infer_backend pt \ --temperature 0 \ --max_new_tokens 2048想用 vLLM 加速并合并 LoRA:
CUDA_VISIBLE_DEVICES=0 swift infer \ --adapters output/vx-xxx/checkpoint-xxx \ --stream true \ --merge_lora true \ --infer_backend vllm \ --max_model_len 8192 \ --temperature 0 \ --max_new_tokens 2048第三步是合并模型并做最终验证。合并命令:
CUDA_VISIBLE_DEVICES=0 swift export \ --model Qwen/Qwen3-4b \ --adapters output/v18-20250512-205806/checkpoint-123 \ --max_length 2048 \ --merge_lora true合并后用合并后的权重再推理一次,输入数据集里的问题,看回答是否符合预期:
CUDA_VISIBLE_DEVICES=0 swift infer \ --model output/v18-20250512-205806/checkpoint-123-merged \ --stream true \ --temperature 0 \ --max_new_tokens 2048如果你更喜欢 WebUI,可以启动:
CUDA_VISIBLE_DEVICES=0 swift app \ --model Qwen/Qwen3-4b \ --adapters output/qwen3-4b-lora \ --stream true \ --infer_backend pt \ --max_length 2048 \ --lang zh微调前后的对比验证,建议固定同一批测试问题,分别用基座模型和合并后的模型跑一遍,把输出并排看。这一步用 TaoToken 的模型做自动打分也可以,把两组回答和参考答案一起发给评测模型,让它按维度打分。
5. 本篇常见错排查
报错一:openai.AuthenticationError或 401。九成是 Key 没注入或复制时带了空格。先echo $TAOTOKEN_API_KEY确认环境变量存在,再检查config.toml里是不是写成了字面量${TAOTOKEN_API_KEY}而没有做替换。如果你用的是 Python 读取 toml,记得手动做环境变量展开。
报错二:--adapters路径找不到。Swift 的 checkpoint 目录名带时间戳,每次训练都不一样。不要硬编码,用ls output看一下实际目录名。另外--adapters指向的是 checkpoint 文件夹本身,不是它的父目录。
报错三:推理时显存不够。Qwen3-4b 用 bfloat16 加载大约需要 8GB 以上显存,加上 KV Cache 和 vLLM 的预分配会更高。如果显存紧张,把--infer_backend从vllm换回pt,或者降低--max_model_len。训练侧则可以把--per_device_train_batch_size保持 1,靠--gradient_accumulation_steps堆等效 batch。
报错四:数据集格式不对导致训练启动即报错。自定义数据集必须是 JSONL,每行一个完整 JSON,messages里的 role 只能是 system/user/assistant。常见错误是用了 JSON 数组而不是 JSONL,或者一行里塞了多个样本。用head -n 1 dataset/dataset-train.json | python -m json.tool验证第一行能否解析。
报错五:TaoToken 调用超时。默认超时 120 秒,长文本生成可能不够。在config.toml的[api]段把timeout调大,同时确认max_retries至少为 2,避免偶发网络抖动直接失败。批量数据清洗时建议加并发控制,不要一次性打太多请求。
报错六:合并后的模型回答异常。先确认--merge_lora true是否真的执行成功,再检查合并时用的 base model 和训练时是否一致。如果训练用了--model Qwen/Qwen3-4b,合并时也要用同一个。合并后建议先用swift infer跑一条简单问题,确认模型能正常加载再批量评测。
6. 把 Key 和配置收口之后,Swift 微调才真正可复现
走到这里,你应该已经有一套能跑通的链路:TaoToken 提供统一 Key 和 base_url,config.toml把 API、训练、推理、评测四类参数收在一处,Swift 负责 Qwen3-4b 的 LoRA 微调、推理、合并和 WebUI。下次换模型,只改[train]里的model;下次换数据集,只改dataset;下次换评测模型,只改[eval]里的judge_model。Key 不再散落,配置不再打架。
如果你在接入阶段卡住,优先看 API Keys 管理和接入文档:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 和 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。想先验证模型对话效果,可以直接用模型对话页:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果你后面要把微调、评测、Agent 调用做成长期流程,Coding Plan 更适合统一管理:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。ClaudeCodeAnthropic 相关接入参考:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。