公开文章
端侧模型微调实战合集计划
以通用 Recipe JSON 生成模型为主线,讲清楚数据构建、QLoRA 微调、结构化验证、GGUF 量化和 Android 端侧部署
端侧模型微调实战合集计划
合集定位
合集名称:端侧模型微调实战
公众号定位:
这不是一套“逐行讲代码”的微调教程,也不是把训练命令堆给读者的 runbook。
这个合集要讲的是一件更具体的事:
怎么把一个垂直生成能力训练进小模型,让它在端侧稳定输出可解析、可校验、可被 App 消费的结构化结果。
公众号负责讲清楚:
- 为什么这个场景值得微调
- 为什么不是直接 prompt、RAG 或云端 API
- 数据怎么清洗,怎么变成训练样本
- 小模型训完后,如何判断它真的可用
- 从 LoRA 到 GGUF,再到 Android 本地推理,中间有哪些工程坑
- 这种方法适合哪些端侧产品,不适合哪些场景
完整代码放在 GitHub,文章只保留关键片段、关键配置和关键判断。
一句话定位:
用一个通用 Recipe JSON 生成模型,讲清楚“小模型如何学会一个垂直生成任务,并最终跑到端侧”。
主案例为什么继续用 Recipe
我们讨论过 PokéAPI、TMDB、Open Library 这些公开数据源,但它们都有一个共同问题:
它们更像事实查询,不像生成能力。
比如宝可梦图鉴,输入 pikachu 后输出图鉴 JSON,本质上很容易被读者质疑:
这件事为什么不用本地数据库查?
Recipe 的价值更强。因为用户输入不是一个固定 key,而是一段带约束的自然语言:
给我一份低脂、高蛋白、30 分钟内能完成的晚餐
模型要输出的也不是某条数据库原文,而是一份新的、结构完整的 recipe JSON:
title、description、ingredients、steps、nutrition、time、servings、tags ...
这更接近微调真正适合的任务:
- 输入有变化空间
- 输出不是死数据查询
- 结果能被程序解析
- 领域足够窄
- 结构足够稳定
- 端侧运行有意义
公开表达边界
Recipe 可以继续用,但文章里必须做隔离。
可以写:
- 通用菜谱生成
- 公开菜谱结构
- 自建样例数据
- 脱敏后的 recipe JSON
- 本地 App 消费结构化结果
- Android 端侧离线推理
- 数据清洗、样本构建、QLoRA、GGUF、JSON 校验
不要写:
- 公司名称
- 内部项目路径
- 内部接口
- 真实业务来源
- 具体设备类型
- 设备控制参数
- 任何可以联想到公司业务的字段
文章里的统一说法:
本文使用的是脱敏后的通用 Recipe JSON 示例,重点不在菜谱业务本身,而在“端侧小模型如何学习稳定的结构化生成能力”。
如果需要展示数据,全部使用通用字段和自造样例,不出现内部字段。
目标读者
- Android、后端或全栈工程师,想切入端侧 AI
- 已经会调用大模型 API,但不清楚微调到底解决什么问题的人
- 对 LoRA、QLoRA、GGUF、llama.cpp 感兴趣,但不想一上来啃论文和源码的人
- 正在做端侧 AI、本地推理、离线能力或结构化生成能力的开发者
- 想把模型能力私有化、本地化、离线化的团队
GitHub 与公众号分工
文章只讲关键判断,完整代码放 GitHub。
| 内容 | 放公众号 | 放 GitHub |
|---|---|---|
| 为什么选 Recipe | 必须讲 | README 简述 |
| 数据清洗 | 讲链路和坑 | 放完整脚本 |
| 训练样本设计 | 重点讲 | 放构建脚本和样本 |
| QLoRA 参数 | 讲关键参数 | 放完整训练命令 |
| 推理验证 | 讲验证思路 | 放脚本和测试样例 |
| GGUF / Android | 讲链路和坑 | 放 runbook、JNI、Compose |
| 失败案例和复盘 | 重点讲 | 可放 issue / notes |
每篇文章固定保留一个小节:
## 完整代码在哪里|CODE
这篇对应仓库里的这些文件:
- `xxx.py`
- `yyy.sh`
- `README.md`
如果某篇文章先于代码发布,可以写成:
完整代码会放到 GitHub,本文先讲设计和关键片段。
合集基调与边界
这个合集的基调必须明确:
端侧微调不是把 ChatGPT 变小,而是让小模型在一个窄场景里稳定完成一个结构化生成任务。
Recipe 案例的重点不是“会不会做菜”,而是:
- 用户输入是自然语言约束
- 模型输出是稳定 JSON
- App 可以解析、校验、展示或继续加工
- 端侧推理可以降低延迟、减少网络依赖,并让体验更可控
应该少写:
长篇源码讲解 / 大段参数表 / 泛泛概念科普 / 训练命令堆叠 / 万能 AI 叙事
应该多写:
为什么选这个任务 -> 一开始哪里容易想错 -> 数据怎么变成样本 -> 训完怎么验证 -> 端侧落地还差什么
每篇代码控制原则:
- 只贴 1-3 段关键代码
- 单段代码尽量不超过 30 行
- 复杂实现只给 GitHub 路径
- 关键命令可以贴,但不把文章写成 runbook
- 技术词首次出现必须用大白话解释
推荐目录结构
文章/2026-07/on-device-model-fine-tuning-practice/
├── plan.md
├── 01-why-fine-tune-on-device-model/
│ └── index.md
├── 02-recipe-data-cleaning/
│ └── index.md
├── 03-recipe-training-sample-design/
│ └── index.md
├── 04-qlora-training-small-model/
│ └── index.md
├── 05-inference-and-json-validation/
│ └── index.md
├── 06-merge-gguf-android/
│ └── index.md
├── 07-android-local-recipe-model/
│ └── index.md
└── 08-final-review/
└── index.md
每篇如果需要配图,继续使用:
images/
├── covers/
└── illustrations/
图片 PNG 交付按当前规范走 2x:
- 封面:900×383 设计,1800×766 导出
- 正文插图:1200×900 设计,2400×1800 导出
公众号版文章规划
为什么端侧模型微调不是“把 ChatGPT 变小”
文章目标:先校准读者预期。微调不是让 0.5B 小模型变成通用专家,而是让它在一个窄场景里稳定输出指定结构。
核心问题:
为什么不用云端大模型?为什么 prompt 不够?为什么 Recipe 这种生成任务适合讲微调?
建议结构:
- 从一个具体输入切入:
低脂、高蛋白、30 分钟晚餐 - 云端大模型能做,但端侧场景更看重离线、低延迟、成本和可控
- prompt 能解决一部分,但很难长期保证字段完整、格式稳定、输出习惯一致
- 微调真正解决的是“稳定输出格式”:字段有哪些、层级怎么组织、哪些值不能乱编
- 哪些场景别急着微调:知识频繁变化、开放问答、数据少、规则能解决、RAG 更合适
关键代码/素材:
SYSTEM_PROMPT- 一条输入约束对应的目标 Recipe JSON
- 微调 / RAG / 规则 / 云端 API 选择矩阵
完整代码在哪里:
README.mdrecipe_constants.pydata/examples/recipe.target.json
可配图:
- 端侧微调不是压缩 ChatGPT,而是训练稳定输出格式
用户约束 -> recipe JSON输入输出示意图- 合集路线图
微调前最重要的不是训练,而是数据清洗
文章目标:把读者从“先跑训练脚本”的冲动里拉回来,讲清楚数据才是微调的地基。
核心问题:
原始 recipe 数据为什么不能直接喂给模型?
建议结构:
- 原始数据通常会混着展示字段、元信息、无关状态、空值和历史字段
- 为什么要先保留 raw,再做 normalized
- 哪些字段适合进入目标 JSON:标题、简介、食材、步骤、时间、份量、营养、标签
- 哪些字段不要进入训练:内部 ID、来源 URL、状态字段、媒体字段、业务字段
- 为什么字段名、单位、数组顺序都要稳定
- 为什么公开文章只能使用脱敏样例
关键代码/素材:
normalize_recipeshould_keep_recipenormalize_detail_summary.json
完整代码在哪里:
normalize_recipe_details.pydata/raw/data/normalized_detail/data/examples/
可配图:
- raw detail -> normalized detail 字段清洗图
- 保留字段 / 删除字段对照图
训练样本怎么设计,决定了模型到底学什么
文章目标:讲清楚最关键的设计:训练输入不要像数据库导出,要像真实用户会说的话。
核心问题:
模型到底是在“复述菜谱”,还是在学习“根据用户约束生成可用 Recipe JSON”?
建议结构:
- 错误做法:把完整 metadata 塞进 prompt,让模型复述
- 真实使用时,用户只会给菜名、饮食目标、时间限制、食材限制
- 多 prompt 变体如何提升鲁棒性
- target JSON 为什么要剔除内部字段和不稳定字段
- 顶层字段为什么要固定
- system prompt 必须训练和推理共用
关键代码/素材:
build_prompt_variantsbuild_targetSYSTEM_PROMPT
完整代码在哪里:
build_generation_dataset.pyrecipe_constants.pydata/finetune_generation/train.jsonldata/finetune_generation/val.jsonl
可配图:
- 旧训练范式 vs 新训练范式
- 一条 recipe 扩成多条训练样本
QLoRA 实战:小模型训练不是越大越好
文章目标:讲训练本身,但不写成训练脚本说明书。重点解释为什么 0.5B / 1.5B 小模型是端侧案例的合理起点。
核心问题:
为什么不是全量微调?为什么不是直接上 7B?
建议结构:
- 端侧目标决定了模型不能太大
- QLoRA 用大白话解释:基座冻结,只训练一层小补丁
- 4-bit、LoRA adapter、target modules 各自解决什么问题
- batch、梯度累积、学习率、epoch、max_seq_length 怎么理解
- 训练完成后产物是什么:不是完整模型,而是 adapter
- 先把任务训稳,再谈更大模型
关键代码/素材:
LoraConfigBitsAndBytesConfigSFTConfig关键配置train_generation_lora.sh
完整代码在哪里:
train_generation_lora.pytrain_generation_lora.sh
可配图:
- QLoRA 训练结构图
- adapter / base model / merged model 关系图
训完怎么验证:别只看 loss,要看 JSON 能不能用
文章目标:把“训练成功”从 loss 拉回业务可用性。
核心问题:
模型输出看起来像菜谱,但程序真的能解析和展示吗?
建议结构:
- eval loss 能说明什么,不能说明什么
- 结构化输出必须看 JSON 可解析率
- 必看指标:字段完整率、类型合法率、单位合法率、步骤缺失率、截断率
- 用约束输入做验证:低脂、素食、20 分钟、少食材
- 不能只测训练集中见过的 prompt,要换问法测
- adapter 推理验证时 chat template 要和训练一致
关键代码/素材:
infer_generation_lora.py- JSON parse / schema validate 伪代码
validate_recipe_output
完整代码在哪里:
infer_generation_lora.pyvalidate_recipe_json.pydata/finetune_generation/dataset_summary.json
可配图:
- prompt -> raw output -> JSON parse -> schema validate 链路
- 训练指标 vs 产品指标对比图
从 LoRA 到 GGUF:模型怎么变成端侧能跑的文件
文章目标:讲训练产物怎么变成 Android 端侧可部署的模型文件。
核心问题:
为什么 adapter 不能直接丢到 Android 里跑?
建议结构:
- adapter 只是增量权重,不是完整模型
- 为什么要
merge_and_unload - 合并后为什么还要转 GGUF
- Q4_K_M 量化解决什么问题
- 模型文件命名、版本、hash、发布目录怎么管理
- 转完后先做 sanity check,不要直接进 App
关键代码/素材:
merge_lora_adapter.pyconvert_to_gguf.sh- 产物目录结构
完整代码在哪里:
merge_lora_adapter.pyconvert_to_gguf.shdocs/model_release.md
可配图:
- adapter -> merged HF -> f16 GGUF -> Q4_K_M GGUF
- 模型版本发布流程
Android 本地跑 Recipe 生成模型
文章目标:把合集从训练拉回 Android 工程。
核心问题:
模型文件能跑起来,离产品可用还差什么?
建议结构:
- GGUF 文件放哪里:assets、下载、私有目录、版本更新
- llama.cpp / JNI 调用路径
- Android 侧 prompt template 必须和训练保持一致
- 流式输出怎么接 UI
- 模型初始化耗时、内存峰值、线程数怎么采样
- 失败时如何回退模板、云端或提示用户重试
关键代码/素材:
- Android Termux 跑通记录
- Kotlin / Compose / JNI 接入材料
- 性能采样日志格式
完整代码在哪里:
ANDROID_TERMUX_RUNBOOK.mdANDROID_KOTLIN_COMPOSE_JNI.md- Android 示例工程路径后续补充
可配图:
- Android 本地推理架构图
- 模型下载、校验、加载、推理、回退流程图
复盘:哪些场景值得微调,哪些别碰
文章目标:收束合集,给出工程判断框架。
核心问题:
看完 Recipe 项目后,读者怎么判断自己的业务要不要微调?
建议结构:
- 这个项目证明了什么:小模型可以学会窄领域结构化生成
- 它没有证明什么:不能替代通用大模型
- 适合微调的任务:垂直、稳定、可评测、强格式、可离线
- 不适合微调的任务:知识频繁变化、开放问答、数据太少、规则就能解决
- 微调 vs RAG vs 规则 vs 云端 API
- Android 工程师可以怎么把这条能力迁移到自己的端侧产品里
完整代码在哪里:
- GitHub 仓库首页
docs/fine_tuning_guide.md- 各篇文章对应脚本
可配图:
- 微调 / RAG / 规则 / 云端 API 决策树
- 端侧模型工程闭环总结图
发布节奏建议
建议先按 8 篇公众号版推进:
- 端侧微调解决什么问题
- Recipe 数据清洗
- Recipe JSON 训练样本设计
- QLoRA 微调小模型
- 推理验证与 JSON 校验
- 合并、GGUF、量化
- Android 本地部署
- 微调场景复盘
如果后续读者反馈技术细节需求很强,再拆出番外篇:
- TRL 版本兼容踩坑
- assistant-only loss 真的有必要吗
- 多语言字段怎么处理
- 端侧 JSON 修复器怎么写
- Android llama.cpp JNI 性能优化
- Recipe 生成任务什么时候应该改用 RAG
单篇文章推荐结构
每篇文章都可以沿用这个结构:
## 这篇解决什么问题
## 我一开始怎么想错了
## 最后采用的方案
## 关键代码片段
## 完整代码在哪里|CODE
## 这一步踩过的坑
## 下一篇做什么
正式写作时可以根据内容灵活调整,但必须保留“完整代码在哪里|CODE”。
写作注意事项
- 每篇只讲一个主问题,不要把整套流水线塞进一篇
- 标题不要手写「一、二、三」或「①、②、③」,默认主题会自动承担章节感
- 代码要有,但不要堆满;优先贴关键函数、关键配置、关键命令
- 完整代码统一引导到 GitHub,不在公众号里逐行解释
- 每篇都要有“为什么这么设计”,不要只写“执行这个命令”
- 涉及 API key、内部接口、真实业务数据时,要脱敏或改成占位符
- 不写公司名称、内部项目路径、具体设备类型、设备控制参数
- 微调效果不要吹满,要明确边界:小模型、垂直任务、结构化输出、端侧约束
- Recipe 案例只呈现通用字段和自造样例,正文配图用流程图、JSON 卡片和端侧架构图
- 每篇结尾都给下一篇留钩子,形成合集连续感
第一篇建议
第一篇建议先写:
我用 Recipe 训练了一个端侧小模型:微调真正适合的场景
它适合作为合集开篇,因为它能先把读者的预期校准好:
- 微调不是让 0.5B 小模型变成通用专家
- 端侧模型也不是云端大模型的廉价替代
- 真正适合微调的是垂直、稳定、可评测、强结构任务
- Recipe 生成比事实查询更能体现微调价值
第一篇不需要写太多训练代码,重点讲:
问题 -> 为什么 Recipe 比事实库更适合 -> 微调到底学什么 -> 公开表达边界 -> 这个合集会怎么做
配图建议:
- 封面:小模型、本地 Recipe JSON、Android 端侧设备的技术感组合
- 正文图 1:微调 / RAG / 规则 / 云端 API 的选择矩阵
- 正文图 2:
用户约束 -> recipe JSON输入输出示意图 - 正文图 3:合集路线图
关注公众号
不错过后续文章和配套资源
扫码关注「唐人Console」,获取文章更新、配套资源和领取入口。 需要解锁时,文章页会生成专属口令。
扫码关注