1. 从零跑通 swift 微调 Qwen-7B 再到 ollama 本地推理的完整链路
如果你手里有一批自己的问答数据,想让 Qwen-7B 学会你的说话风格或者业务知识,最后还想在本地用 ollama 直接ollama run跑起来,那这篇就是给你写的。swift 是魔搭社区开源的一套大模型微调框架,能让你用几行命令完成 LoRA 微调;ollama 则是本地推理最省心的工具之一。把这两件事串起来,中间还差一个模型格式转换的环节,很多人就是卡在 GGUF 转换和 Modelfile 上。
我自己走这条路的时候,踩过的坑主要集中在三块:数据集格式写错导致 loss 不降、LoRA 合并后忘了导出完整权重、以及 ollama 的 Modelfile 模板和 Qwen 的 chat 格式对不上导致输出乱码。下面按顺序把每一步都拆开,命令可以直接复制,参数我会解释清楚为什么这么设。
适合谁看:有 Linux 环境、至少一张 16G 以上显存的卡、会基本的命令行操作,想给自己的 Qwen-7B 喂自定义数据并落地到 ollama 的人。如果你还没装 CUDA 和 PyTorch,建议先把基础环境弄好,本文默认你已经能跑通nvidia-smi。
整条链路是:准备自定义数据集 → swift LoRA 微调 → swift infer 验证 → swift export 合并 LoRA → 转 GGUF → 量化 → 写 Modelfile → ollama create → ollama run。每一步都有可验证的结果,不会让你走到最后才发现前面错了。
2. TaoToken 前置准备:给微调后的模型一个稳定的 API 出口
微调完的模型除了本地 ollama 跑,很多时候你还想把它接到自己的应用里,或者和云端更强的模型做对比测试。这时候一个统一的 API 入口就很省事。TaoToken 提供的就是这样一个入口,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加 UTM 参数。
为什么在微调流程里要提这个?因为你在验证阶段经常需要「微调前 vs 微调后」的对比。本地 ollama 跑微调后的模型,云端 API 跑原始 Qwen-7B,两边同样的 prompt 一对比,微调到底有没有生效一目了然。TaoToken 的模型对话页面可以直接做这种对比测试,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。
如果你后面要把微调模型接进 Claude Code 或者做长期 coding agent,可以看 coding plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。需要生成和管理 Key 的话在 console:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
这里要强调一点:TaoToken 是合规的 API 聚合入口,不是让你去搞什么灰色中转。你拿到的 Key 就是正常调用模型的凭证,Base URL 填https://taotoken.net/api,Model ID 按文档里列出的填。三件套(Base URL + Key + Model ID)缺一不可,后面在验证环节我会给出具体的调用示例。
对于本文的主线——swift 微调 + ollama 本地推理——TaoToken 主要用在两个地方:一是微调前的 baseline 对比,二是微调后如果你不想每次都本地加载模型,可以用 API 方式快速验证效果。本地 ollama 适合离线、隐私敏感的场景,API 适合快速迭代和对比。
3. 可复制配置:swift 微调 Qwen-7B 与数据集格式
先说数据集。swift 支持本地 csv 文件,格式就三列:instruction、input、output。instruction 是你的问题,input 是问题的背景补充(没有就留空),output 是期望的答案。我建议你至少准备 500 条以上,太少的话模型学不到东西,loss 会一直飘。
一个最小可用的 csv 示例:
instruction,input,output 贝贝是谁,,贝贝是我养的一只橘猫,今年三岁,最喜欢在键盘上睡觉。 介绍一下我们的产品,,我们的产品是一款面向开发者的 API 聚合工具,支持多种大模型统一调用。 怎么写一个 Python 的 hello world,,print("hello world") 就是最简单的写法,注意 Python 3 里 print 是函数,要加括号。注意 csv 里如果有逗号,字段要用双引号包起来,否则会解析错位。我建议用 pandas 生成而不是手写,避免编码问题,统一存成 UTF-8。
然后是微调命令。多卡训练一定要加前面的环境变量,否则默认只用一张卡,慢到怀疑人生:
export MKL_THREADING_LAYER=GNU \ CUDA_VISIBLE_DEVICES=0,1,2,3 \ NPROC_PER_NODE=4 \ swift sft \ --model_type qwen2-7b-instruct \ --model_id_or_path /root/.cache/modelscope/hub/qwen/Qwen2-7B-Instruct \ --sft_type lora \ --dtype AUTO \ --dataset /root/data/my_dataset.csv \ --self_cognition_sample 3000 \ --model_name 我的助手 \ --model_author 我的团队 \ --num_train_epochs 1 \ --lora_rank 8 \ --lora_alpha 32 \ --lora_dropout_p 0.05 \ --lora_target_modules ALL \ --gradient_checkpointing true \ --batch_size 1 \ --weight_decay 0.1 \ --learning_rate 1e-4 \ --gradient_accumulation_steps 16 \ --output_dir output几个关键参数解释一下。--dataset指向你的本地 csv 路径,这是自定义数据集的核心。--lora_rank 8和--lora_alpha 32是 LoRA 的秩和缩放系数,rank 越大能学的信息越多但显存占用也越高,8 是个稳妥的起点。--lora_target_modules ALL表示对所有线性层加 LoRA,效果通常比只加 qkv 好。--gradient_accumulation_steps 16配合batch_size 1,等效 batch 是 16,显存不够时的标准做法。
训练过程中你会看到 loss 逐步下降,正常情况从 2.x 降到 1.x 甚至更低。如果 loss 一直不降,先检查数据集格式,再看 learning_rate 是不是太大。
训练完成后,输出目录在output/qwen2-7b-instruct/vX-日期-时间/checkpoint-XX,记下这个路径,后面推理和合并都要用。
4. 验证请求与成功结果:推理、合并、转 GGUF、ollama 跑起来
第一步,用 swift infer 验证微调是否生效:
CUDA_VISIBLE_DEVICES=0,1,2,3 swift infer \ --ckpt_dir output/qwen2-7b-instruct/v2-20240826-101129/checkpoint-48进入交互后问它一个你数据集里出现过的问题,比如「贝贝是谁」。如果它能答出你训练数据里的答案,说明微调生效了。这一步很关键,别跳过,否则后面转换完发现效果不对,你都不知道是训练的问题还是转换的问题。
第二步,合并 LoRA 到基础模型:
CUDA_VISIBLE_DEVICES=0,1,2,3 swift export \ --ckpt_dir output/qwen2-7b-instruct/v2-20240826-101129/checkpoint-48 \ --merge_lora true执行后会生成checkpoint-48-merged目录,里面是完整的模型权重。LoRA 本身只是低秩矩阵,必须合并后才能转 GGUF,这一步不能省。
第三步,安装 ollama 并准备转换工具:
curl -fsSL https://ollama.com/install.sh | sh ollama serve然后拉取 ollama 源码,注意要指定版本,新版可能缺少 llama.cpp 子模块:
git clone -b v0.3.14 https://github.com/ollama/ollama.git cd ollama git submodule init git submodule update llm/llama.cpp编译量化工具:
python -m venv llm/llama.cpp/.venv source llm/llama.cpp/.venv/bin/activate pip install -r llm/llama.cpp/requirements.txt cd llm/llama.cpp/ make如果报错,先装apt install cmake ccache。编译成功后目录下会有convert_hf_to_gguf.py和llama-quantize等工具。
第四步,转换模型为 GGUF:
python ../ollama/llm/llama.cpp/convert_hf_to_gguf.py \ output/qwen2-7b-instruct/v2-20240826-101129/checkpoint-48-merged \ --outtype f16 \ --outfile converted.bin--outtype f16是不损失精度,转换后大概 14G 左右。
第五步,量化到 4bit 减小体积:
../ollama/llm/llama.cpp/llama-quantize converted.bin quantized.bin q4_0量化后大概 4G 左右,q4_0 是常用的 4bit 量化方案,速度和精度平衡得不错。
第六步,写 Modelfile:
FROM quantized.bin PARAMETER temperature 0.7 PARAMETER top_p 0.8 PARAMETER repeat_penalty 1.05 TEMPLATE """{{ if .System }}<|im_start|>system {{ .System }}<|im_end|> {{ end }}{{ if .Prompt }}<|im_start|>user {{ .Prompt }}<|im_end|> {{ end }}<|im_start|>assistant {{ .Response }}<|im_end|> """ SYSTEM """You are a helpful assistant."""第一行FROM quantized.bin必须和你的量化文件名一致,这是最容易出错的地方。TEMPLATE 用的是 Qwen 的 chat 格式,<|im_start|>和<|im_end|>不能写错,否则输出会乱。
第七步,创建并运行:
ollama create test2 -f Modelfile ollama list ollama run test2ollama list能看到 test2 就说明创建成功,ollama run test2进去问同样的问题,对比微调前的输出,如果答案更贴近你的数据集,整条链路就通了。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
微调这条链路的报错和 API 调用不太一样,但如果你在验证阶段用 TaoToken 做对比,可能会遇到下面这些。
401 Unauthorized:这个基本是 Key 的问题。检查你的 API Key 有没有复制完整,有没有多余空格。TaoToken 的 Key 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 生成,Base URL 填https://taotoken.net/api,注意不要多加斜杠或者路径。如果还是 401,去 console 确认 Key 的状态是否正常。
local proxy failed:这个报错通常出现在你本地配了代理但代理没起来,或者环境变量HTTP_PROXY/HTTPS_PROXY指向了一个不可用的地址。解决办法是先unset HTTP_PROXY HTTPS_PROXY再重试。注意这里说的是清理环境变量,不是让你去搞什么网络工具,纯粹是本地环境配置问题。
reading choices 报错:这个一般出现在你调用 API 后解析返回结果时,返回体里没有choices字段。原因可能是请求格式不对,比如 model 名字写错、messages 格式不对。检查你的请求体,确保model字段和文档里的一致,messages是标准的 role/content 结构。
OAuth 相关报错:如果你在用 Claude Code 或者类似的工具接入,可能会遇到 OAuth 认证失败。这时候检查你的配置文件,Claude Code 的 settings 里 Base URL 和 Key 要配对。如果是 Codex 的 auth.json,格式是这样的:
{ "base_url": "https://taotoken.net/api", "api_key": "你的Key", "model": "文档里列出的Model ID" }三件套 Base URL、Key、Model ID 必须同时正确,缺一个都会报错。Cline 的 MCP 配置也是同理,在 settings 里把这三项填对。
另外微调本身常见的坑:数据集 csv 编码不是 UTF-8 导致读进来乱码、LoRA 合并时忘了加--merge_lora true导致导出的还是适配器、GGUF 转换时模型路径写错、Modelfile 里 FROM 的文件名和实际量化文件名不一致。这些都会让最后ollama run出问题,建议每步都验证一下输出文件是否存在、大小是否合理。
6. 把微调模型接进你的工作流:API 与本地推理的配合
整条链路跑通后,你手里其实有两个东西:一个是本地 ollama 里的微调模型,一个是 TaoToken 提供的云端 API 入口。这两个不是二选一,而是配合使用。
本地 ollama 适合:数据隐私敏感、需要离线、频繁调用不想花 token 的场景。微调后的模型在本地跑,响应速度取决于你的显卡,7B 的 q4 量化在消费级卡上基本能到可用水平。
TaoToken API 适合:快速对比测试、需要调用更大模型、做 agent 或者 coding 辅助的场景。你可以用 API 跑原始 Qwen-7B 作为 baseline,和本地微调模型对比,验证微调效果。模型对话入口在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
如果你要把微调模型接进 Claude Code 做长期编码辅助,可以看 coding plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。Claude Code 的 Anthropic 接入配置在 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite ,里面有具体的 Base URL 和 Key 配置方式。
最后给一个实用建议:微调数据集的质量比数量重要。我试过用 200 条精心构造的数据,效果比 2000 条随便爬的数据好得多。每条数据的 output 要准确、风格一致,instruction 要覆盖你实际会问的问题类型。训练完先用 swift infer 验证,再走转换流程,别一上来就全套跑完才发现数据有问题。