ADR 0009: KV 量化策略
日期: 2026-06-09 状态: Proposed — 待 P0 0.5 (Triton DCU FP8 跑通) + P2 0.4 (baseline 数字) 验证后改 Accepted Owner: 队员 B (vLLM) 主笔 + 队员 A (Kernel) review + 队长拍板 关联: - AGENTS.md 第 9/21 行: 允许 KV cache 动态量化 + activation 动态量化; 禁持久化量化 - spec §5.1 决策表 (block-size × KV 量化行) + §5.4 (FP8 格式选择) - ADR 0007 决策矩阵 (5×4 cells) + ADR 0008 block-size 假设 - Plan Stream B (Task 3B.1-3 KV 量化器)
Context
block-size × 量化粒度 × 数据格式三轴耦合, 20 个组合不可能全测; 需要先拍板:
1. 数据格式: FP8 FNUZ (CDNA3 限定) / INT8 (CDNA2 + CDNA3 都可) / bf16 (baseline)
2. 粒度: per-tensor / per-head / per-token / KIVI 2D / KVQuant outlier-aware
3. vLLM 接入点: 内置 --kv-cache-dtype flag vs 自定义 KV cache hook
4. DCU 特有坑: FNUZ vs OCP 不兼容; CDNA2 无原生 FP8; aiter FP8 路径稳定性
9 周预算 + 4 人 5-12h/周, 选"必做 3 + stretch 2" 而不是"全 FP8 重训练"路线。
Decision
必做 (集成日必上)
| 项 | 选择 | 理由 |
|---|---|---|
| 数据格式 (CDNA3) | FP8 E4M3 FNUZ (__hip_fp8_e4m3_fnuz) |
spec §5.4 已验; 比 INT8 动态范围宽, K/V 异常值容得下 |
| 数据格式 (CDNA2) | INT8 对称 per-head | CDNA2 无原生 FP8, INT8 是 aiter/FlashAttention-2 fork 都支持的稳定路径 |
| 粒度 (prefill 写) | per-token scale | prefill 阶段计算一次 scale, 摊薄开销 |
| 粒度 (decode 读) | per-head scale | decode 阶段每 token 算 scale 收益小开销大, per-head 跟 PagedAttention block 头维对齐 |
| vLLM 接入 | --kv-cache-dtype fp8 (内置) 优先; 不行再 monkey-patch vllm/v1/kv_cache_interface.py 的 KVCacheBlock 写路径 |
spec §5.1 决策 + AGENTS.md 边界 (不动 scheduler, 允许 hook) |
| 精度退路 | 关键 cache (首 256 token 或 system prompt) 留 FP16, KIVI 风格 2D 分组 | 关键 cache 占总 token < 10%, 留高精度是 Δ 兜底 |
Stretch (做出来加分, 不做不丢分)
- aiter FP8 KV 路径:
VLLM_ROCM_USE_AITER=1启 aiter FA + aiter quantize kernel - KVQuant 风格 outlier-aware: per-channel 阈值, 异常通道 FP16, 其余 INT4
CDNA2 vs CDNA3 分支
| 维度 | CDNA2 (gfx90a) | CDNA3 (gfx942) |
|---|---|---|
| FP8 KV | 不支持, 跳过 | FNUZ E4M3, 必做 |
| INT8 KV | 必做 (退路) | 备选 (FP8 失败时降级) |
| aiter FP8 | 不可用 | stretch |
| block-size 配合 | 16 (per-head scale 1 个 head ≈ 128B 量化参数) | 16 或 32 (per-head scale 摊到 32 token) |
vLLM 0.18.1 接入点
- L1 (首选):
vllm serve --kv-cache-dtype fp8 ...(vLLM 0.18.1 已支持auto/fp8/int8) - L2 (回退): monkey-patch
vllm.v1.kv_cache_interface.KVCacheTensor的copy_from_blocks/copy_to_blocks, 写自定义 quant/dequant hook, scale 存 per-block metadata - L3 (stretch):
VLLM_ROCM_USE_AITER=1+ aiter 自带 FP8 KV cache kernel
不做什么 (显式排除)
- 全 FP8 重训练 / QLoRA 风格预量化 — 赛题禁止持久化量化
- per-tensor scale — 精度损失太大, 27B 模型 outlier 集中在少数 head/channel
- INT4 KV — 27B 上精度风险 > 收益, 留作 stretch 都不必做
Consequence
| 维度 | 影响 | 缓解 |
|---|---|---|
| 精度 Δ | FP8 per-head 在 LongBench 上历史报告 Δ < 1.5%, KIVI 关键 cache 留 FP16 进一步压到 < 1% | OpenCompass Δ > 3% 触发回退到 INT8 |
| 显存收益 | FP8 比 bf16 节省 ~50% KV cache 显存; 27B + 32k context 下 HBM 占用降 ~6 GB | 留 1 GB buffer 给 quantization scale 元数据 |
| 解码速度 | FP8 dequant 加 1 个 kernel; per-head scale 在 decode 阶段不增加 per-token 开销 | P3 末 bench 验证 SLA 不破 (TTFT/TPOT P99 ≤ Baseline × 1.5) |
| DCU 风险 | FNUZ 与 OCP 不兼容 — 上游 PyTorch / vLLM 默认 OCP 路径会 crash 或静默错 | 启动时 assert torch.float8_e4m3fnuz == __hip_fp8_e4m3_fnuz; P0 0.5 最小 case 跑通 |
| 工程成本 | 队员 B vLLM patch 估 8-12h (P3 Stream B); L1 flag 路线只需 1-2h 验通 | 优先 L1, L2/L3 仅在 L1 失败时启用 |
验证清单 (P0-P2 期间)
- P0 0.5: 最小 Triton FP8 store + load 跑通 on DCU (gfx942)
- P2 2.2: bf16 baseline 数字到位 (3 档 50 prompts/档)
- P3 3B.1:
--kv-cache-dtype fp8端到端跑通 on DCU, OpenCompass Δ < 3% - P3 3B.2: per-head scale 实测 8k-16k 档 SLA 不破
- P3 3B.3: 关键 cache FP16 (KIVI 风格) Δ 改善验证
- P3 集成日: 锁进 src/kv_quant/config.py 默认值