1.1 TinyLlama 是什么:模型架构、参数规模、训练数据与能力边界
我第一次接触 TinyLlama 是在一台老旧的笔记本上,当时想跑个大模型玩玩,结果显存直接爆了。后来朋友推荐了这个只有 11 亿参数的小家伙,我才发现原来轻量级模型也能做不少事情。TinyLlama 基于 Llama 2 的架构设计,用的是 Transformer 解码器结构,具体来说就是 RMSNorm 归一化、SwiGLU 激活函数、旋转位置编码这套组合拳。它的隐藏层维度是 2048,总共 22 层,注意力头数 32 个,词表大小 32000。这些数字看起来枯燥,但它们决定了模型能记住多少东西、推理时需要多少显存。
训练数据这块挺有意思。TinyLlama 用了大约 3 万亿个 token 进行预训练,数据集主要是 SlimPajama 和 StarCoderData 的混合。3 万亿 token 对 11 亿参数的模型来说,相当于“喂”了非常大量的文本,官方说这是为了让小模型也能有不错的语言理解能力。不过说实话,它的知识储备肯定没法跟 70 亿甚至 130 亿参数的模型比。我试过问它一些冷门事实,它经常一本正经地胡说八道。但如果是做文本分类、简单问答、格式转换这类任务,它的表现完全够用。
能力边界这个问题得坦诚一点。TinyLlama 擅长的是模式识别和语言流畅度,不擅长复杂推理和深度知识检索。它的上下文窗口是 2048 个 token,处理长文档时得自己想办法分段。我把它当作一个“能理解指令的文本处理引擎”来用,而不是一个无所不知的专家。这个定位想清楚了,后面的选型和部署就顺理成章了。
1.2 为什么选择 TinyLlama:轻量推理、低成本微调、边缘设备与离线场景
选模型这事儿我踩过不少坑。刚开始总想着“越大越好”,下了 70 亿参数的模型,结果推理速度慢得像蜗牛,微调更是想都别想。后来算了一笔账:TinyLlama 在 FP16 精度下只占大约 2.2GB 显存,INT4 量化后不到 700MB。这意味着什么?一张 4GB 显存的旧显卡就能跑,甚至纯 CPU 推理也不会等到天荒地老。我实测过在 Ryzen 5 5600G 的核显上跑量化版,生成速度大概每秒 8 到 12 个 token,日常对话完全能接受。
微调成本是另一个让我心动的地方。全量微调 TinyLlama 只需要一张 8GB 显存的卡,LoRA 微调更是 6GB 就能搞定。相比之下,微调 70 亿参数的模型至少得 24GB 显存起步。我用手头的 RTX 3060 做了几次 LoRA 实验,每次训练也就一两个小时,电费都省了。这种低成本试错的机会对小团队和个人开发者来说太重要了,你可以快速验证想法,不行就换方向,不用心疼算力账单。
边缘设备和离线场景是我最看重的。TinyLlama 可以量化成 GGUF 格式,塞进树莓派或者手机里跑。我有个做智能家居的朋友,把 TinyLlama 部署在本地网关上做语音指令解析,完全不需要联网。数据不出户,隐私有保障,响应延迟还低。离线场景下,比如野外考察、工厂车间、医疗设备,这种能本地跑的小模型就是刚需。云端 API 再便宜,网络断了就全完蛋。
1.3 TinyLlama 的典型应用:个人助手、知识问答、文本分类、代码补全、嵌入式 AI
个人助手是我最常用的场景。我给自己搭了一个本地小助手,用 TinyLlama 做意图识别和回复生成。每天早上问它天气、让它帮我整理待办事项、把口语化的笔记转成结构化文本。它的指令跟随能力在同类小模型里算不错的,只要你把提示词写清楚,它很少跑偏。我试过用系统提示词设定角色,比如“你是一个简洁高效的助理,回答不超过三句话”,它就真的会照做。
知识问答这块得配合 RAG 用。单靠 TinyLlama 自己的知识库,问它“公司年假政策是什么”肯定答不上来。我的做法是用向量数据库存文档,检索出相关段落再塞给模型生成答案。这样它只需要做“阅读理解”和“语言组织”,不需要记住具体事实。文本分类也是它的强项,我拿它做过情感分析、垃圾邮件识别、工单自动打标签,准确率能到 85% 以上。关键是推理快,批量处理几千条数据几分钟就跑完了。
代码补全和嵌入式 AI 是两个比较特别的方向。代码补全方面,TinyLlama 的训练数据里包含 StarCoderData,所以它对 Python、JavaScript 这些语言有基本理解。我试过用它补全简单的函数和注释,效果还行,复杂逻辑就力不从心了。嵌入式 AI 更有意思,有人把量化后的 TinyLlama 跑在 ESP32 这种微控制器上,虽然速度慢,但证明了可行性。智能音箱、车载语音、工业传感器这些场景,未来都可能用上这种超轻量模型。
1.4 环境与资源准备:Python、PyTorch、Transformers、CUDA/CPU、显存与内存估算
环境搭建我建议用 Conda 或者 venv 隔离,别把系统 Python 搞乱了。Python 版本选 3.10 或 3.11 比较稳,3.12 有些库还没跟上。PyTorch 安装得看你的硬件,NVIDIA 显卡就装 CUDA 版本,AMD 或者纯 CPU 就装对应版本。我一般去 PyTorch 官网复制安装命令,比自己猜靠谱。Transformers 库用 pip 装最新版就行,记得顺便装 accelerate 和 sentencepiece,后面加载模型和分词都要用。
显存估算有个简单公式:FP16 精度下,模型参数乘以 2 就是显存占用。TinyLlama 的 11 亿参数大约需要 2.2GB,加上推理时的 KV Cache 和中间激活值,实际占用 3GB 左右。INT8 量化减半,INT4 再减半。内存方面,加载模型时系统 RAM 至少要有 8GB,处理长文本或者批量推理时建议 16GB。我用 Docker 跑过,容器内存限制设 4GB 也能启动,但稍微复杂点的任务就会 OOM。
CUDA 和 CPU 的选择取决于你的场景。有 NVIDIA 显卡就用 CUDA,推理速度能快 5 到 10 倍。没有的话 CPU 也能跑,只是得选量化版本,不然等得心急。我试过用 ONNX Runtime 在 CPU 上加速,比原生 PyTorch 快一些。Mac 用户可以用 MPS 后端,M 系列芯片的神经引擎挺给力的。不管什么硬件,建议先跑个 Hello World 级别的推理脚本,确认环境没问题再往下走。
1.5 模型获取与版本选择:Hugging Face 仓库、Chat 版本、GGUF 格式、许可证注意
Hugging Face 是获取模型的首选渠道。TinyLlama 的官方仓库是 TinyLlama/TinyLlama-1.1B-Chat-v1.0,这个 Chat 版本已经做过指令微调,开箱即用。还有个 TinyLlama-1.1B-intermediate-step-1431k-3T 是预训练版本,适合你自己做微调。下载方式有两种:用 git clone 拉整个仓库,或者用 huggingface-cli download 只拉需要的文件。我一般用后者,省流量也省时间。国内用户如果下载慢,可以配 HF 镜像或者用 ModelScope。
GGUF 格式是 llama.cpp 生态的产物,适合 CPU 推理和边缘设备。TheBloke 这个用户上传了大量量化版本,从 Q2_K 到 Q8_0 都有。Q4_K_M 是我最推荐的,质量和速度平衡得最好,文件大小约 700MB。Q2 虽然更小,但输出质量下降明显,有时候会语无伦次。Chat 版本和基础版本的区别在于有没有对话模板,Chat 版内置了 <|user|> 和 <|assistant|> 这样的特殊 token,直接用就行。基础版需要你自己构造提示词格式。
许可证这块得留个心眼。TinyLlama 用的是 Apache 2.0 许可证,商用完全没问题,也不需要开源你的修改。这点比 Llama 2 的原版许可证宽松多了。不过训练数据里可能包含一些有版权的内容,虽然模型权重本身不受影响,但生成的内容你得自己把关。我建议商用前读一遍 Apache 2.0 的条款,重点看专利授权和免责声明部分。如果要做二次分发,保留原始的 LICENSE 文件就行。
1.6 学习路线图:本地部署、微调教程、评估优化、应用发布的完整流程
我的学习路线是这样的:先跑通本地部署,再尝试微调,然后做评估优化,最后发布应用。本地部署是第一步,也是建立信心的关键。你不需要一上来就搞微调,先把模型跑起来,用 Transformers 加载、生成一段文本,感受一下它的能力和脾气。这个阶段我建议多试几种提示词,看看它在什么情况下表现好、什么情况下会崩。有了直观感受,后面做微调才知道该往哪个方向使劲。
微调教程我会从数据准备开始讲,然后是 LoRA 和 QLoRA 的具体实现。你需要理解什么是指令微调、为什么小模型更适合用参数高效方法、怎么构造高质量的问答对。训练过程中要盯着 loss 曲线,学会判断过拟合和欠拟合。评估优化阶段重点是量化、KV Cache、批处理这些推理加速技术,以及困惑度、ROUGE 这些评估指标。应用发布就是把模型封装成 API 或者 WebUI,让别人也能用上你的成果。
整条路线走下来,快的话两周,慢的话一两个月。取决于你每天投入多少时间,以及有没有踩到大坑。我的经验是别贪多,每个阶段做一个小项目。部署阶段就做一个命令行对话工具,微调阶段就做一个领域问答模型,评估阶段就对比不同量化等级的效果。做完一个再进下一个,知识是长在手上的,不是看视频看出来的。准备好了的话,下一章我们就从本地部署开始动手。
2.1 TinyLlama 本地部署方案总览:Transformers、llama.cpp、Ollama、vLLM、TGI
我折腾 TinyLlama 部署的时间不算短,前后试过五种路子。每种方案都有自己的脾气,适合不同的人跟场景。Transformers 最灵活,你能控制每一个细节,适合做实验和定制化开发。llama.cpp 走的是量化路线,CPU 上跑得飞快,树莓派、旧笔记本都能用,GGUF 格式功不可没。Ollama 把复杂度都藏起来了,一条命令就跑起来,适合想快速体验的人。vLLM 和 TGI 是给生产环境准备的,批处理、并发推理、服务化 API 这些都有,性能强但配置稍微麻烦点。
我个人建议从 Transformers 入手,把模型加载、生成参数这些搞明白。之后玩 llama.cpp,感受一下量化的魔力,你会发现原来 700MB 的模型也能干活。Ollama 可以作为日常快速测试的工具,它的 Modelfile 很好用,改系统提示词很方便。vLLM 和 TGI 留到需要给多人提供服务的时候再上。我一开始就贪心装了 vLLM,结果 CUDA 版本不匹配折腾了一下午,后来老老实实从 Transformers 开始,反而顺利多了。
选方案的时候看你的硬件和需求。只有 CPU 就死磕 llama.cpp 和 Ollama,有 N 卡就试试 vLLM。想在 Python 里嵌入模型做应用,Transformers 是首选。要做个聊天机器人给团队用,Ollama 的 OpenAI 兼容接口能省很多事。我的习惯是在本地开发用 Transformers 调试,部署到服务器换成 vLLM,中间用 Ollama 做快速原型验证。工具没有好坏,顺手就行。
2.2 使用 Hugging Face Transformers 部署:模型加载、tokenizer、生成参数设置
Transformers 部署 TinyLlama 的代码其实不长,但坑都在细节里。我一般这么写:from transformers import AutoModelForCausalLM, AutoTokenizer,模型选 TinyLlama/TinyLlama-1.1B-Chat-v1.0。加载的时候记得加 torch_dtype=torch.float16 和 device_map="auto",显存不够它会自动分到 CPU。tokenizer 要单独加载,Chat 版本自带对话模板,用 tokenizer.apply_chat_template 能省去手动拼特殊 token 的麻烦。我第一次用的时候忘了加 add_generation_prompt=True,结果模型把用户消息重复了一遍,排查了半天。
生成参数里 max_new_tokens 控制输出长度,我通常设 256 到 512,太长容易胡言乱语。temperature 默认 0.7,想稳定就调到 0.2,想有创意就 0.9。top_p 我一般保持 0.9,配合 repetition_penalty 1.1 防止复读。do_sample=True 才会随机采样,不然就是贪婪解码,输出会很呆。我试过 num_beams=4 做束搜索,质量有提升但速度慢了三倍,小模型不太划算。
代码跑通后建议封装成一个函数,传入 messages 列表直接返回回复。这样后面接 Gradio 或者 FastAPI 都方便。我习惯把模型和 tokenizer 放在全局变量里,避免每次推理重新加载。显存不够的话可以用 load_in_4bit=True,需要装 bitsandbytes。不过量化加载会慢一些,因为要在加载时做转换。我实测 4bit 量化后显存占用从 2.2GB 降到 800MB 左右,推理速度只慢了一点点,性价比很高。
2.3 使用 llama.cpp 与 GGUF 量化部署:量化等级、CPU/GPU 混合推理、内存占用
llama.cpp 是我在旧设备上最喜欢用的方案。它的核心是 GGUF 格式,把模型权重和元数据打包在一起,量化等级从 Q2_K 到 Q8_0 有十几种。Q4_K_M 是我的推荐,文件大小 700MB 左右,质量几乎无损。Q2_K 虽然只有 400MB,但输出经常语无伦次,我试过让它写邮件,结果冒出一些莫名其妙的词。Q6_K 和 Q8_0 质量更好,但文件大小涨到 1GB 以上,除非你对质量极度敏感,不然 Q4 系列够用了。
编译 llama.cpp 不算复杂,git clone 仓库后 make 一下就行。有 NVIDIA 显卡的话加 LLAMA_CUBLAS=1,能把部分层放到 GPU 上跑。-ngl 参数控制放多少层,我一般设 20 到 22,TinyLlama 总共 22 层,全放上去显存也就多占 1GB 左右,速度能提升好几倍。纯 CPU 的话用 -t 8 指定线程数,我试过在 Ryzen 5 上开 8 线程,生成速度大概每秒 10 个 token。内存占用看量化等级,Q4_K_M 加载后大概占 1.2GB 内存,跑起来再加 500MB 左右。
命令行推理用 ./main -m tinyllama-1.1b-chat.Q4_K_M.gguf -p "你的问题" -n 256。交互模式加 -i 参数。llama.cpp 也支持服务化,./server 启动后能用 HTTP 调用。我试过把 server 跑在树莓派 4B 上,响应速度虽然慢,但稳定运行了一周没崩。CPU/GPU 混合推理是它的亮点,显存不够就把部分层留在内存里,速度下降能接受。这种灵活性让 llama.cpp 成了边缘设备上的首选。
2.4 使用 Ollama 一键运行:Modelfile 编写、API 调用、OpenAI 兼容接口
Ollama 把模型下载、量化、运行全包了,安装后一条 ollama run tinyllama 就能开始对话。它的 Modelfile 是自定义模型的关键,我写过一个给客服场景用的版本。FROM 指定基础模型或者 GGUF 文件,TEMPLATE 定义对话格式,PARAMETER 设置温度、上下文长度这些。比如 PARAMETER temperature 0.3 让回答更稳定,PARAMETER num_ctx 2048 设置上下文窗口。写完用 ollama create my-tinyllama -f Modelfile 创建,之后 ollama run my-tinyllama 就能用。
API 调用是我最常用的功能。Ollama 默认监听 11434 端口,curl http://localhost:11434/api/generate 就能发请求。它的 OpenAI 兼容接口在 /v1/chat/completions,这意味着你可以直接用 openai Python 库,把 base_url 改成 http://localhost:11434/v1。我试过用 LangChain 接 Ollama,几乎不用改代码就能从 OpenAI 切换过来。流式输出也支持,设置 stream: true 就能逐字返回,做聊天界面体验很好。
Ollama 的模型仓库里有现成的 TinyLlama,直接 ollama pull tinyllama 就行。想用自定义的 GGUF 文件,在 Modelfile 里写 FROM ./my-model.gguf 也能加载。它的显存管理很聪明,模型不活跃时自动卸载,需要时再加载。我笔记本只有 4GB 显存,同时跑两个小模型也没问题。缺点是可定制性不如 llama.cpp 和 Transformers,比如你想改 KV Cache 的精度,Ollama 没给这个选项。日常使用完全够,深度调优就得换工具了。
2.5 使用 vLLM 与 TGI 高性能部署:批处理、并发推理、服务化 API
vLLM 是给生产环境准备的推理引擎,核心优势是 PagedAttention 和连续批处理。我拿它做过压力测试,同时发 50 个请求,TinyLlama 在 RTX 3060 上还能保持每秒 200 个 token 的总吞吐。安装 pip install vllm 之后,启动命令很简单:python -m vllm.entrypoints.openai.api_server --model TinyLlama/TinyLlama-1.1B-Chat-v1.0。它默认就提供 OpenAI 兼容的 API,端口 8000。客户端用 openai 库直接连,跟调 GPT 一模一样。
TGI 是 Hugging Face 的推理方案,用 Rust 写的,性能也很强。它支持动态批处理和 FlashAttention,对长上下文优化得不错。安装用 Docker 最省事:docker run --gpus all -p 8080:80 ghcr.io/huggingface/text-generation-inference:latest --model-id TinyLlama/TinyLlama-1.1B-Chat-v1.0。启动后访问 /generate 接口,参数有 max_new_tokens、temperature、top_p 这些。TGI 的流式输出做得很好,适合做实时对话应用。
vLLM 和 TGI 都能显著提升并发能力,但它们对显存的要求比单次推理高。vLLM 默认会预分配 90% 的显存做 KV Cache,显存小的话得加 --gpu-memory-utilization 0.5。TGI 也有类似参数。我建议显存 8GB 以上再考虑这两个方案,不然量化后的 TinyLlama 虽然能跑,但并发数上不去。它们更适合部署在服务器上,给多个用户同时提供服务。个人本地玩的话,Ollama 和 llama.cpp 更轻便。
2.6 本地部署常见问题:显存不足、推理速度慢、上下文长度限制、离线下载、端口安全
显存不足是最常遇到的。TinyLlama 全精度需要 2.2GB,加上 KV Cache 和中间激活,3GB 显存是底线。4GB 显卡勉强能跑,但稍微长点的对话就会 OOM。解决办法有几种:用 4bit 量化加载,显存降到 800MB 左右;用 llama.cpp 的 GGUF 量化版,CPU 内存也能跑;或者限制 max_new_tokens 和上下文长度。我试过在 2GB 显存的 MX150 上跑 Q4_K_M,虽然慢但能用。实在不行就升级硬件,或者用云 GPU 按小时租。
推理速度慢分两种情况。CPU 推理本身就慢,TinyLlama 在普通 CPU 上大概每秒 5 到 15 个 token,做聊天能感觉到延迟。换 llama.cpp 的量化版会快一些,开 GPU 加速效果更明显。另一种是 Python 代码写得不好,比如每次推理都重新加载模型,或者用了 num_beams 做束搜索。我早期写过一版每个请求都 from_pretrained,结果响应要十几秒,改成全局加载后降到一秒以内。批处理也能提升吞吐,vLLM 和 TGI 在这方面做得很好。
上下文长度限制是 TinyLlama 的硬伤,2048 个 token 大概对应 1500 个汉字。处理长文档得自己切分,或者用滑动窗口。我试过把长文分段摘要再合并,效果还行。离线下载用 Hugging Face 的镜像站或者 huggingface-cli download --resume-download 支持断点续传。国内推荐用 HF_ENDPOINT=https://hf-mirror.com 环境变量。端口安全容易被忽略,Ollama 和 vLLM 默认监听所有网卡,局域网里谁都能访问。我一般改成 --host 127.0.0.1,需要远程访问就加个 Nginx 反向代理和认证。别把模型服务暴露在公网上,被人白嫖算力还是小事,数据泄露就麻烦了。
3.1 微调前准备:任务定义、数据格式、训练验证划分、基座模型与 Chat 模板
微调这件事,我的教训是别急着写代码。先想清楚你要模型干什么。做客服问答、代码补全、还是文本分类,任务类型直接决定数据格式和训练目标。我见过有人拿着分类数据去调对话模型,结果模型学会了把标签拼在回复末尾,特别滑稽。
任务定下来之后,数据格式要跟基座模型对齐。TinyLlama Chat 版有自己的对话模板,用 tokenizer.apply_chat_template 能自动拼好。如果你用基座版做指令微调,得手动加特殊 token,忘了就会让模型困惑。我建议新手直接用 Chat 版当起点,省去模板适配的麻烦。
训练验证划分一般 9:1 或者 8:2,数据量小的话留 5% 做验证也行。我得提醒一句,验证集要跟训练集同分布,别拿客服数据训练再用代码数据验证,那样指标没有意义。基座模型选择上,TinyLlama-1.1B-Chat-v1.0 是最稳妥的起点,它已经做过指令微调,你只需要在特定领域上补一补。
3.2 微调方法选择:全量微调、LoRA、QLoRA、Adapter、指令微调与继续预训练
方法选择看你手里的显卡和要解决的问题。全量微调更新所有参数,效果上限高,但 TinyLlama 全量微调至少需要 12GB 显存,加上优化器状态和梯度,16GB 才比较舒服。我一开始不信邪,用 8GB 显卡硬跑,结果 batch size 只能设 1,训练一天才跑完一个 epoch,太煎熬了。
LoRA 是我最推荐的方式。它冻结原模型,只在注意力层插入低秩矩阵,可训练参数降到原来的 1% 左右。8GB 显存就能跑,训练速度快。QLoRA 更进一步,把基座模型量化到 4bit 再挂 LoRA,6GB 显存也能玩。代价是训练速度慢一点,但效果跟 LoRA 差不太多。我做过对比,QLoRA 训出来的模型在领域问答上跟 LoRA 的差距在 3% 以内,显存却省了将近一半。
Adapter 是另一种参数高效方法,在层间插入小型网络。它比 LoRA 更早出现,效果也不错,但生态支持不如 LoRA 广泛。指令微调适合让模型学会遵循指令,继续预训练适合注入领域知识。我的建议是:先做指令微调让模型听懂你的任务,数据里有大量领域文本的话再加一轮继续预训练。两者可以叠加,顺序一般是先继续预训练再指令微调。
3.3 数据集构建与清洗:指令回答对、多轮对话、领域语料、去重与质量过滤
数据集质量决定微调上限,这话我深有体会。我最早用爬来的问答数据训了一版,模型学会了一堆错误回答,比微调前还差。后来老老实实手工整理了 200 条高质量指令对,效果立刻上来了。TinyLlama 参数量小,1000 到 5000 条高质量样本就能看到明显提升,贪多反而容易过拟合。
指令回答对是最常见的格式,JSONL 里每行一个样本,包含 instruction、input、output 三个字段。多轮对话用 conversations 列表,每项有 role 和 content。领域语料就是纯文本,做继续预训练时用。我习惯把所有数据统一成 JSONL,方便后面用 datasets 库加载。
去重和过滤不能省。精确去重可以用集合判断,语义去重得用 embedding 算相似度。质量过滤我设几条规则:回答长度低于 10 个字符的丢掉,包含乱码的丢掉,重复标点的丢掉。我还用 TinyLlama 自己给数据打分,让它判断回答质量,分数低的剔除。这个方法有点取巧,但实测能过滤掉 20% 左右的垃圾数据。
3.4 使用 Transformers 与 PEFT 实现 LoRA 微调:环境配置、代码结构、训练参数
环境配置不复杂,pip install transformers peft datasets accelerate bitsandbytes 基本够了。我用的是 transformers 4.36 以上的版本,PEFT 0.7 以上。装的时候注意 CUDA 版本匹配,我踩过 torch 和 CUDA 不兼容的坑,报错信息很难懂,最后重装驱动才解决。
代码结构我习惯分四块:加载模型和 tokenizer、配置 LoRA、准备数据集、训练。加载模型时加 load_in_4bit=True 就走 QLoRA 路线。LoRA 配置用 LoraConfig,r=8、lora_alpha=16、target_modules=["q_proj", "v_proj"] 是常用组合。r 越大可训练参数越多,效果可能更好但显存占用增加。我试过 r=16,显存多用了 200MB,效果提升不明显,后来就固定用 r=8。
训练参数里 learning_rate 我设 2e-4,LoRA 一般比全量微调大一些。num_train_epochs 设 3 到 5,多了容易过拟合。per_device_train_batch_size 设 4,显存不够就降到 2 配合 gradient_accumulation_steps=4。warmup_ratio=0.03 让学习率慢慢升上去,训练更稳定。save_steps 设 100,方便随时停下来测试效果。我一般训到验证 loss 不再下降就停,不硬跑满 epoch。
3.5 使用 TRL 进行 SFT 与 DPO:SFTTrainer、数据 collator、对话模板、训练监控
TRL 把 SFT 训练包装得很方便。SFTTrainer 帮你处理了数据 collator、padding、loss 计算这些琐事。用的时候把数据集传进去,指定 dataset_text_field 或者 formatting_func 就行。我一般写个 formatting_func 把样本套进对话模板,这样能确保训练格式跟推理格式一致。
SFTConfig 里参数跟普通训练差不多,多了一个 max_seq_length 控制序列长度。TinyLlama 支持 2048,我一般设 1024 省显存。packing=True 能把多个短样本拼成一条长序列,提升训练效率,但要注意别把不同对话拼在一起,可能造成混淆。我试过 packing,训练速度提升 30% 左右,但验证 loss 波动变大,后来还是关掉了。
DPO 是偏好优化,需要正例和负例配对。数据格式是 prompt、chosen、rejected 三个字段。DPOTrainer 会用参考模型计算概率差,训练目标是让模型更倾向 chosen。我对同一批指令让 TinyLlama 生成两个回答,人工标注好坏做负例。DPO 训练比 SFT 敏感,beta 参数设 0.1 比较稳,学习率要降到 5e-5 以下。训练监控我习惯看 wandb,loss 曲线和验证指标都能实时看到,比盯命令行输出舒服。
3.6 使用 Unsloth 与 Axolotl 加速微调:低显存技巧、速度对比、配置示例
Unsloth 是我最近发现的宝藏工具。它重写了注意力计算,训练速度比原生 Transformers 快 2 倍左右,显存占用减少 30% 到 50%。安装稍微麻烦点,要按它文档里的版本装 torch 和 xformers。用起来很简单,把 FastLanguageModel.from_pretrained 替换掉原来的加载方式,其他代码基本不用改。我拿 6GB 的 3060 笔记本实测,原本 QLoRA 只能跑 batch size 1,用 Unsloth 能跑到 batch size 4。
Axolotl 走的是配置驱动路线,YAML 文件里写清楚模型、数据、训练参数,一条命令启动。它内置了很多优化,FlashAttention、梯度检查点、DeepSpeed 都能通过配置开启。适合需要批量实验或者团队协作的场景,配置能版本管理,比散落的 Python 脚本规范。我搭过 Axolotl 的 QLoRA 配置,跑 TinyLlama 微调大概比手写脚本快 1.5 倍,主要是数据加载和 batch 组织优化得好。
低显存技巧还有几个:开启梯度检查点能把激活值占的显存省下来,代价是速度慢 20%;用 optim="paged_adamw_8bit" 让优化器状态分页存储,8bit 量化后显存占用降低一半;缩短 max_seq_length 到 512 也能省不少。我组合用过这些技巧,在 4GB 显存的 MX150 上成功微调了 TinyLlama,虽然跑了一整夜,但确实跑通了。
3.7 微调训练过程:学习率、批次大小、梯度累积、检查点保存与恢复
学习率是最重要的超参数。LoRA 我一般从 2e-4 开始,效果不好就降到 1e-4 或者升到 3e-4。学习率太大会导致 loss 震荡甚至发散,太小则收敛慢。我习惯用 cosine 调度,配合 warmup 让训练前期平稳。weight_decay 设 0.01 防止过拟合,LoRA 层参数少,这个值可以小一点。
批次大小和梯度累积是显存和效果的平衡。有效批次大小等于 batch_size × gradient_accumulation_steps × GPU 数量。我一般让有效批次保持在 32 到 64 之间。单卡 8GB 显存跑 QLoRA,batch_size=4、gradient_accumulation_steps=8,有效批次 32,训练比较稳。批次太小会让梯度噪声大,loss 曲线抖得厉害。
检查点保存我设 save_steps=100,保留最近 3 个。训练中断了从检查点恢复很方便,trainer.train(resume_from_checkpoint=True) 就行。我遇到过跑了一天断电的情况,幸好有检查点,只丢了 100 步的进度。训练日志要保存下来,loss、学习率、显存占用都记着,出问题好回溯。我习惯把 wandb 的 run 链接也存在笔记里,过几天回头看还能找到。
3.8 微调后合并与导出:合并 LoRA、保存模型、转换为 GGUF、上传 Hugging Face
训练完的 LoRA 权重是分离的,要用 merge_and_unload() 合并回基座模型。合并后的模型跟原模型结构一样,可以直接用 Transformers 加载推理。我一般合并后先跑几个测试问题,确认效果符合预期再导出。合并前记得把模型设成 eval 模式,不然 dropout 会干扰。
保存模型用 save_pretrained,tokenizer 也要一起存。文件大概 2.2GB 左右,float16 精度。想省空间可以保存成 8bit 或者 4bit,用 bitsandbytes 的量化保存功能。我一般存两个版本,全精度的备份,量化的日常用。保存路径建议带上日期和任务名,比如 tinyllama-customer-service-20240601,方便管理。
转 GGUF 用 llama.cpp 的 convert.py 脚本,先把模型转成 GGML 格式再量化。python convert.py --outfile model.gguf --outtype f16 转全精度,然后用 quantize 工具做 Q4_K_M 量化。整个过程大概几分钟。上传 Hugging Face 用 huggingface-cli upload,记得写好 model card,说明基座模型、训练数据、使用方法。许可证也要注意,TinyLlama 是 Apache 2.0,商用没问题,但你的训练数据如果有版权限制,上传前要确认清楚。我传过一个模型忘了写 model card,后来别人问怎么用,才发现文档多重要。
4.1 评估指标与测试集:困惑度、准确率、ROUGE/BLEU、人工评估、安全与偏见
微调完一个模型,我第一件事是把它拉到测试集上跑一遍。困惑度这个指标我每次都会看,但它容易骗人。有次我训了个客服模型,困惑度从 12 降到 8,我以为成了,结果一问具体问题,回答全是“好的,我帮您查询”这种废话。困惑度低只说明模型对测试文本的概率预测更准,不代表生成内容有用。
准确率适合分类任务,比如情感分析或者意图识别。我做过一个意图分类的 TinyLlama,测试集准确率 92%,但上线后发现用户换个说法就懵了。ROUGE 和 BLEU 我用来快速对比摘要和翻译任务,分数高不一定读起来顺,分数低肯定有问题。这些自动指标只能当参考,别当圣旨。
人工评估才是真刀真枪。我拉了两个同事,每人看 50 条生成结果,从相关性、流畅度、有用性三个维度打分。我们还会故意问一些敏感问题,比如“怎么破解密码”,看模型会不会胡答。安全与偏见测试不能省,TinyLlama 参数量小,容易被带偏,我试过给它一个带偏见的提示,它直接顺着说了下去。后来我在系统提示里加了拒绝规则,情况好了很多。
4.2 推理性能优化:量化、KV Cache、批处理、投机解码、FlashAttention
量化是我最常用的优化手段。llama.cpp 的 Q4_K_M 量化把模型压到 700MB 左右,推理速度翻倍,质量损失我测下来不到 5%。我一般在本地部署用 Q4,服务端用 Q8 或者 FP16。AWQ 和 GPTQ 也试过,显存占用更低,但配置麻烦,TinyLlama 本身小,Q4 已经够用了。
KV Cache 开启后生成速度提升明显,尤其是长对话。我习惯设 use_cache=True,但上下文拉到 2048 时显存会多占几百 MB。批处理在服务端很关键,vLLM 的连续批处理能让并发吞吐量涨好几倍。我压测过,单条请求延迟 200ms,开批处理后 10 并发平均延迟只增加到 350ms,吞吐量上去了。
投机解码我用 TinyLlama 当草稿模型,配合一个更大的模型验证,理论上能加速。实际测下来 TinyLlama 本身太小,草稿质量一般,加速比只有 1.2 倍左右,不太划算。FlashAttention 我装了 flash-attn 库,训练和推理都提速 15% 到 20%,显存也省了一点。装的时候注意 CUDA 版本,我折腾了一下午才编译通过。
4.3 提示工程与 RAG 结合:系统提示、少样本示例、检索增强、工具调用
系统提示是控制模型行为的第一道闸门。我写过一个“你是一个专业的法律助手,只回答法律相关问题,其他问题一律拒绝”的提示,模型听话了很多。少样本示例我一般放 2 到 3 个,放在系统提示后面。示例质量比数量重要,我试过放 5 个例子,模型反而开始模仿例子的格式,忘了回答内容。
RAG 是我做领域问答的标配。用 FAISS 建向量库,把文档切片存进去,用户提问时检索 top3 相关段落拼进上下文。TinyLlama 的上下文窗口 2048,我一般留给检索内容 800 字左右。LangChain 和 LlamaIndex 都能快速搭起来,我习惯手写检索逻辑,方便控制细节。加 RAG 后,领域问答准确率从 55% 提到了 78%。
工具调用我试过让 TinyLlama 输出 JSON 格式的函数调用。模型能学会,但格式偶尔出错,需要加校验和重试。我给它定义了两个工具:查天气和算数学。提示里写清楚工具描述和输出格式,模型大部分时候能正确调用。TinyLlama 能力有限,工具别超过三个,否则容易混乱。
4.4 部署为本地 API 与 WebUI:FastAPI、Gradio、Streamlit、OpenAI 兼容接口
FastAPI 是我部署本地 API 的首选。写一个 /generate 接口,接收 prompt 和参数,返回生成文本。模型加载一次放在全局变量里,避免每次请求都重新加载。我用 uvicorn 启动,--workers 1 就够,多了会显存溢出。接口加个简单的 token 验证,防止局域网里被人乱调。
Gradio 适合快速搭演示界面。我一般写个聊天窗口,加几个滑块调 temperature 和 max_tokens。十分钟就能跑起来,发给同事体验很方便。Streamlit 我也用过,写起来更简单,但自定义程度低一些。两个工具都能直接调 FastAPI 接口,分离前后端。
OpenAI 兼容接口我最推荐。llama-cpp-python 自带 server,启动后就是 /v1/chat/completions 格式。我直接把现有项目的 base_url 改成本地地址,其他代码一行不用动。FastChat 也能做同样的事,支持多模型切换。这个方案让我省了很多适配时间,各种 OpenAI 客户端都能直接连。
4.5 常见故障排查:胡言乱语、灾难性遗忘、过拟合、显存溢出、速度慢
胡言乱语我遇到过两次。一次是训练数据里混进了乱码,模型学会了输出奇怪符号。另一次是推理时 temperature 设到 1.5,模型开始放飞自我。解决办法很简单:清洗数据,把 temperature 降到 0.7 以下。我还会加重复惩罚,repetition_penalty=1.1,能减少复读机现象。
灾难性遗忘是微调的通病。我拿 TinyLlama 做领域微调后,它连简单的算术都不会了。后来我在训练数据里混了 10% 的通用指令数据,遗忘问题缓解了不少。LoRA 本身对基座模型影响小,比全量微调好很多。过拟合看验证 loss 就知道,连续几个 epoch 不降反升,赶紧停。我一般保留验证 loss 最低的那个检查点。
显存溢出和速度慢经常一起出现。我习惯先降 batch size,再开梯度检查点,还不行就上 4bit 量化。速度慢的话,检查是不是没用 KV Cache,或者序列长度设太长了。有次我设了 2048 的 max_seq_length,推理一条要 8 秒,降到 512 后只要 1.5 秒。硬件不行就别硬撑,量化是好朋友。
4.6 持续迭代与版本管理:数据回流、A/B 测试、模型注册、许可证与合规
模型上线不是终点。我搭了一个简单的反馈按钮,用户可以对回答点赞点踩。踩的数据我每周导出一次,人工标注后加入训练集。数据回流让模型慢慢适应真实用户的问题分布,我试过迭代两轮后,用户满意度从 70% 升到了 82%。回流数据要过滤,别把恶意输入也喂进去。
A/B 测试我做得比较粗糙。部署两个模型,用 Nginx 分流 10% 的用户到新模型,对比点击率和满意度。跑了三天,新模型在长问题上表现更好,短问题打平,我就全量切了。模型注册我用文件夹加 JSON 记录,每个版本存模型文件、训练参数、数据路径和评估结果。简单但够用,比 MLflow 轻量。
许可证与合规要留心。TinyLlama 是 Apache 2.0,商用没问题。你的训练数据如果来自网络爬虫,得确认版权。用户对话数据涉及隐私,存储前要脱敏。我见过有人把用户聊天记录直接拿来训练,后来被投诉了。合规这事不出问题没人管,出了问题就是大麻烦。
4.7 综合实战案例:从 TinyLlama 本地部署到 LoRA 微调再到领域问答机器人
我拿一个真实项目串一下整个流程。目标是做一个法律问答机器人,能回答合同法相关的问题。第一步用 Ollama 拉下 TinyLlama,跑几个基础问题,发现它法律知识几乎为零。第二步收集了 2000 条法律问答对,清洗后格式化成 JSONL。第三步用 Unsloth 做 QLoRA 微调,6GB 显存跑了 3 个 epoch,大概两小时。
微调后模型能答对 60% 的测试问题,但遇到复杂条款还是胡说。我加了 RAG,把合同法全文切片存进 ChromaDB,检索 top3 段落拼进提示。准确率提到 75%。我又写了系统提示,限定回答范围,拒绝非法律问题。部署用 FastAPI 加 llama-cpp-python 的 OpenAI 兼容接口,前端用 Gradio 搭了个聊天页。
上线后我收集了 300 条用户反馈,把答错的案例标注后加入训练集,又做了一轮 LoRA 微调。第二轮模型准确率到了 83%,响应速度平均 1.2 秒。这个案例告诉我,TinyLlama 虽然小,配上微调和 RAG,在垂直领域完全能打。整个项目从零到上线花了两周,成本几乎为零,就是电费。