1. 先把话说清楚:这个“小模型”到底是个什么东西
昨天某国际头部厂商放出了一批开源权重的轻量级模型,圈子里一夜之间全是相关讨论。我早上刷到消息,下午就把机器腾出来跑了一遍。这里我不复述官方通稿,只讲一个真正的从业者在拿到权重之后会关心什么:它多大、跑得动吗、中文行不行、能不能塞进我现有的项目里。为了方便叙述,下文统一按它的代号叫G 系列,两个规格分别写作G-2B和G-7B,参数量就是名字里的数字,这是最不容易记混的叫法。
先给结论,省得你翻到最后:这是一个定位在“端侧推理与轻量私有化部署”的开源权重家族,两个尺寸、8K 上下文、开放商用许可(具体条款要自己逐条读,别听二手转述)。它不是那种能跟你辩论哲学、写十万字长文的重型模型,你拿它去对标旗舰闭源模型一定失望。但如果你想在一台没有独立显卡的笔记本上跑一个“永远在线、不联网、数据不出机器”的助手,或者想拿它做结构化信息抽取、文本分类、意图识别这类高并发小任务,那它出现的时机恰到好处。
我为什么对这类小模型这么上心?因为过去两年我经手的项目里,真正卡住落地的问题,关键难点从来都在成本、延迟和数据合规这三件事上;模型够不够聪明反而没那么关键。一个 7B 级别的模型,如果量化得当,在一张消费级显卡上能跑到每秒几十个 token,单机并发撑住十几个请求,那么很多“必须调用云端接口”的场景,其实可以直接搬到本地。尤其是处理客户合同、病历摘要、内部工单这类数据,哪怕只省掉一次数据出域的风险,价值就远超省下来的那点接口费。
再说回 G 系列本身。它有两个规格,这个设计不是随手为之,背后是两条完全不同的使用路径。2B 那一档面向的是“资源极度受限”——手机、树莓派、老笔记本、浏览器里跑,它的对手是各种端侧小模型;7B 那一档面向的是“单卡桌面”,一张 8G 到 12G 显存的卡就能舒服地跑起来,是个人开发者和中小团队最现实的私有化起点。两个规格共用一套分词器和相近的训练配方,这意味着你在 7B 上调好的提示词、验证过的输出格式,迁到 2B 上大概率还能用,只是效果会打折。这种“家族一致性”在工程上非常值钱,我后面会专门讲怎么利用它。
还有一点值得单独提:8K 上下文。在 2B 这个量级上,很多同尺寸模型只给 2K 到 4K,做长文档处理会很别扭。8K 意味着你可以一次性把一份十几页的说明书、一份完整的会议记录塞进去,不用先做分块再拼接。分块拼接是长文档处理里最容易出错的环节,一断裂就丢上下文,一重叠就重复计费,能不做就不做。
2. 上手前的硬核算账:显存、量化与下载
2.1 显存到底要多少,给你一个能直接套的算法
很多人第一步就卡在“我这台机器能不能跑”。别猜,算。模型权重的显存占用有一个足够准确的粗算公式:
权重显存(GB)≈ 参数量(B)× 每参数字节数
而每参数字节数由量化精度决定:FP16 是 2 字节,INT8 约 1 字节,4-bit 量化约 0.5 字节,再往上还有 3-bit、2-bit,但精度损失开始变得难以接受。除此之外,还有**上下文缓存(KV Cache)**这笔账,很多教程直接忽略,结果一跑长文本就爆显存。它的粗算方式是:
KV 缓存(字节)≈ 2 × 层数 × 隐藏维度 × 上下文长度 × 批大小 × 每元素字节数
这个公式算出来精度要求高,实操里我用一个更糙但更好记的经验值:8K 上下文、批大小 1 的情况下,KV 缓存大约占 0.5G 到 1.5G,模型越大、层数越多,越靠近上限。再留 0.5G 给推理引擎自己的开销,就是你的实际需求。
按这个算法,我把常见配置整理成一张表,你可以直接对号入座:
| 规格 | 量化档位 | 权重占用 | 8K 上下文总占用(估) | 能跑起来的硬件 |
|---|---|---|---|---|
| G-2B | FP16 | 约 4.5G | 约 5.5G | 6G 显存以上的独显 |
| G-2B | INT8 | 约 2.2G | 约 3.2G | 4G 显存,或纯 CPU |
| G-2B | 4-bit | 约 1.3G | 约 2.3G | 纯 CPU 可跑,手机可行 |
| G-7B | FP16 | 约 15G | 约 16.5G | 16G 显存较紧张,24G 舒服 |
| G-7B | INT8 | 约 7.5G | 约 9G | 12G 显存 |
| G-7B | 4-bit | 约 4.2G | 约 5.5G | 8G 显存刚好,6G 要缩上下文 |
这张表是我自己反复调出来的,跟实际跑下来的数字误差在 10% 以内。关键提醒:不要按“权重占用 = 总需求”来规划硬件,那是最常见的翻车姿势。我见过有人在 8G 卡上跑 4-bit 的 7B,短对话一切正常,一喂两万字长文就卡死,排查半天才发现是 KV 缓存把显存吃干净了。
2.2 量化档位怎么挑:为什么我几乎总选 Q4_K_M
量化档位有一堆后缀,Q2、Q3、Q4_0、Q4_K_M、Q5_K_M、Q8_0……新手看到直接晕。我的选择逻辑非常简单粗暴:默认 Q4_K_M,显存富余就上 Q5_K_M,显存紧张才降到 Q3_K_M,Q2 系列除非是纯端侧演示,否则不用。
理由是这么几条。第一,4-bit 是精度和体积的拐点,从 FP16 降到 4-bit,体积砍掉四分之三,而在我做过的一批抽取类任务上,准确率下降通常在一个百分点左右,属于业务能接受的范畴。第二,Q4_K_M 里的 K 表示用了分组量化,M 表示中等档位,相比朴素的 Q4_0,它对权重中那些“重要通道”的保护更好,实测在中文长句和数字抽取上差距明显——Q4_0 经常把金额、日期这类数字抄错,Q4_K_M 就少很多。第三,Q5 以上收益递减,体积却涨得很快,在 7B 这个量级上性价比不高。
这里有个反直觉的经验:如果你要做的是分类、意图识别这类任务,量化带来的损失几乎感觉不到;要做的是代码生成、数学推理,量化的杀伤力就很大。我建议你把量化档位和你自己的任务类型绑定考虑,而不是迷信某个“最佳档位”。我自己的做法是:拿 200 条真实业务样本,分别在 Q4_K_M 和 Q8_0 上跑一遍,对比准确率差异,如果差距小于 2%,就用 Q4_K_M 省下那 60% 的显存。
2.3 下载与校验:这一步千万别偷懒
权重文件动辄几个 G,下载中断、文件损坏是家常便饭。我的习惯是把模型统一放在一个固定目录下,按“家族/规格/量化档位”三层命名,绝对不用默认文件名。这样做的好处是,半年后你回头看自己的实验记录,一眼就知道当时用的是哪一档,不用去猜model_q4.gguf到底是哪来的。
校验这一步很多人跳过,我劝你别。下载完成后至少做两件事:比对文件大小与发布方给出的字节数是否一致,以及跑一句最简单的推理确认能正常输出。我曾经遇到过一个文件,大小对得上,能加载,但输出全是乱码,最后发现是下载过程中某一段静默损坏了。这种问题排查起来极其痛苦,因为你会先怀疑量化、怀疑引擎、怀疑参数,最后才怀疑到文件本身。
另外提醒一句:不同量化档位的文件后缀不同,混用会直接加载失败或者输出异常。如果你同时下载了多个档位,务必在目录名上区分清楚。
3. 三条实操路线:从“能跑”到“能用”
3.1 路线一:命令行引擎,十分钟出第一句话
最快验证“这玩意儿到底行不行”的方式,是用一个基于纯 C/C++ 的本地推理引擎跑 GGUF 格式的量化文件。这类引擎的优点是零依赖、装上就能用,缺点是参数名各家略有差异。下面给的是通用形态,具体参数名请以你手上引擎的--help为准:
# 通用命令行引擎调用形态 engine-cli run \ --model ./models/g-family/g-2b/g-2b-q4_k_m.gguf \ --prompt "用三句话解释什么是模型量化,不要用术语。" \ --ctx 2048 \ --threads 8 \ --temp 0.7 \ --top-p 0.9 \ --repeat-penalty 1.1 \ --n-predict 256参数我逐个解释一下取舍,这几个数字都不是随便填的:
--ctx 2048:第一次测试别直接开 8K,上下文越长,加载时间和显存占用越高,先用 2K 跑通,确认没问题再往上加。--threads 8:一般设成物理核心数,不是逻辑线程数。我试过设成 16,速度反而慢了,因为线程切换开销吃掉了收益。--temp 0.7+--top-p 0.9:这是我自己常用的“日常对话”组合,温度太低会变得机械重复,太高会开始胡说。做抽取类任务时我会把温度压到 0.1 到 0.2,几乎等于贪心解码,输出更确定。--repeat-penalty 1.1:小模型特别容易陷入复读,这个值调高一点能明显缓解,但超过 1.3 会让语言变得别扭。--n-predict 256:限制生成长度,防止它停不下来。命令行引擎不像服务端有超时机制,忘了设这个参数,一不小心就能生成几千个 token。
第一次跑通,屏幕上吐出第一句通顺的中文,那种感觉还是挺爽的。但我必须泼一盆冷水:命令行跑通只能说明权重没坏、硬件够用,距离“能用”还有一段距离。真正决定体验的是提示词、上下文管理、输出解析这些东西,模型本身只是其中一环。
3.2 路线二:Python 接口,接进自己的脚本
命令行适合验证,真要做项目还得走代码。Python 生态里加载这类开源权重有两条路:一条是用高层封装库,几行代码搞定,适合快速迭代;另一条是用底层推理引擎的 Python 绑定,对显存和性能的控制更细,适合做服务端。
我用高层封装时,习惯把加载逻辑封装成一个函数,把设备、精度、量化这些参数都显式暴露出来,而不是散落在各处:
# 通用加载形态,函数名以你所用推理库的实际接口为准 def build_generator(model_dir, dtype="float16", device="cuda:0", max_new_tokens=512): tokenizer = load_tokenizer(model_dir) model = load_causal_lm(model_dir, dtype=dtype, device=device) model.set_generation_config( max_new_tokens=max_new_tokens, temperature=0.3, top_p=0.9, do_sample=True, repetition_penalty=1.1, ) return model, tokenizer def chat(prompt, model, tokenizer): # 关键:一定要用模型自带模板,别自己手拼 text = tokenizer.apply_chat_template( [{"role": "user", "content": prompt}], tokenize=False, add_generation_prompt=True, ) inputs = tokenizer(text, return_tensors="pt").to(model.device) out = model.generate(**inputs) return tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)这里有一个极其重要但九成人会踩的坑:对话模板。这类开源权重在训练时用的是一套特定的对话标记格式,你必须调用分词器自带的模板函数去套,手拼字符串会导致效果断崖式下跌。我见过有人抱怨“这模型怎么答非所问”,贴出代码一看,提示词是直接用 f-string 拼的,把训练时的格式全破坏了。这个坑在 2B 这种小模型上尤其明显,大模型还有点容错空间,小模型直接崩。
另一个细节是inputs["input_ids"].shape[1]这个截断。生成结果里包含了输入部分,不切掉的话你每次都会看到问题原文,接进业务系统会造成数据重复。这个错误我早期犯过,后来养成习惯,所有生成函数里都做这个切片。
走 Python 路线还有一层考量:批处理。单条推理的吞吐并不好看,真正摊薄成本靠的是批量。但批量会成倍放大 KV 缓存占用,8G 卡上批大小超过 4 基本就爆了。我的做法是服务端做请求排队,把短请求攒成一个批次一起推,长请求单独处理,实测吞吐能提升两倍以上。
3.3 路线三:本地服务化,做成 HTTP 接口
要让别的程序调用,就得把它变成一个常驻服务。这类本地推理引擎大多带一个内置的服务模式,启动之后对外暴露一个兼容主流接口格式的 HTTP 端口,你的业务代码不用改,把基础地址换个地方就行。
# 启动本地服务(参数名以引擎实际帮助为准) engine-server \ --model ./models/g-family/g-7b/g-7b-q4_k_m.gguf \ --ctx 8192 \ --batch-size 4 \ --threads 12 \ --host 127.0.0.1 \ --port 8080几个参数值得单独说。--host 127.0.0.1而不是0.0.0.0,这是安全习惯,除非你明确需要局域网访问,否则别把它暴露出去,这类服务没有鉴权,暴露出去等于把模型白送。--batch-size 4是我在 8G 卡上的经验值,前面算过 KV 缓存的账,4 条 2K 上下文的请求刚好卡在边界上。--ctx 8192开到最大是因为服务化之后你很难预判请求长度,但要清楚,第一个请求吃到 8K 上下文之后,剩余并发能力会被进一步压缩。
服务起来之后,用最普通的请求测试一下:
curl http://127.0.0.1:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "messages": [{"role": "user", "content": "把这句话改写成正式邮件语气:明天我不来了。"}], "temperature": 0.2, "max_tokens": 300 }'我为什么推荐服务化而不是在业务代码里直接加载模型?三个原因。第一,模型加载很慢,2B 还好,7B 加载要几秒到十几秒,放在脚本里每次运行都付这个代价,非常浪费。第二,显存独占,一个进程加载了模型,另一个进程再加载就爆了,服务化天然解决资源争抢。第三,更换模型不需要改业务代码,只要接口格式一致,今天用 2B 明天换 7B,业务侧无感。这三点在个人玩具项目里体现不出来,一旦开始多人协作,价值立刻凸显。
4. 实测记录:跑分之外的体感
4.1 速度与资源占用
我这里给的是自己机器上的实测数字,硬件是一张 8G 显存的消费级卡加 32G 内存,环境是量化后的 4-bit 权重。请注意,这些数字只能作为量级参考,不同硬件差异可能达到两三倍,别拿我的数去跟别人吵架。
| 场景 | 规格 | 量化 | 硬件 | 生成速度(token/s) | 首 token 延迟 |
|---|---|---|---|---|---|
| 短问答(200 token 上下文) | G-2B | 4-bit | 纯 CPU,8 线程 | 约 18 | 约 0.4 秒 |
| 短问答 | G-2B | 4-bit | 8G 显卡 | 约 65 | 约 0.1 秒 |
| 短问答 | G-7B | 4-bit | 8G 显卡 | 约 24 | 约 0.3 秒 |
| 长文档摘要(6K 上下文) | G-7B | 4-bit | 8G 显卡 | 约 19 | 约 3.5 秒 |
| 批量抽取(批大小 4) | G-7B | 4-bit | 8G 显卡 | 约 14(单条等效) | 约 1.2 秒 |
从这张表能读出几个很实际的结论。
第一,2B 在纯 CPU 上真的能用。每秒 18 个 token,比人阅读速度快,做一个输入法级别的补全、聊天玩具、离线笔记助手,完全够了。我拿一台四年前的老笔记本试过,加上 8 线程参数调优,体感是“能接受”而不是“勉强”。
第二,7B 在 8G 卡上是紧巴巴的舒服。短请求跑得挺快,但一上长上下文,首 token 延迟从 0.3 秒跳到 3.5 秒,这个差距在交互式产品里是致命的。所以如果你要做实时对话,要么把上下文控制在 2K 以内,要么接受首字等待。我的折中方案是:先用 2B 出一个快速的初稿响应,同时后台用 7B 生成完整回答,做完再替换——这种“先给反馈再给质量”的模式,用户感知好很多。
第三,批处理的单条等效速度会下降,但总吞吐上升。表里批大小 4 的单条等效是 14 token/s,比单条跑的 24 慢,但四条加起来就是 56 token/s,总吞吐翻了一倍多。做后台离线任务时,这个账一定要算清楚。
还有一个容易被忽略的指标:显存峰值。我用工具盯了一轮,7B 4-bit 在 8K 上下文下单请求的峰值到过 6.2G,批大小 4 的时候摸到 7.6G,非常接近 8G 上限。这意味着你在这张卡上不能再开别的显存占用程序了,浏览器开几个视频页面都可能让它崩。这是真实存在的边界,不是理论值。
4.2 中文能力到底什么水平
这个话题我要说得直白些,可能不太好听但有用。
日常问答没问题,但“聪明”程度有限。你问它“帮我写一封请假邮件”,它能写出来,格式规范、语气得体,不需要改太多。你问它“帮我分析这份合同里对我方不利的条款”,它会给你一堆看起来像模像样但经不起推敲的回答,容易漏掉真正的风险点。这是小模型的通病——它掌握了语言的形,但对复杂逻辑的把握不足。
中文语感明显好于同尺寸的一批老模型。我拿几个经典刁钻问题试了一下,比如“这句话有歧义吗”、“把‘差点没赶上’改成书面语”,它的处理比我想的干净。特别是标点使用和成语搭配,没有那种“翻译腔”。我猜跟训练语料的多语言配比有关,这一代模型在非英语语种上普遍下了功夫。
数字和专名是重灾区。我在一个抽取任务里让它从发票文本里提金额和日期,单独一两条基本对,连续几十条就开始出现“19.9 抄成 199”、“3 月 15 号抄成 5 月 13 号”这类错误。这不是量化的问题,FP16 下也有,是小模型在精确复制上的天然短板。如果你的业务对数字零容错,绝对不能让它直接输出,必须加规则校验层。
长文本能力有,但会“走神”。我塞了一份 6000 字的技术文档让它做摘要,前面的章节提炼得挺好,到后面明显开始糊弄,把不相关的内容混进来。这是注意力衰减的典型表现。我的应对办法是分段处理加二次汇总,而不是指望它一口气读完。
4.3 结构化输出:JSON 能不能信
这是我评测一个模型最看重的维度,因为工程落地十有八九要结构化输出。结论是:能用,但必须加约束。
直接说“请以 JSON 输出”,它会给你 JSON,但可能带一堆解释性文字,或者用 markdown 代码块包起来,或者键名跟你要求的对不上。我的做法是三层保险:
第一层,在提示词里给出完整的 schema 示例,不是描述字段,是直接给一个填好假数据的完整 JSON。小模型对“照猫画虎”的服从度远高于对抽象描述的理解。
第二层,要求它在输出前后加分隔标记,方便你截取。比如要求以固定标记开始、以固定标记结束,然后用字符串切分,比正则匹配大括号可靠得多。
第三层,拿到结果后必须校验,解析失败就重试一次,把错误信息也塞回提示词,很多时候第二次就对了。我做过统计,加这三层之后,JSON 解析成功率从七成多提到九成八以上,剩余失败的基本是内容本身超长被截断。
顺便说一个实测有效的小技巧:把温度设成 0。做结构化抽取时,采样带来的随机性纯属负担,温度和 top-p 都压到最低,输出的稳定性提升非常明显。这一点和创意写作场景完全相反,不要用同一套参数打天下。
5. 微调与二次改造:什么时候值得动手
5.1 先想清楚:你真的需要微调吗
我必须先劝退一批人。大部分“效果不好”的问题,不需要微调就能解决。我见过太多人一上来就要微调,结果折腾两周,效果还不如把提示词改好。判断标准很简单,按这个顺序排查:
- 提示词里有没有给出明确的输出格式和示例?没有就先补上,这一步能解决六成问题。
- 任务是不是超出了模型能力边界?比如让 2B 做多步逻辑推理,微调也救不回来,换大模型才是正解。
- 是不是领域术语太多、模型完全不认识?这种情况微调确实有价值。
- 是不是需要固定的输出风格或人设?这种用小样本微调或者直接靠提示词都行,看成本。
我的经验是:微调真正的甜点区是“风格对齐”和“术语注入”,而不是“提升智力”。你指望通过微调让 2B 拥有 70B 的推理能力,那是不可能的;你指望通过微调让它学会你们公司内部那一套产品命名和话术体系,这个非常可行,而且效果立竿见影。
还有个性价比更高的替代方案经常被忽略:检索增强。把领域知识做成向量库,每次问答先检索再拼进提示词。这条路不用训练、随时更新、可解释性好,在知识密集场景里往往比微调更划算。我现在的默认顺序是:提示词 → 检索增强 → 微调,只有在前面两条都撞墙时才动第三条。
5.2 LoRA 微调的参数取舍与数据准备
真要做微调,7B 这个量级我强烈建议用 LoRA 这类低秩适配方法,而不是全参微调。理由很实在:全参微调 7B 至少需要 40G 以上的显存(权重、梯度、优化器状态三份),个人根本玩不起;LoRA 只训练一小部分附加参数,显存需求直接降到十几 G,一张消费级卡加上梯度累积就能跑。
配置上我常用的起步值是这样的(框架不同参数名有差异,逻辑是通用的):
lora_config = { "r": 8, # 秩,越小参数越少,8 是最常见的起点 "lora_alpha": 16, # 通常设成 r 的 2 倍 "lora_dropout": 0.05, "target_modules": ["q_proj", "v_proj"], # 先只挂注意力层试试 "learning_rate": 2e-4, "num_epochs": 3, "batch_size": 1, "grad_accum": 8, # 等效批大小 8 }每个数字背后的逻辑我拆一下。
- 秩 r 设 8:这是最保守的起点。秩越大能表达的变化越复杂,但也越容易过拟合,而且参数一多,小数据集根本喂不饱。我试过 r=64,在只有几百条样本的情况下,训练损失降得很漂亮,验证集上直接崩了。
- 学习率 2e-4:LoRA 用的学习率通常比全参微调高一个量级,因为可训练参数少。这个值不是绝对的,跑起来之后盯住损失曲线,前 50 步损失不降就把学习率砍半。
- 只挂 q_proj 和 v_proj:注意力层的这两个投影对“行为风格”最敏感,先挂这两个看效果。如果欠拟合,再把 k_proj、o_proj 和 FFN 层加上,参数量翻几倍,但效果提升通常有限。这是典型的“先小后大”策略,别一上来全挂。
- 3 个 epoch:小数据集微调最容易犯的错是训太多。我一般训 2 到 3 轮就停,然后拿验证集看输出,出现“不管问什么都往训练集答案上靠”就是过拟合了,立刻回退。
数据准备比参数调优重要十倍。我的数据格式是标准的“指令 - 输入 - 输出”三段式,几百条高质量样本就能见效。这里几条硬经验:
第一条,样本质量远比数量重要。三百条人工精修的数据,效果超过三千条机器生成的数据。我踩过的坑是拿大模型批量生成训练数据,结果学生模型把老师的口癖和错误全学来了,输出变得又臭又长。
第二条,一定要留验证集。至少留 10%,而且在划分之前要打乱。我见过有人按顺序切分,结果训练集全是同一类问题,验证集全是另一类,训出来的模型只会一种回答。
第三条,输出要短。小模型的上下文和生成能力都有限,训练数据里的答案动辄上千字,训完之后它也学会了长篇大论,实际用起来又慢又啰嗦。把答案控制在三百字以内,模型的行为会干脆很多。
6. 踩坑记录与速查表
6.1 我这轮实测踩过的五个坑
坑一:直接用了默认上下文长度,结果第一句话就慢得离谱。有些引擎默认上下文开得很大,加载时预分配的缓存直接把显存吃掉一大半。我一开始没注意,7B 加载完只剩不到 1G 余量,随便说句话就爆。改成显式指定上下文长度,问题消失。教训:所有资源相关的参数都要显式写出来,别信默认值。
坑二:提示词里用了英文模板,中文输出变得很奇怪。我从旧项目里直接抄了一段英文的系统提示,模型倒是能听懂,但输出的中文带着翻译腔,句式很长。换成中文系统提示之后,表达立刻自然了。教训:小模型对提示词语言敏感,中文任务就用中文提示词。
坑三:把对话历史和当前问题拼在一起时,忘了加角色标记。多轮对话里如果只是把历史文本连起来,模型会分不清哪句是它说的。表现是它会重复自己上一轮的回答,或者把用户的话当成自己的话说出来。正确做法是用分词器的模板函数按角色组织,这一点在 2B 上尤其致命。
坑四:并发测试时把服务打挂了。我用压力测试工具直接开了 20 个并发,服务没报错,但响应时间从 1 秒涨到 40 秒,最后 OOM。原因是请求全在排队等 KV 缓存,把显存撑爆了。教训:这类本地服务必须自己做限流,并发数不要超过批大小的两三倍。
坑五:量化文件和引擎版本不匹配。用新格式的量化文件去喂旧版本引擎,有时候不报错,只是输出质量莫名其妙地差。我排查了两个小时才怀疑到版本上。教训:引擎和量化格式的版本对应关系要记在笔记里,升级引擎时把所有模型重测一遍。
6.2 常见问题速查
| 现象 | 最可能的原因 | 排查动作 |
|---|---|---|
| 加载时报显存不足 | 上下文预分配过大 / 量化档位过高 | 先把上下文降到 2048,再考虑换低一档量化 |
| 输出乱码或特殊符号 | 权重文件损坏 / 分词器不匹配 | 重新下载并校验文件大小,确认分词器同源 |
| 答非所问、自说自话 | 对话模板拼错 | 改用分词器自带的模板函数 |
| 长篇输出开始复读 | 重复惩罚过低 / 温度过低 | 重复惩罚调到 1.1 至 1.2,温度调到 0.7 左右 |
| 数字、日期频繁抄错 | 小模型精确复制能力不足 | 加后置规则校验,或换更高量化档位 |
| 长文本后半段跑偏 | 注意力衰减 | 改成分段处理再汇总,别指望一口气读完 |
| 服务响应越来越慢 | 请求堆积、KV 缓存被占满 | 加请求队列和超时,限制并发数 |
| 多轮对话丢失上文 | 历史被截断 / 未按角色拼接 | 检查上下文窗口预算,保留最近 N 轮 |
| 中文输出带翻译腔 | 系统提示用了英文 | 换成中文系统提示,给中文示例 |
| 批处理吞吐不升反降 | 批大小超过显存承受能力 | 从批大小 2 起逐步加,盯显存峰值 |
7. 我个人的一些实际体会
折腾这一轮下来,最大的感受是:小模型的胜负手不在模型本身,而在你怎么用它。
同一个 2B 权重,有人跑出来觉得“就这?还不如用在线接口”,有人能把它做成一个离线可用的合同条款检查器,差别全在工程细节上。提示词有没有给示例、输出有没有做约束、上下文有没有做预算、请求有没有做限流,这四件事做到位,体验能差出一个数量级。
另一个体会是关于选型的。我现在判断一个任务该用多大模型,不看“效果上限”,而看“错误的代价”。做内部工单分类,错了就错了,人工改一下,用 2B 完全够;做对外的合同摘要,错一个字都可能出事,那就得 7B 起步,还要加人工复核。模型大小应该由业务风险的容忍度决定,而不是由排行榜分数决定。
最后一个我想分享的小技巧:给自己搭一个固定的小评测集。我从自己的业务里挑了 50 条真实样本,覆盖短问答、长摘要、结构化抽取、多轮对话四类,每次换模型或者改参数,都跑一遍这 50 条,把结果记在一张表里。这个习惯让我避免了很多次“感觉变好了其实变差了”的误判。模型的输出太主观,光靠肉眼感觉,非常容易被最近一次的表现带偏。
至于要不要现在就跟进这个新模型,我的建议是:如果你手上有明确的本地化需求,值得花半天时间跑一遍;如果只是凑热闹,先把你的提示词和评测集整理好,比追新模型有用得多。