💻 代码实战:部署 · 微调 · Agent

五段可直接跑的代码 + 逐行通俗讲解:Ollama 本地部署 → transformers 生成 → LoRA 微调 → GRPO 训练循环 → function calling 最小 Agent,附显存估算表
OllamatransformersLoRA/peftGRPOFunction Calling
🎯 本页学习目标
1. 10 分钟内在本机跑起一个开源模型
2. 写出最小 LoRA 微调脚本并解释 r/alpha/target_modules
3. 亲手实现一个 25 行的 function calling Agent 循环
建议用时:40 分钟 · 环境:任意能装 Python 的电脑

1 Ollama:三条命令本地跑模型

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)。

2 transformers:10 行加载与生成

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 是「对话格式」的官方实现——你训练时用什么模板,推理就必须用同一个(格式错=性能暴跌,新人第一大坑)。

3 LoRA 微调:只训 0.5% 的参数

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「预训练定上限,后训练唤醒」完全自洽。

4 GRPO 训练循环(伪代码,对应 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 训练。

5 function calling:25 行最小 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)训练的就是把这个循环里的决策做得更聪明。

6 常见报错与排错(新人高频)

六段代码各自对应一个「必踩坑」。先看报错原文,再对号入座:

#报错现场(摘要)根因修复
1torch.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 节)
3ValueError: Unrecognized configuration class … / unexpected keyword argument 'rope_theta'transformers 版本过旧/过新,不认识新架构的参数与配置类固定版本号:看模型卡要求的最低版本,用 requirements.txt 锁死(如 transformers==4.x.y),升级前先跑回归用例
4LoRA 训练时正常,merge_and_unload 合并后行为异常合并前后 base 权重 dtype 不一致(如 FP16 底座在合并时被转成 FP32/INT8),LoRA scale 计算溢出或精度漂移合并前后打印 model.dtype 检查;base 用与训练一致的 dtype 加载再 merge;合并后用固定问句做一次回归测试再上线
5HTTPError: 429 Too Many Requests / ReadTimeout触达限流或服务过载,而代码里没有任何重试逻辑,一次失败整个流程崩指数退避重试:间隔 1s→2s→4s… 设上限并加随机抖动;批量请求配并发上限;长任务设超时+降级方案
6Agent 第二轮「失忆」、重复调用同一工具或答非所问工具执行结果忘回填到对话历史(或 role/name 字段写错),模型下一轮看不到上一步的观测每次执行完必须 msgs.append({"role":"tool",…}) 再发起下一轮(见第 5 节循环);调试时打印完整 msgs,检查角色序列 user→assistant(tool_calls)→tool 是否完整
🩺 排错通用心法:先读报错原文最后一行(不是截图给别人的部分),再定位「训练侧还是推理侧、数据侧还是版本侧」——上表 6 条覆盖了新人 80% 的现场。改一个变量做一次实验,别一次改三个参数。
0.6BFP16 1.2GINT4 0.5G 4BFP16 8GINT4 2.5G 7~8BFP16 16GINT4 4.5G 14BFP16 28GINT4 8G 红=FP16 权重,绿=INT4 权重;32B:FP16 64G vs INT4 18G —— 量化是消费级显卡的第一杠杆
图:不同量化精度与模型规模下的显存占用对比(FP16/INT8/INT4)

7 显存估算速查表

模型规模FP16 权重INT4 权重推理建议LoRA 微调建议
0.6B~1.2GB~0.5GBCPU 可跑8G 卡
4B~8GB~2.5GB8G 卡12G 卡
7~8B~16GB~4.5GB12G 卡(量化)24G 卡
14B~28GB~8GB24G 卡(量化)多卡 / QLoRA
32B~64GB~18GB48G / 双 24G(量化)QLoRA + 梯度检查点
671B(MoE)~1.3TB~350GB多节点集群不适用(用蒸馏版)

量化三句话:INT4 把权重压到 1/4(训练后量化 AWQ/GGUF,或训练中 QLoRA);显存公式:权重 ≈ 参数量×每参数字节,KV Cache 另算(07-01)。MoE 记得显存看「总参数」、速度看「激活参数」。

8 本节自测

