ollama pull deepseek-r1:7b # 拉取 R1 蒸馏版(约 4.7GB)
ollama run deepseek-r1:7b # 交互对话(自动起本地服务 11434)
curl http://localhost:11434/api/chat -d '{"model":"deepseek-r1:7b","messages":[{"role":"user","content":"用一句话解释MoE"}]}'
要点:Ollama 自动做量化(GGUF 格式)与显存调度;蒸馏版 7B 在 8G 显存即可流畅运行——「大厂能力、桌上尺寸」是蒸馏的意义(呼应 07-02)。
from transformers import AutoModelForCausalLM, AutoTokenizer
name = "Qwen/Qwen3-4B-Instruct" # 任意宽松许可模型
tok = AutoTokenizer.from_pretrained(name)
model = AutoModelForCausalLM.from_pretrained(name, device_map="auto", torch_dtype="auto")
msgs = [{"role":"user","content":"解释一下谐波减速器为什么零背隙"}]
ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt").to(model.device)
out = model.generate(ids, max_new_tokens=256)
print(tok.decode(out[0][ids.shape[1]:], skip_special_tokens=True))
要点:device_map="auto" 自动分层放显存/内存;apply_chat_template 是「对话格式」的官方实现——你训练时用什么模板,推理就必须用同一个(格式错=性能暴跌,新人第一大坑)。
from peft import LoraConfig, get_peft_model
cfg = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj","k_proj","v_proj","o_proj"], # 只往注意力层插低秩旁路
task_type="CAUSAL_LM")
model = get_peft_model(model, cfg)
model.print_trainable_parameters() # trainable params: 0.5% 左右
训练循环用 trl.SFTTrainer 包一层即可(喂「指令-回答」JSONL)。为什么 LoRA 有效:能力改动本质是低秩方向上的微调,不用重学全部知识——与 07-03「预训练定上限,后训练唤醒」完全自洽。
for problem in math_dataset:
group = [policy.sample(problem) for _ in range(G)] # ① 同题采样 G 个答案
rewards = [verify(a) for a in group] # ② 可验证奖励(对/错)
A = [(r - mean(rewards)) / (std(rewards)+1e-4) # ③ 组内基线=优势(无 critic!)
for r in rewards]
loss = -sum( min( ratio*A, clip(ratio,1-ε,1+ε)*A ) # ④ PPO 式裁剪保稳定
for ratio,a in zip(ratios(group), group) )
loss.backward(); optimizer.step() # ⑤ 无 KL(R1 口径):放开手长思考
全部「魔法」就在 ③:优势不需要价值网络,同题互相当参照。把 verify 换成你机器人的任务成功判定,这套循环就直接迁移到具身 Agent 训练。
import json, requests
TOOLS = [{"type":"function","function":{"name":"get_joint_temp",
"description":"读取机器人关节温度","parameters":{"type":"object",
"properties":{"joint":{"type":"string","enum":["hip","knee","shoulder"]}},"required":["joint"]}}}]
def read_sensor(joint): return {"hip":41.2,"knee":38.7,"shoulder":35.1}[joint] # 你的真实驱动器接口
msgs = [{"role":"user","content":"髋关节现在多少度?要不要休息?"}]
while True:
r = requests.post("http://localhost:11434/api/chat",
json={"model":"qwen3:4b","messages":msgs,"tools":TOOLS}).json()["message"]
msgs.append(r)
if not r.get("tool_calls"): print(r["content"]); break # 没有工具调用=最终回答
for call in r["tool_calls"]:
args = call["function"]["arguments"]
result = read_sensor(**args) # 执行真实动作
msgs.append({"role":"tool","content":json.dumps(result),
"name":call["function"]["name"]}) # 结果回填继续对话
这就是 Agent 的最小骨架:模型决定「调什么工具」→ 你执行真实世界动作 → 结果回填 → 模型再决策。把这个 read_sensor 换成你关节驱动器的 CAN 读取,LLM 就真的在「感知你的机器人」——Agentic RL(07-03)训练的就是把这个循环里的决策做得更聪明。
六段代码各自对应一个「必踩坑」。先看报错原文,再对号入座:
| # | 报错现场(摘要) | 根因 | 修复 |
|---|---|---|---|
| 1 | torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate … | 显存不够:batch/序列太长、FP16 权重超预算、激活没有省 | 减小 batch → 开梯度累积(等效大 batch 不涨显存)→ 换 QLoRA/INT4 量化 → 缩短 max_len → 开梯度检查点(见下表估算显存) |
| 2 | 无报错,但微调后线上回答「变笨」甚至胡言乱语 | chat template 不一致:训练用手拼字符串、推理用 apply_chat_template(或反之),模型见了陌生的对话格式 | 训练与推理模板必须同源:统一走 tokenizer.apply_chat_template,并核对 special tokens 与 system 角色是否被正确编码(本页第 2 节) |
| 3 | ValueError: Unrecognized configuration class … / unexpected keyword argument 'rope_theta' | transformers 版本过旧/过新,不认识新架构的参数与配置类 | 固定版本号:看模型卡要求的最低版本,用 requirements.txt 锁死(如 transformers==4.x.y),升级前先跑回归用例 |
| 4 | LoRA 训练时正常,merge_and_unload 合并后行为异常 | 合并前后 base 权重 dtype 不一致(如 FP16 底座在合并时被转成 FP32/INT8),LoRA scale 计算溢出或精度漂移 | 合并前后打印 model.dtype 检查;base 用与训练一致的 dtype 加载再 merge;合并后用固定问句做一次回归测试再上线 |
| 5 | HTTPError: 429 Too Many Requests / ReadTimeout | 触达限流或服务过载,而代码里没有任何重试逻辑,一次失败整个流程崩 | 指数退避重试:间隔 1s→2s→4s… 设上限并加随机抖动;批量请求配并发上限;长任务设超时+降级方案 |
| 6 | Agent 第二轮「失忆」、重复调用同一工具或答非所问 | 工具执行结果忘回填到对话历史(或 role/name 字段写错),模型下一轮看不到上一步的观测 | 每次执行完必须 msgs.append({"role":"tool",…}) 再发起下一轮(见第 5 节循环);调试时打印完整 msgs,检查角色序列 user→assistant(tool_calls)→tool 是否完整 |
| 模型规模 | FP16 权重 | INT4 权重 | 推理建议 | LoRA 微调建议 |
|---|---|---|---|---|
| 0.6B | ~1.2GB | ~0.5GB | CPU 可跑 | 8G 卡 |
| 4B | ~8GB | ~2.5GB | 8G 卡 | 12G 卡 |
| 7~8B | ~16GB | ~4.5GB | 12G 卡(量化) | 24G 卡 |
| 14B | ~28GB | ~8GB | 24G 卡(量化) | 多卡 / QLoRA |
| 32B | ~64GB | ~18GB | 48G / 双 24G(量化) | QLoRA + 梯度检查点 |
| 671B(MoE) | ~1.3TB | ~350GB | 多节点集群 | 不适用(用蒸馏版) |
量化三句话:INT4 把权重压到 1/4(训练后量化 AWQ/GGUF,或训练中 QLoRA);显存公式:权重 ≈ 参数量×每参数字节,KV Cache 另算(07-01)。MoE 记得显存看「总参数」、速度看「激活参数」。