从今天觉醒,技术赋予每一个人数字生命
把 KV Cache 压到 FP4:BitDecoding-FP4 的算法-系统协同设计复盘
长上下文推理的真正瓶颈从来不是算力,而是 KV Cache 的容量与带宽。一个 128K 上下文的请求,KV Cache 轻松吃掉几十 GB 显存,decode 阶段每步都要把整段缓存从 HBM 里拖一遍——吞吐被显存带宽锁死,并发被显存容量锁死。4-bit 微缩放格式(NVFP4、MXFP4)加上原生 FP4 Tensor Core 看起来是答案,但过去一年实测下来,两条路都走不通:
- 系统先行的 FP4 方案:直接把 KV 做 vanilla 量化塞进 FP4 Tensor Core,带宽省了,精度崩了;
- 算法先行的 4-bit 方案(vector codebook 一系):精度守住了,但 codebook 查找的开销把 decode 吞吐打回原形,而且根本喂不饱 Tensor Core。

这就是 BitDecoding-FP4 要解决的断裂带:算法量化与系统加速之间的鸿沟。实测效果先摆出来:在 B300 上跑 Qwen3-4B 和 Qwen3-30B-A3B,峰值服务吞吐相比 BF16 KV 提升 2.4–3.0 倍;精度上 NVFP4 与 BF16 KV 差距在 0.5 分以内(MXFP4 在 1 分以内),且在 RL 训练 rollout 中能紧贴 BF16 KV 的轨迹。下面拆解它怎么做到的,以及我们复现时踩过的坑。
背景与痛点:误差预算花在了哪
FP4 只有 16 个可表示值,直接量化注意力链路,误差集中在三个位置,各有不同的失效模式:
- Query:单次 FP4 量化后,attention logit 相对误差高达 60%——softmax 前的分数已经面目全非;
- Softmax 概率:长上下文下概率分布极平,最大值可能只有 1e-3 量级,用 E4M3 做 scale 时直接下溢归零,注意力权重整行消失;
- Key 异常通道:token-wise scaling 下,个别 outlier channel 撑爆 block 的 dynamic range,其余值全部被压进最低量化档。
不解决的代价很直接:要么掉精度换不来速度,要么换来速度不敢上线。
方案设计:三个算法补丁 + 一个系统重构
设计原则只有一条:每个误差源用针对性的算法手段压掉,且手段本身必须与原生 FP4 MMA 兼容——凡是需要查表、反量化回高精度的路,一律不走。
- Dual-Residual Query Quantization:把 Q 拆成两个 FP4 张量 Q₁ + Q₂,logits = Q₁Kᵀ + Q₂Kᵀ。多一次 FP4 GEMM,仍然是 Tensor Core 原生路径,logit 误差从 60% 压到 1%;
- Underflow-Aware Probability Scaling:在选 scale 时就约束下溢边界,不让长上下文概率被 E4M3 舍成零;
- Outlier-Aware Key Quantization:static smoothing + Hadamard rotation + scale search 三连,在 token-wise scaling 下摊平 Key 异常值。
系统侧用转置 softmax 把概率量化融进 kernel(消除 softmax 瓶颈),并用 stacked query MMA 适配 paged KV cache,保证 Tensor Core 不断粮。
明确放弃的方案:vector codebook(查找开销与 Tensor Core 路径不兼容)、运行时 per-channel Key 量化(太贵)、P 矩阵保 FP8(省不了带宽还引入额外转换)。
核心实现
双残差 Query 量化(参考实现)
下面是可在任意 GPU 上验证趋势的 PyTorch 参考实现(算法级,非 kernel):
import torch
# E2M1 全部可表示值
FP4_LEVELS = torch.tensor([-6., -4., -3., -2., -1.5, -1., -0.5, 0.,
0.5, 1., 1.5, 2., 3., 4., 6.])
def fp4_quant(x: torch.Tensor, block: int = 16):
"""NVFP4 风格:block 内共享 scale,最近邻量化到 E2M1 网格。"""
B, D = x.shape
pad = (block - D % block) % block
x = torch.nn.functional.pad(x, (0, pad)).view(B, -1, block)
amax = x.abs().amax(-1, keepdim=True).clamp_min(1e-8)
q = x / (amax / 6.0)
idx = (q.unsqueeze(-1) - FP4_LEVELS.to(x.device)).abs().argmin(-1)
return (FP4_LEVELS.to(x.device)[idx] * (amax / 6.0)).view(B, -1)[:, :D]
def dual_residual_fp4(qt: torch.Tensor):
q1 = fp4_quant(qt)
return q1 + fp4_quant(qt - q1) # 第二级 FP4 吸收残差
torch.manual_seed(0)
Q, K = torch.randn(8, 128) * 2, torch.randn(4096, 128)
ref = Q @ K.T
e1 = (fp4_quant(Q) @ K.T - ref).abs().mean() / ref.abs().mean()
e2 = (dual_residual_fp4(Q) @ K.T - ref).abs().mean() / ref.abs().mean()
print(f"single: {e1:.2%} dual-residual: {e2:.2%}")
合成数据上能看到误差数量级的下降;真实模型张量上的 60%→1% 与论文口径一致。
异常值感知的 Key 量化
def hadamard_matrix(n: int):
H = torch.ones(1, 1)
while H.shape[0] < n:
H = torch.cat([torch.cat([H, H], 1), torch.cat([H, -H], 1)], 0)
return H / n ** 0.5
H = hadamard_matrix(128).to(K.device) # head_dim=128,2 的幂直接用
K_rot = K @ H # 旋转摊平 outlier channel
Q_rot = Q @ H # Q 必须同步旋转:(QH)(KH)ᵀ = QKᵀ
Hadamard 矩阵正交且自逆(归一化后 HH = I),旋转不改变注意力结果,只重新分配各通道能量,让 token-wise FP4 的 scale 不被单个 outlier 绑架,再配合 scale search 选最优缩放。
Kernel 侧:转置 softmax 融合
关键点是改变 softmax 的数据布局,让概率归一化与 FP4 量化在同一个 kernel pass 内完成,避免中间结果落 HBM;scale 选择逻辑里加下溢保护(小于 E4M3 最小正规格时提升 scale)。paged KV 上用 stacked query MMA 把多个 query 块拼进一次 MMA,掩盖 block table 寻址延迟。
效果验证
复现环境:NVIDIA B300(Blackwell Ultra),CUDA 13.x,PyTorch 2.9+。算法参考代码(上文)任意 GPU 可跑;端到端 kernel 需要原生 FP4 Tensor Core:
git clone https://github.com/OpenBitSys/BitDecoding-FP4.git
cd BitDecoding-FP4 && pip install -r requirements.txt
# 具体 benchmark 入口以仓库 README 为准
结论(dense + MoE,GQA + MLA 均覆盖):NVFP4 精度差 ≤0.5 分,MXFP4 ≤1 分;峰值吞吐 2.4–3.0×;RL rollout 轨迹贴 BF16。
踩坑记录:
- 概率整行归零变 NaN:长上下文下未做下溢保护时,softmax 输出量化后全零,归一化除零。排障方法:按行检查注意力概率和,定位到 scale 选择逻辑加 clamp。
- Hadamard 维度陷阱:head_dim 非 2 的幂(如 MLA 变体)不能直接构造,需要分块 Hadamard 或正交填充——padding 不保持正交性会引入静默精度损失,loss 不掉但长文本 eval 缓慢劣化。
- Scale 布局混用:NVFP4 block=16、MXFP4 block=32,paged cache 里 scale 张量 layout 弄混后输出"看起来差不多对",极难排查。务必先用小输入对拍 FP32 参考再上大模型。
边界与演进
硬性约束:依赖 Blackwell 及以后的原生 FP4 Tensor Core,Hopper/Ampere 上这套不成立(低 bit 模拟路径请看前代工作)。方案聚焦 decode 阶段,prefill 仍是计算密集的 BF16 领地。MXFP4 精度略逊于 NVFP4,硬件支持时优先后者。下一步值得关注的是与主流 serving 框架的深度集成,以及 FP4 KV 与低 bit 权重的协同设计——当缓存和权重都进 4-bit,长上下文服务的成本模型会被重写。
1513



被折叠的 条评论
为什么被折叠?