1. LoRA 的 r 调大意味着?
💡 解析: B 正确——r 是低秩矩阵的秩=容量旋钮:r 越大旁路可承载的改动越复杂,可训练参数也越多。A 错在「显存更省」与事实相反——r 越大旁路参数越多,显存与训练时间都上涨;C 错在「学习率自动变小」——学习率是独立超参,与 r 联动的只是等效缩放 alpha/r 这个输出比值,不是学习率本身;D 错在「推理一定变快」——旁路矩阵参与前向计算,r 大反而略慢,「一定快」不成立。实践口径:r=8~32 起步,数据多再加,小数据调大只会过拟合(见第 3 节)。
2. 推理时对话格式用错模板,后果是?
💡 解析: A 正确——模型在训练时按特定 chat template 学会对话结构,推理时换了模板等于把输入换到一个陌生分布上,性能显著下降甚至胡言乱语(见第 6 节排错表第 2 条)。B 错在「没有影响」完全错误——模板决定 special token、角色标记与换行格式,这是模型见过的「句子结构」的一部分;C 错在「只影响速度」——模板错误影响的是生成质量,不是推理延迟;D 错在「会自动纠正」是想当然:模型不会自己补上缺失的特殊 token 或改写角色标记,唯一正解是统一走 apply_chat_template。
3. GRPO 组内基线替代了什么组件?
💡 解析: C 正确——GRPO 的核心工程贡献就是用「同题组内均值」当基线,把价值网络(critic)整个免掉,显存省一半、调参少一堆(理论见 07-03 第 5 节公式③,代码见本页第 4 节第③行)。A 错在「策略网络」是被优化的主体,任何 RL 都不能没有它;B 错在「奖励模型」仍需要——可验证奖励或 RM 打分提供信号,组内比较才有数值可比;D 错在「分词器」属于模型本体组件,训练循环根本不碰它,与基线估计毫无关系。
4. Agent 循环里「工具结果回填」的作用?
💡 解析: B 正确——工具结果回填把真实世界的执行结果放回上下文,模型下一轮基于观测继续决策,这才构成「感知-决策-行动」闭环;不回填,模型只能凭空想象工具返回了什么(排错表第 6 条就是忘回填的现场)。A 错在「仅记录日志」——回填内容是对话历史的正式组成部分,直接参与下一轮注意力计算,不只是留档;C 错在「触发训练」——Agent 运行循环与训练循环是两回事,推理期间权重不更新;D 错在「压缩 KV Cache」没有此机制,回填反而让上下文变长、KV Cache 更大。
5. 8G 显存想跑 7B 模型,最合理的做法?
💡 解析: A 正确——8G 卡上 INT4/GGUF 量化版权重约 4.5G,留足 KV Cache 与激活空间,Ollama 默认即此方案,7B 可流畅推理。B 错在「FP16 直接硬塞」——FP16 的 7B 权重约 16G(见第 7 节速查表),8G 卡必然 OOM,这是排错表第 1 条的标准现场;C 错在「换 671B」更是天方夜谭——全量 FP16 要约 1.3TB,差三个数量级;D 错在「不可能」与事实相反——量化正是「消费级显卡跑大模型的第一杠杆」,本页显存条形图就是证据。
6. QLoRA 与 LoRA 的区别?
💡 解析: C 正确——QLoRA 把被微调的底座量化到 4bit 冻结,再在其上挂 BF16 的 LoRA 旁路训练:显存大降、单卡可微调 32B,代价是底座 4bit 带来的少量精度损失(原论文与 LoRA 的区别,见参考来源)。A 错在「不需要数据」——微调的本质没变,照样要指令-回答训练集;B 错在「训练更快且效果一定更好」双重不实:反量化计算并不比纯 LoRA 快,效果也因量化损失「通常略差」而非更好;D 错在「只能微调 MoE」——QLoRA 适用于任意底座,MoE 反而因显存大头在专家权重而另有讲究。
📌 本节小结:Ollama 一条命令入门、transformers 十行生成、LoRA 三个超参(r/alpha/target_modules)、GRPO 五行核心、Agent 循环二十五行——大模型工程入门的完整最小闭环。
🤔 思考题:1. 把 Agent 的工具换成你关节驱动器的「读温度/设扭矩/急停」,最小 Agent 循环要加什么安全护栏?2. LoRA 微调后模型「只会新任务、忘了旧任务」怎么办?

9 参考来源(更新至 2026-08)