事情本身不复杂,线索都在日志里。自己写的推理脚本把一个 batch 内所有样本 pad 到最长 2048,短样本其实只有 80 token,实测吞吐比逐条跑还低 40%,GPU 利用率看着很高,其实都在算填充。
第一反应是「batch 内长度差异越大浪费越多」,验证下来不成立。
排查方向其实有限,先把可能性排一排。填充位置的注意力和前向计算照常执行。也可能是有效 token 占比低,利用率数字被撑高。
少数情况下是没有按长度分桶就直接组批。
动手时先统计一个批次里的长度分布
python -c "print([80,120,2048,96])"
随后按长度分桶再组批
python -c "print('buckets 128/256/512/1024/2048')"
接着用 attention_mask 屏蔽填充位
python -c "import torch;print(torch.ones(2,4,dtype=torch.long))"
末了对比分桶前后的每秒有效 token
python bench_throughput.py --bucketed --duration 60
那几天正好在做别的变更,先把时间线对齐才敢动手。
恢复的判定其实很直接。每秒有效 token 提升三成以上,总耗时缩短,输出内容与不分桶时逐字一致。
分桶会在桶边界引入等待,桶越细收益越大但调度越复杂,先粗分五档看效果。报错文案容易误导,真正的原因往往在相邻那一层的日志里。
