Day 9·1 KV 也量化——q8 KV 把缓存与 decode 带宽压到一半 源码精读篇本文为源码/方法论精读无独立实测文中数字均引述仓库 docs 的板端实测记录一句话导读q8 KV把 K、V 从 f16 压到 INT8按每 token 每头定标量化逐字节算清缓存与 decode 扫描带宽省约一半的账并讲清 KV 为何敢量化、f32 为何只是调试探针。8-3 我们把 KV 的布局讲清楚了可一个残酷的事实还在KV 是模型里除权重外最肥的东西。8K 上下文 × 28 层 × 8 KV 头 × 128 维若用 f16 存 K、V 各一份光这一块就约 560MB——decode 每词还要把它们全扫一遍。今天把 KV 也量化成 q8看带宽怎么压到约一半。1. 知识点为什么 KV 敢量化权重能 Q4/Q8第 5 天KV 为什么不能量化 KV 的顾虑是“精度”但注意两个事实decode 只“读”KVK/V 一旦写入就只被乘与累加量化误差只影响当次注意力打分/加权不像权重误差会一层层传播叠加KV 每维都有独立的 scale引擎对每个 (token, head) 单独定标见下128 维共享一个 scale误差被锁在“一行的局部”不会累积。于是引擎默认把内存 KV 压成INT8q8每个元素从 2 字节f16降到 1 字节K、V 各一份直接砍半再配合 8-3 的“decode 扫全 KV”路径每词的 KV 读取带宽同比例减半另有更狠的--kv-q4模式K/V 再压一半约 1.65 倍更密Day 24 思路同源。2. 对应代码q8 KV 的存储与“一 token 一 scale”引擎在分配阶段默认走 q8vllm_safetensors.c 第 8208–8244 行/* Compressed KV cache: INT8 (near-lossless) by default; --kv-q4 switches * to the Q4_0 payload cache (~1.65x denser, decode dot without dequant). */ st-use_kv_q8 g_kv_q4 ? 0 : 1; ... st-k_cache_q8[l] alloc_kv_blocks_i8(n_blocks, bs, kv_dim, ...); st-k_scale[l] cf_calloc_guard((size_t)max_seq * nkv, sizeof(float), ...); /* 每 token 每头一个 scale */写入时按“每 token 每头”做 max-abs INT8 量化第 9466–9476 行if (st-use_kv_q8) { /* Per-token per-head max-abs INT8 K/V quantization (near-lossless). */ int8_t *kdst kv_row_i8(st-k_cache_q8[l], cl, kv_dim, st-kv_bs); int8_t *vdst kv_row_i8(st-v_cache_q8[l], cl, kv_dim, st-kv_bs); kv_quantize_per_head(kdst, vdst, st-k_scale[l] (size_t)cl * nkv, st-v_scale[l] (size_t)cl * nkv, st-k_buf, st-v_buf, nkv, hd); /* Also store in float cache */ /* fp32 镜像仍保留调试/对照用 */ memcpy(... k_buf, kv_dim * sizeof(float)); ... }逐字节算笔账2B 模型参数nkv8、hd128即每个 token 每层 K 与 V 各kv_dim 1024f16 KV K 1024×2B V 1024×2B 4096 B / token / 层 q8 KV K 1024×1B V 1024×1B 2048 B scale8 头 × (K、V 各一) × 4B 64 B 合计 2112 B / token / 层 → 4096 → 2112缓存字节与 decode 全量扫描带宽均 ×0.52省约 48%接近一半scale 的粒度是“每个 (token, 头)”——比“整层一个 scale”细得多这正是 q8 KV 敢自称 near-lossless 的原因9-3 会给实测误差。3. 改动后果关掉 q8 KV 试试--kv-q4 / 全 f32 的代价引擎把 KV 模式做成可切默认 q8near-lossless解码走 INT8 单遍9-2 主角--kv-q4切到 Q4_0 payload 缓存比 q8 再密 ~1.65 倍decode 点积免反量化直接吃 nibble全 f32 的 KV代码注释明确记为DEBUG 用途第 8210–8214 行曾用来排查 K 缓存损坏定位后恢复生产默认——f32 KV 不是给生产用的是调试探针。想亲眼看到“KV 模式影响带宽”连跑--bench-mixed之外的 decode 场景不现实无直接开关计时但可以换位验证——8-3 说过 decode 每词扫全 KVKV 从 4096B/token/层f16降到 2112Bq8后decode 的 KV 扫描带宽需求直接 ×0.52这正是基准报告里 8K decode 引擎能维持 ~137ms/词vs llama f16-KV 全扫 411ms的重要来源之一叠加 Day 10 的稀疏详见 9-2/10 天。4. 学员调试任务A 档板端动手跑引擎--kv-q4与默认模式各一次短生成同一 prompt记录输出 token 是否一致q4 KV 比 q8 更激进肉眼可看的场景值得留个心眼用 5-1 的解析脚本确认模型 KV 相关张量名结合本页算一遍“2B 8K 上下文 q8 KV 约多少 MB”8K×28 层×2112B≈?。B 档纯读源码读alloc_kv_blocks_i8/kv_quantize_per_head在 vllm_safetensors.c 内搜索画出“一个 token 的 K 行 → 1024 int8 8 个 float scale”的落盘/落存图。预期输出你能算清 f16→q8 KV 省多少字节、scale 为什么按“每 token 每头”、以及 f32 KV 为何只是调试探针。收尾本篇源码点名vllm_safetensors.cq8 KV 分配与开关第 8208–8244 行、per-head 量化写入第 9466–9476 行。开源仓库Kestrel-LLM (Gitee)源码可得双许可学习 / 学术研究免费下篇预告q8 KV 存好了decode 怎么读它才最省下一篇 9-2 进flash_attn_single_q_q8_neon——量化 Q、在线 rescale、一条路径扫完全部 KV。关键词q8 KV、KV cache、INT8 量化、带宽、decode上一篇Day 8·3 KV Cache按头存储vs按token存储内存布局下一篇Day 9·2 flash_attn_single_q_q8_neon——单 q 单遍扫全 KV