chapter 13 / capstone · 全课程收官实战 · 预计实操时间 1-2 个周末
这一章没有新理论——它把前面 13 章的知识拧成一个可以在你自己的 Mac 上跑通的端到端项目:用 LoRA+SFT 让一个 4B-9B 的开源小模型,稳定输出结构化的像素画 JSON。每一步标注了它回收的章节。做完它,你就完成了从「学过」到「做过」的跨越。
回收第 11 章任务设计学的第一条:收窄任务面。不做「AI 画图」,做「生成符合此 schema 的 JSON」:
schema · pixel_art_v1{
"size": 8 | 16 | 32, // 画布边长
"palette": ["#RRGGBB", ...], // 2-16 个十六进制颜色
"pixels": [[int, ...], ...] // size×size 矩阵,值为 palette 索引
}
这个表示选择本身就是项目一半的成功(第 8 章论证过 vs 扩散模型):每条规则都可程序化判定——JSON 可解析、字段齐全、hex 合法、矩阵矩形、索引不越界。于是同一个 validate() 函数通吃三件事:评测打分、训练数据过滤、(进阶)RLVR 奖励(第 8 章监督信号进化轴的终点站)。可验证性不是运气,是设计出来的。
下面就是这个校验器的网页版。必做实验:① 依次点三个错误样例——调色板越界(模型「想用」没声明的颜色)、行长不齐(长序列数错数,token 化的诅咒,第 7 章)、非法色值(把自然语言色名当 hex)——这三类就是真实训练前小模型最常犯的错;② 直接在文本框里改 JSON,看校验逐条变红变绿、画布实时重绘(非法格子亮红色)。
左边可以随意编辑 · 右边即时渲染 + 逐条校验 · 注意 PASS/FAIL 同时驱动评测/清洗/奖励三件事
第 8 章方法论第一条:训练之前先有评测。12 个分层用例(4 易:单色块;4 中:指定物体+配色;4 难:风格约束+尺寸 32),每例跑 validate() + 任务断言:
import json, re, sys
from mlx_lm import load, generate
HEX = re.compile(r'^#[0-9a-fA-F]{6}$')
def validate(text: str) -> tuple[bool, str]:
try: d = json.loads(extract_json(text)) # 模型可能裹 markdown,先抽取
except Exception as e: return False, f'parse: {e}'
if not all(k in d for k in ('size','palette','pixels')): return False, 'missing fields'
if not (2 <= len(d['palette']) <= 16 and all(HEX.match(c) for c in d['palette'])):
return False, 'bad palette'
px, n = d['pixels'], d['size']
if len(px) != n or any(len(r) != n for r in px): return False, 'not rectangular'
if any(not (0 <= v < len(d['palette'])) for r in px for v in r): return False, 'index OOB'
return True, 'ok'
model, tok = load(sys.argv[1]) # 基座或 adapter 路径
cases = [json.loads(l) for l in open('eval_cases.jsonl')]
passed = 0
for c in cases:
out = generate(model, tok, prompt=render_chat(c['instruction']), max_tokens=2048)
ok, why = validate(out)
print(('✓' if ok else '✗'), c['id'], why)
passed += ok
print(f'{passed}/{len(cases)} pass')
跑基座模型(如 Qwen3-4B-Instruct)的预期结果:0/12 到 2/12——它会输出大致像样但行长不齐、索引越界的 JSON(实验室里的错误②③)。这个数字就是一切后续工作的参照系。
目标 3000-5000 条「指令 → 标准 JSON」(MLX 社区经验的甜点区间)。管线(第 7 章 FineWeb 思想的微缩版):
格式为 chat JSONL(第 8 章 template+loss mask),13M token 量级。
候选:4B dense / 8B dense / 同级混合架构(线性注意力系)/ 小 MoE。直接给第 7 章讲过的真实结论与方法:
# —— 阶段A:LoRA 管线验证(约 20 分钟,第 8 章计算器算过显存账) ——
python -m mlx_lm lora --model Qwen/Qwen3-4B-Instruct \
--train --data ./data --iters 600 --batch-size 4 \
--num-layers 16 --learning-rate 1e-5 # 600步≈3 epoch(小子集)
python eval.py ./adapters # 期望:0/12 → 6-9/12,loss 平滑下降
# 这一步回答的问题是「管线对不对」,不是「质量好不好」
# —— 阶段B:全量数据过夜跑(约 13M token,8B 每 epoch 3-4 小时 / 4B 减半) ——
python -m mlx_lm lora --model Qwen/Qwen3-8B-Instruct \
--train --data ./data_full --iters 2400 --batch-size 4 \
--num-layers 32 --learning-rate 1e-5 \
--steps-per-eval 200 --val-batches 25 # 睡前启动,醒来看 val loss 曲线
节奏就是第 8 章说的:白天建数据和评测,晚上跑训练,早上看分数。值得盯的三个信号:val loss 跟 train loss 同降(不过拟合,第 1 章)、生成样例的行长错误率(最顽固的错误类型)、以及 loss 降但评测不动的情况(说明错不在拟合在数据——进下一步)。
假设过夜后 9/12。错的 3 个不是噪声,是下一轮数据工程的工单(第 8 章对照实验纪律):
| 失败案例 | 诊断 | 对策 |
|---|---|---|
| 32×32 大图行长出错 | 长序列计数退化(数据里 32 尺寸样本只占 8%) | 定向补 500 条 32×32 样本 |
| 「复古配色」风格漂移 | 风格词→调色板的映射数据稀疏 | 程序化生成时把风格词显式枚举进指令模板 |
| 偶发 markdown 包裹 JSON | SFT 数据全是裸 JSON,但 system prompt 没禁止 | 数据侧统一+system 明确「只输出 JSON」双保险 |
两到三轮「评测→归因→补数据→重训」后稳定在 11-12/12,这套循环本身(数据集+评测+管线)就是你沉淀下来的资产——模型只是它的当前输出(第 7/8 章的判断在此闭环)。
mlx_lm fuse 合并 adapter → 4bit 量化(第 9 章:4bit 是悬崖前最后一站,对格式化输出任务几乎无损)→ 本地 OpenAI 兼容服务(mlx_lm server)。8B-int4 ≈ 4.5GB,一台 Mac mini 即可服务。generate_pixel_art(prompt) → validate() → 失败则带错误信息重试一次 → 仍失败升级旗舰模型。校验失败率就是线上监控指标,分层兜底保住尾部质量。