按长度分桶批处理替代逐条循环:小语言模型优化第三法
内容提要
小语言模型优化第三法为按长度分桶批处理:逐条推理受内存带宽限制效率低,普通批处理因填充浪费算力;按token长度排序后分批,各批仅填充至局部最大值,可将填充开销降至约8%,吞吐量提升近一倍。需左填充、只取末位logits,并核对结果与逐条推理一致。
延伸解读
为什么逐条推理是最大浪费
文章指出,批大小为1时,小模型受内存带宽限制而非计算限制:硬件为服务单个序列需将所有权重从内存中读出,算术单元大部分时间空闲。这在GPU和CPU上都成立,而0.5B模型最常运行在CPU上。批处理能将权重读取分摊到多个序列,但需注意填充带来的新浪费。
按长度分桶如何减少填充开销
真实文本长度呈长尾分布,若每批填充到全局最大长度,大部分计算是填充。按token长度排序后分批,每批只填充到局部最大值,可将填充开销降至约8%。文章实测显示,排序后批处理吞吐量提升近一倍,且与逐条推理结果一致。
左填充与位置编码的注意事项
必须设置padding_side='left',否则logits[:,-1,:]会落在填充token上,产生错误预测。左填充会移动绝对位置,但对Qwen2.5这类旋转位置编码模型无害,因为注意力只依赖相对距离。若模型使用可学习绝对位置编码,则需根据attention mask构建position_ids。
批处理与前缀缓存的组合风险
前缀缓存的批维度为1,若与批处理组合,需将每个key/value张量沿批维度扩展并在之后正确裁剪。文章建议谨慎操作,并务必用非批处理路径验证预测结果,不能假设两种优化能免费组合。
Q&A
小语言模型优化第三法是什么?它解决了什么问题?
第三法是按长度分桶批处理。它解决逐条循环推理时内存带宽受限、效率低的问题,同时避免普通批处理因填充到全局最大长度而浪费算力。
为什么逐条处理小语言模型效率低?
在批大小为1时,小模型受内存带宽限制而非计算限制:硬件需要为每个序列流式读取所有权重,算术单元大部分时间空闲,导致效率低下。
按长度分桶批处理具体怎么做?
先按token长度对数据排序,再分批,每批只填充到该批的局部最大长度。需使用左填充,只取最后一个位置的logits,并核对结果与逐条推理一致。
按长度分桶批处理能带来多少性能提升?
在示例中,处理600张工单从逐条的144.35秒(4.2项/秒)提升到79.60秒(7.5项/秒),吞吐量提升近一倍,填充开销降至约7.6%。
为什么必须使用左填充?
因为取logits[:, -1, :]时,右填充会使短于批次最大长度的行在-1位置取到填充token,产生错误预测;左填充保证索引-1是每个序列真正的最后一个token。
批处理时如何避免生成巨大的logits张量?
通过设置logits_to_keep=1(旧版为num_logits_to_keep=1)只请求最后一个位置的logits,避免默认返回所有位置的logit向量造成多GB张量分配和丢弃。
排序后批处理需要注意什么?
排序会打乱数据顺序,必须保留原始索引并将结果写回正确位置,否则会导致输入与预测错位,产生看似合理但错误的输出。
如何选择批大小BATCH_SIZE?
应通过测量而非直觉选择。吞吐量会先陡升后趋于平稳,超过饱和点后只会增加最长桶出现内存不足的风险。最佳值取决于硬件和长度分布,条件变化时应重新测量。
前缀缓存和批处理能直接组合使用吗?
需要谨慎处理。前缀缓存的批维度为1,跨批复用需沿该维度扩展每个键值张量并在之后正确裁剪。建议有意为之,并对照非批处理路径验证预测结果。
这些优化会让模型变得更聪明吗?
不会。这些技术只是优化调度和计算方式,不改变模型能力。每个优化都通过验证输出与慢速路径完全一致来确认,改变预测结果的优化不是优化而是回归。