ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

大模型推理优化实战:量化、蒸馏与模型选型指南

大模型推理优化实战:量化、蒸馏与模型选型指南 1. 推理优化到底在优化什么从显存墙到延迟账本很多人第一次接触推理优化脑子里冒出来的第一个念头就是把模型压小一点。这个直觉不算错但只对了一半。真正做过线上部署的人都知道推理优化本质上是在三个互相拉扯的指标之间找平衡点显存占用、单次推理延迟、输出质量。你压得越狠显存是省下来了但模型可能开始胡言乱语你追求极致延迟批处理规模就得缩小吞吐量又掉下去了。所以这一章我想先把优化对象这件事讲清楚不然后面聊量化、聊蒸馏都是空中楼阁。1.1 显存墙为什么模型加载就吃掉大半张卡先算一笔账。一个 7B 参数的模型如果权重以 FP16 存储每个参数占 2 字节光权重就是 7 × 10^9 × 2 ≈ 14GB。这还没算 KV Cache、激活值、框架自身的开销。实际跑起来一张 24GB 的卡加载完 7B 模型留给 KV Cache 的空间可能只剩 6~8GB。这就是所谓的显存墙——不是算力不够是显存先撑不住。KV Cache 这个东西特别容易被新手忽略。它的大小和序列长度、批大小、层数、注意力头维度都成正比。粗略估算公式是KV Cache ≈ 2 × batch_size × seq_len × num_layers × hidden_dim × dtype_bytes注意前面那个 2是因为 Key 和 Value 各存一份。你把这个公式代进去就会发现序列长度翻倍KV Cache 直接翻倍。所以长上下文场景下KV Cache 往往比权重还吃显存。这也是为什么后来会出现 PagedAttention、KV Cache 量化这些技术——它们针对的根本不是权重而是这块动态增长的内存。1.2 延迟账本首 token 和后续 token 是两笔账延迟这件事必须拆成两个指标看TTFTTime To First Token首 token 延迟和TPOTTime Per Output Token每 token 延迟。这两个指标的优化手段完全不同混在一起谈就是耍流氓。TTFT 主要受 prefill 阶段影响也就是模型处理你输入的那段 prompt。这段是计算密集型的矩阵乘法占主导所以它对算力敏感对显存带宽相对没那么敏感。而 TPOT 是 decode 阶段每生成一个 token 都要把整个权重过一遍这是显存带宽密集型的。这就解释了一个反直觉的现象有时候你把模型量化到 INT8TTFT 没怎么变但 TPOT 明显下降了——因为权重变小了从显存搬到计算单元的时间短了。我见过不少团队优化了半天只盯着总延迟看结果发现首 token 快得飞起后面生成慢如蜗牛用户体验依然很差。所以做优化之前先明确你的场景到底卡在哪一笔账上。对话类应用通常对 TTFT 敏感因为用户等着第一句话出来而批量生成类任务更看重 TPOT 和吞吐。1.3 吞吐与并发的取舍批处理不是越大越好吞吐量靠批处理堆上去这个道理大家都懂。但批处理有个隐藏成本显存占用随批大小线性增长尤其是 KV Cache。你 batch 开到 32KV Cache 可能就是 batch1 时的 32 倍。所以实际部署里continuous batching连续批处理这类技术才显得重要——它让不同请求动态拼批而不是傻等一个固定批次凑满。这里有个经验值可以参考在单张 24GB 卡上跑 7B 模型INT8 量化后batch size 通常能开到 16~32 而不会 OOM如果是 FP16可能 8 就到顶了。当然这跟序列长度强相关长上下文场景要往下调。我的建议是先用小 batch 跑通然后逐步加压盯着显存监控曲线找拐点而不是拍脑袋定一个数。提示优化前一定要建立基线。没有基线的优化都是玄学。至少记录三个数FP16 原始模型的 TTFT、TPOT、峰值显存。后面每做一步优化都拿这三个数对比。2. 量化把浮点数塞进更小的盒子里量化是推理优化里性价比最高的手段没有之一。它不需要重新训练模型改造成本低收益却立竿见影。但量化的水也很深INT8、INT4、GPTQ、AWQ、GGUF 这些名词堆在一起新手很容易懵。这一章我按原理—方法—实操的顺序把它捋一遍。2.1 量化的本质用精度换空间和带宽量化的核心思想一句话就能说清把原本用 16 位或 32 位浮点表示的权重和激活值用更少的位数表示。比如 INT8 就是 8 位整数INT4 就是 4 位整数。位数少了存储空间小了显存带宽压力也小了推理自然快。但这里有个关键问题浮点数的动态范围很大能表示从 1e-38 到 1e38 的值而 INT8 只能表示 -128 到 127 这 256 个整数。怎么把前者映射到后者靠一个缩放因子scale和零点zero point。公式大致是quantized round(real_value / scale) zero_point real_value (quantized - zero_point) * scalescale 决定了量化的粒度zero_point 保证了 0 能被精确表示这对 ReLU 这类激活很重要。听起来简单但难点在于权重里总有一些离群值outliers它们数值特别大如果为了照顾它们把 scale 调大那大部分正常值就被压到很小的整数区间里精度损失严重。这就是为什么早期朴素量化效果差而后来 GPTQ、AWQ 这些方法能work——它们都在想办法处理离群值。2.2 主流量化方法对比GPTQ、AWQ、GGUF 怎么选市面上主流的量化方法各有侧重我整理了一张表方便你按场景选方法量化对象典型位宽特点适用场景GPTQ权重4bit/3bit逐层量化用校准集补偿误差GPU 推理追求极致压缩AWQ权重4bit保护重要通道激活感知GPU 推理质量优先GGUF权重2~8bitllama.cpp 生态CPU/GPU 混合本地部署消费级硬件INT8 动态量化权重激活8bit框架原生支持改造成本低快速验证通用场景SmoothQuant权重激活8bit把激活的离群值迁移到权重需要激活量化的场景选型逻辑其实不复杂。如果你在 GPU 上跑追求压缩率和速度GPTQ 和 AWQ 是首选AWQ 在质量上通常略胜一筹但 GPTQ 生态更成熟、工具链更全。如果你要在消费级硬件上本地跑比如 Mac 或者只有 CPU 的机器GGUF 几乎是唯一选择llama.cpp 对它的支持无可替代。如果你只是想快速验证量化有没有效果直接用框架自带的 INT8 动态量化几行代码就能跑。2.3 实操用 Python 跑一次 INT8 量化光说不练假把式。下面这段代码演示如何用 PyTorch 对一个模型做动态 INT8 量化这是最省事的入门方式import torch from transformers import AutoModelForCausalLM, AutoTokenizer model_name your-model-path tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name, torch_dtypetorch.float16) # 动态量化权重转 INT8激活在推理时动态量化 quantized_model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, # 只量化线性层 dtypetorch.qint8 ) # 保存量化后的模型 torch.save(quantized_model.state_dict(), model_int8.pt) # 对比显存占用 def get_model_size(m): param_size sum(p.numel() * p.element_size() for p in m.parameters()) return param_size / 1024 / 1024 # MB print(f原始模型大小: {get_model_size(model):.2f} MB) print(f量化模型大小: {get_model_size(quantized_model):.2f} MB)跑完你会发现模型大小大概降到原来的 1/2 到 1/4。但要注意动态量化主要加速的是 CPU 推理在 GPU 上收益有限因为 GPU 对 INT8 的支持需要特定算子。如果你想在 GPU 上吃满 INT8 的红利得用 TensorRT 或者 vLLM 这类专门优化的推理引擎。2.4 量化踩坑精度掉了怎么办量化最怕的就是精度崩了。我踩过的坑里最常见的有这么几个第一个坑是校准集选得不对。GPTQ 这类方法需要一小批数据做校准如果你随便拿几条无关的文本量化后的模型在你的实际任务上可能表现很差。校准集应该尽量贴近你的真实使用场景比如你做代码生成校准集就该用代码。第二个坑是过度量化。4bit 已经很激进了3bit、2bit 虽然能跑但质量下降往往是非线性的——可能 4bit 只掉 1% 的效果2bit 直接掉 20%。我的经验是除非显存实在紧张否则 4bit 是性价比的甜点区。第三个坑是忽略了特定层的敏感性。有些层比如第一层和最后一层对量化特别敏感把它们保持 FP16其余层量化往往能挽回不少精度。这个技巧在 GPTQ 里可以通过配置实现。注意量化后一定要做评测不能只看 loss。用你的实际任务跑一批测试用例对比量化前后的输出质量。我见过太多人量化完直接上线结果用户反馈答非所问。3. 蒸馏让小模型学会大模型的手感如果说量化是压缩那蒸馏就是传承。它不改变模型的位数而是让一个小模型去模仿一个大模型的行为。这一章聊聊蒸馏的原理、变体和实操。3.1 知识蒸馏的核心软标签比硬标签信息量大传统训练用的是硬标签比如分类任务里一张猫的图片标签就是猫one-hot 编码非黑即白。但大模型的输出是概率分布比如它可能给出猫 0.9狗 0.07狐狸 0.03。这个分布里藏着大量信息——它告诉小模型这张图虽然主要是猫但有点像狗完全不像汽车。这就是软标签soft label的价值。蒸馏的损失函数通常是两部分加权一部分是学生模型和真实标签的交叉熵另一部分是学生模型和教师模型软输出的 KL 散度。公式大致是Loss α * CE(student, hard_label) (1-α) * KL(student_soft, teacher_soft)温度参数 T 在这里很关键。它把 softmax 的输出软化让概率分布更平滑小概率类别的信息也能被学生学到。T 一般取 2~10太小了软标签退化成硬标签太大了分布太平信息又被稀释。3.2 蒸馏的几种变体从 logits 到思维链最早的蒸馏只蒸馏 logits输出层的概率分布但大模型时代蒸馏的花样多了起来Logits 蒸馏最经典学生模仿教师的输出分布。适合分类、生成任务。特征蒸馏学生不仅学输出还学中间层的特征表示。适合有明确中间表征的任务。思维链蒸馏教师生成详细的推理过程学生学这个过程的每一步。这对推理类任务特别有效比如数学题。指令蒸馏教师对大量指令生成回答学生用这些问答对做微调。这是目前最实用的方式本质上是用教师模型造数据。我个人最推荐指令蒸馏因为它实现简单、效果稳定。你只需要准备一批 prompt让大模型生成回答然后用这些数据微调小模型。整个过程不需要访问大模型的内部结构黑盒也能做。3.3 实操用教师模型造数据微调学生模型下面是一个指令蒸馏的简化流程。假设你有一个强教师模型和一个待训练的小模型from transformers import AutoModelForCausalLM, AutoTokenizer import json # 第一步用教师模型生成训练数据 teacher_name teacher-model-path teacher AutoModelForCausalLM.from_pretrained(teacher_name, torch_dtypeauto, device_mapauto) teacher_tokenizer AutoTokenizer.from_pretrained(teacher_name) prompts [ 解释什么是梯度下降, 写一个 Python 快速排序, 如何优化数据库查询性能, # ... 准备几百到几千条 ] dataset [] for p in prompts: inputs teacher_tokenizer(p, return_tensorspt).to(teacher.device) outputs teacher.generate(**inputs, max_new_tokens512, temperature0.7) response teacher_tokenizer.decode(outputs[0], skip_special_tokensTrue) dataset.append({instruction: p, output: response}) with open(distill_data.json, w, encodingutf-8) as f: json.dump(dataset, f, ensure_asciiFalse, indent2) # 第二步用这批数据微调学生模型伪代码具体用你的训练框架 # student AutoModelForCausalLM.from_pretrained(student-model-path) # trainer SFTTrainer(modelstudent, train_datasetdataset, ...) # trainer.train()这里有几个实操要点。数据质量比数量重要1000 条高质量问答往往胜过 10000 条垃圾数据。温度设置要合理生成数据时温度可以稍高0.7~0.9增加多样性但太高会产生幻觉。要过滤教师模型的输出不一定都对最好加一层质量筛选比如用另一个模型打分或者人工抽检。3.4 蒸馏的边界学生不可能超过老师蒸馏有个天花板学生模型的能力上限受教师模型限制。如果教师本身在某个任务上就不行学生学不到东西。所以选教师很关键要选在你目标任务上表现强的模型。另一个常见误区是盲目追求小。有人想把 70B 蒸馏到 1B结果发现效果惨不忍睹。蒸馏的压缩比是有极限的一般来说学生模型参数量不低于教师的 1/10 比较稳妥。7B 蒸馏到 1.5B 是可行的70B 蒸馏到 7B 也常见但再往下就要谨慎了。还有个坑是灾难性遗忘。学生模型在蒸馏数据上微调后可能在通用能力上退化。解决办法是混合训练数据蒸馏数据里掺一部分通用语料保持模型的泛化能力。4. 模型选型没有最好只有最合适聊完量化和蒸馏最后落到选型上。这部分最考验经验因为参数表上的数字和实际表现往往有差距。我按几个维度来拆解。4.1 按部署环境选GPU、CPU、边缘设备部署环境是选型的第一约束。我把它分成三档GPU 服务器显存充足追求吞吐和延迟。这种场景下7B~70B 的模型都能跑量化用 GPTQ/AWQ推理引擎选 vLLM 或 TensorRT-LLM。vLLM 的 PagedAttention 对长上下文特别友好TensorRT-LLM 在 NVIDIA 卡上延迟最低。CPU 或消费级硬件显存有限甚至没有独显。这种场景 GGUF 格式 llama.cpp 是标配。模型规模控制在 7B~13B量化到 Q4_K_M 或 Q5_K_M能在 16GB 内存的机器上流畅跑。如果内存只有 8GB那就得上 3B 以下的模型。边缘设备比如嵌入式板子、手机。这种场景模型要极小1B 以下量化到 INT4 甚至 INT2。通常还需要专门的推理框架比如针对特定硬件的加速库。这类部署对模型结构有要求不是所有模型都能跑。4.2 按任务类型选通用对话、代码、数学推理不同任务对模型的要求不一样。通用对话看重流畅度和知识面7B~13B 的通用模型就够用。代码任务对模型的逻辑能力要求高通常需要专门的代码模型参数量建议 7B 起步。数学推理是最吃模型能力的小模型往往推理链条断裂建议 13B 以上或者用蒸馏了思维链的专用模型。这里有个反直觉的点模型大不一定在所有任务上都好。有些 7B 的专用模型在特定任务上能吊打 70B 的通用模型。所以选型前先明确你的核心任务别盲目追大。4.3 选型决策表一张表帮你快速定位我把常见场景和推荐配置整理成表场景硬件推荐模型规模量化方式推理引擎本地个人使用16GB 内存笔记本7BGGUF Q4_K_Mllama.cpp小型服务单张 24GB 卡7B~13BAWQ 4bitvLLM中型服务多张 24GB 卡13B~34BGPTQ 4bitvLLM/TensorRT高并发 APIA100/H100 集群70BFP16/INT8TensorRT-LLM边缘设备嵌入式板1B 以下INT4专用框架这张表是起点不是终点。实际选型还要考虑你的并发量、延迟要求、预算。比如同样是 7B 模型QPS 要求 100 和 QPS 要求 10硬件配置差好几倍。4.4 选型后的验证别信 benchmark信你自己的数据最后强调一点公开 benchmark 只能参考不能全信。很多模型在 MMLU、HumanEval 上分数很高但在你的实际任务上可能一塌糊涂。原因很多benchmark 可能被污染、模型的训练数据分布和你的场景不匹配、评测方式和你实际用法不同。我的做法是候选模型选出来后用自己的真实数据跑一轮评测。准备 50~100 条代表性测试用例人工或半自动打分对比几个候选模型的实际表现。这一步花不了多少时间但能避免上线后翻车。评测维度至少包括答案准确性、格式合规性、响应延迟、显存占用。提示评测时一定要用和生产环境一致的配置。你用 FP16 评测选出来的模型上线量化到 INT4表现可能完全不同。选型和量化要一起考虑。5. 把量化、蒸馏、选型串起来一个完整的落地思路前面四章分别讲了量化、蒸馏、选型但实际项目里这三件事是交织的。这一章我想给一个完整的落地流程把前面的知识点串起来。5.1 先定约束再谈优化很多人一上来就问用什么量化方法好这是本末倒置。正确的顺序是先明确约束条件。你的硬件是什么显存多大延迟要求多少并发多少预算是多少这些约束定下来可选空间就缩小了一大半。举个例子。假设你的约束是单张 24GB 卡、延迟 TTFT 500ms、并发 20、预算有限。那么 70B 模型直接排除13B 是上限。量化方式上FP16 的 13B 约 26GB超显存必须量化。INT8 约 13GB能跑但 KV Cache 空间紧张。4bit 约 7GBKV Cache 空间充裕并发能上去。所以结论是 13B 4bit 量化。这个推导过程比直接问用什么量化靠谱得多。5.2 优化顺序量化优先蒸馏按需在优化手段的排序上我的建议是先量化后蒸馏。原因很简单量化是无损改造不需要重新训练成本低见效快蒸馏需要造数据、训练、评测周期长成本高。先用量化把模型塞进硬件如果效果达标就收工如果量化后质量掉太多再考虑蒸馏一个小模型替代。当然也有例外。如果你的目标就是极致小模型比如边缘部署那蒸馏是必须的量化只是辅助。这种情况下顺序反过来先蒸馏出小模型再量化压缩。5.3 一个真实的优化案例复盘我拿一个实际项目举例。需求是在单张 24GB 卡上部署一个代码助手支持 8K 上下文并发 10TTFT 800ms。第一步基线测试。选了一个 7B 的代码模型FP16 加载显存占用 14GB8K 上下文下 KV Cache 约 4GB总共 18GB能跑但并发上不去batch4 就接近 OOM。TTFT 约 600ms勉强达标。第二步量化。用 AWQ 量化到 4bit权重降到约 4GBKV Cache 不变总占用约 8GB。batch 能开到 16并发问题解决。TTFT 降到约 400ms因为权重搬运量小了。质量方面跑了 100 条代码测试用例通过率从 82% 降到 80%可接受。第三步蒸馏优化可选。发现模型在一些复杂算法题上表现一般用更大的教师模型蒸馏了一批思维链数据微调后通过率回到 84%。这一步是可选的因为量化后已经达标蒸馏是锦上添花。这个案例的关键点是每一步都要验证不要一次性堆所有优化。先量化看效果不够再蒸馏这样能清楚知道每个手段的贡献。5.4 监控与迭代上线不是终点模型上线后监控比优化本身更重要。要盯的指标包括TTFT、TPOT、显存峰值、GPU 利用率、错误率、用户反馈。这些数据能告诉你优化有没有效果以及什么时候需要重新调整。我见过不少团队优化完就撒手不管结果流量涨上来后 OOM 频繁或者模型在新数据上表现退化。推理优化是个持续的过程不是一锤子买卖。建议至少每周看一次监控数据每季度重新评估一次模型选型——毕竟模型迭代很快半年前的最优解现在可能已经过时了。6. 那些文档里不会写的实操心得最后这一章我想分享一些踩坑踩出来的经验都是常规文档里不会提的。6.1 量化工具的版本兼容性是个大坑量化工具链的版本兼容性极差。你可能遇到 GPTQ 的某个版本和 transformers 的某个版本不兼容报一堆看不懂的错。我的建议是锁定版本别乱升级。用 conda 或 venv 建独立环境把量化工具、推理框架、模型库的版本都固定下来记录在 requirements.txt 里。升级前先在测试环境验证别在生产环境直接升。6.2 显存监控要盯峰值不是均值显存监控有个陷阱均值看起来很低但峰值可能已经接近 OOM。尤其是长上下文场景KV Cache 是动态增长的处理长输入时显存会突然飙升。所以监控要盯峰值并且留出 20% 的余量。我一般会在代码里加显存检查超过阈值就拒绝新请求或者降级处理避免整个服务崩掉。6.3 蒸馏数据的质量筛选比生成更费劲蒸馏的难点不在生成数据而在筛选数据。教师模型生成的回答里可能有幻觉、有格式错误、有重复。我一般会做三层筛选第一层用规则过滤明显错误的比如长度异常、包含特定错误标记第二层用另一个模型打分低于阈值的丢掉第三层人工抽检 5%~10%。这套流程下来数据量可能只剩原始的 60%但质量高很多。6.4 选型时别忽略推理框架的生态选模型的时候除了看模型本身还要看推理框架对它的支持程度。有些新模型刚出来vLLM 还没适配你只能用 transformers 原生推理速度慢好几倍。所以选型时优先选生态成熟的模型或者至少确认你的推理框架支持它。这个信息一般在框架的 GitHub 仓库或者文档里能查到。6.5 量化后的模型要重新测 KV Cache 行为量化改变的不只是权重还可能影响 KV Cache 的数值范围。有些量化方法对 KV Cache 也做量化这时候长上下文的表现可能和短上下文差异很大。所以量化后一定要测长上下文场景别只测短 prompt。我遇到过量化后短文本正常、长文本开始重复输出的情况就是 KV Cache 量化导致的。6.6 留一条回退路径任何优化都要留回退路径。量化后质量不达标能快速切回 FP16蒸馏后模型退化能切回原始模型。做法很简单模型文件保留多个版本配置里用开关控制加载哪个。这样出问题能分钟级回退而不是手忙脚乱重新部署。这套东西说起来都是常识但真到项目里能全部做到位的团队不多。我自己也是踩了无数次坑才养成这些习惯。推理优化这件事技术方案是一方面工程纪律是另一方面后者往往更决定成败。
返回列表