21 · 收益区间实测界定:KV 分页与 L3 驻留为何被判「不做」
一句话 :按原计划,KV 复用迁移到 GPU 后还剩两站(P2 KV 分页、GPU 侧 L3 分层驻留),两者都预设「GPU 显存是瓶颈」;本篇先把这个前提量化,结论是不做 ------并写清什么条件下结论会翻转。 前置 :建议先读 第 19 篇 与 第 20 篇(KV 复用迁移的上下篇)。 环境 :x86-64 + NVIDIA RTX 5090(32.6 GB)· Qwen3-30B-A3B q4local ·
max_seq=8192。
状态:⛔ 实测门禁未通过 → P2 与 GPU 侧 L3 驻留均不作(已入档)。
一、问题与结论
- 门禁先于实现:P2 的价值全部来自「回收镜像里没被写入的部分」。所以先量这个上界,量出来不划算就不写代码 ------ 而不是先实现块表再说。
- 镜像单槽 = 415,236,096 B ≈ 396 MiB (
nl·max_seq·(2·kv_dim + 8·nkv),本机 30B-A3B)。 - 最重档峰值只用到 60.2% :
memory.total = 32607 MiB,峰值used = 19625 MiB→ 余量 12982 MiB。 - 判定 :分页可回收的上界 (= 镜像未写入部分)≤ 396 MiB/槽,占总量 1.2% ;而余量有 12.98 GiB。显存不是瓶颈 ⇒ P2 不作 。GPU 侧 L3 驻留依赖同一瓶颈假设 ⇒ 同样不作。
- 边界必须一起写 :结论绑定「32.6 GB 卡 + ~18 GiB 权重常驻 +
max_seq=8192」。换小显存卡 / 更大模型 / 把max_seq抬到 40960,需重测后再议。 - 按「精度口径」这条纪律,这是 x86/RTX 5090 口径,不可读作 RK3588 结论。
二、背景:为什么这两站需要先过门禁
迁移方案里 P2 的目标写得很直白:「长上下文显存收益」 。也就是说,它并不改吞吐、不改数值(分页只是把「连续偏移寻址」换成「块表两步寻址」),唯一的收益是显存峰值下降。
于是这段投入是否值得,完全取决于一个可测量的事实:显存是不是瓶颈。如果余量本来就有十几 GiB,那么:
- 收益上界 = 镜像未写入的部分(上下文越满越少,满窗时为 0);
- 成本 = 改三个 attention 内核的行寻址 + 与
VLLM_CUDA_GRAPH图捕获的组合验证(图里固化的是镜像基址,改寻址后必须重验开/关两种状态)。
这正是「收益区间实测界定」这条方法论原则要挡的情况:先拿到收益上界,再决定是否付实现成本。
三、核心机制:显存口径怎么定
要判断「够不够」,先得知道本机显存是怎么被留出来的。设备侧权重缓存(wcache)的默认预算取自「空闲显存的一半,上限 24 GiB」(src/npu/cuda/vllm_cuda_kernels.cu):
c
/* 文件名:src/npu/cuda/vllm_cuda_kernels.cu,vcuda_dev_create() */
if (s->budget == 0) {
size_t freeb = 0, totb = 0;
if (cudaMemGetInfo(&freeb, &totb) != cudaSuccess) {
freeb = prop.totalGlobalMem / 2;
}
/* Default: half of the free device memory, capped at 24 GiB. ... */
s->budget = freeb / 2;
const size_t cap = (size_t)24 << 30;
if (s->budget > cap) s->budget = cap;
if (s->budget < (size_t)256 << 20) s->budget = (size_t)256 << 20;
}
也可以用环境变量强制覆盖(src/npu/cuda/vllm_cuda.c):
c
/* 文件名:src/npu/cuda/vllm_cuda.c */
size_t budget_mb = cfg ? cfg->wcache_mb : 0;
{
const char *e = getenv("VLLM_CUDA_WCACHE_MB");
if (e && e[0]) { long v = atol(e); if (v > 0) budget_mb = (size_t)v; }
}
峰值大头是权重与预加载,而不是 KV;wcache 条目数的期望值也与 MoE 结构有关(src/main.c):
c
/* 文件名:src/main.c */
/* wcache 条目期望值:稠密每层 7 single + 2 fused。MoE 层的 gate/up/down
* 与 fused GU 走独立专家缓冲(d_moe_*),不占 wcache 条目,故每层只有
* q/k/v/o + fused QKV = 5 条。两者都再加 1 个 lm_head。 */
const int want_wc = cfg->n_layers * (cfg->is_moe ? 5 : 9) + 1;
3.1 镜像几何
text
模型: Qwen3-30B-A3B-q4local (config.json)
num_hidden_layers = 48 num_key_value_heads = 4
head_dim = 128 → kv_dim = 4 × 128 = 512
运行时: [STREAM] vqf_load OK: dim=2048 layers=48 heads=32 kv=4 ffn=98304 vocab=151936 max_seq=8192
镜像单槽 = nl × max_seq × (2 × kv_dim + 8 × nkv) # 2 个 int8 K/V(1B)+ 2 个 f32 scale(4B)
= 48 × 8192 × (2×512 + 8×4)
= 48 × 8192 × 1056
= 415,236,096 B ≈ 396 MiB (占 memory.total 的 1.2%)
按 VC_KV_SLOTS = 8 全部占满算:≈ 3.09 GiB(9.5%)。但只有真正做 append 的 state 才分配,所以实际开销正比于并发 state 数。
四、实测数据
4.1 峰值显存
用 500 ms 采样的 nvidia-smi 盯最重档(权重驻留 + 全 GPU decode + 位级档,实测):
text
$env:VLLM_CUDA_DECODE=1; $env:VLLM_CUDA_INFER=1; $env:VLLM_CUDA_FORCE_CPU=1; $env:VLLM_CUDA_L2EXACT=1
.\vllm_kestrel_x64.exe --cuda --model <30B-A3B-q4local> --stream-test --stream-n 4
memory.total = 32607 MiB
idle used = 936 MiB
peak used = 19625 MiB (60.2%) ← 500 ms 采样
→ 峰值余量 = 12982 MiB
(VLLM_CUDA_INFER=1 的权重缓存预算是「空闲显存的一半」≈ 15495 MiB,实测 289/433 个权重驻留、设备缓存 1845.8 MB ------ 即峰值的大头是权重与预加载,不是 KV。)
4.2 结论表
| 量 | 值 | 占总显存 |
|---|---|---|
| 镜像单槽 | 396 MiB | 1.2% |
| 8 槽全分配 | 3.09 GiB | 9.5% |
| 分页可回收上界(镜像未写入部分) | ≤ 396 MiB/槽(满窗时为 0) | ≤ 1.2% |
| 峰值余量 | 12982 MiB | 39.8% |
收益上界 ≤ 1.2%,余量 39.8% ⇒ 不是瓶颈。
五、边界:什么时候这个结论会翻转
写「不做」时必须同时写下边界,否则这条判定会在别的机器上被误用:
| 变量 | 本机口径 | 翻转子 |
|---|---|---|
| 显存 | RTX 5090 32.6 GB | 更小的卡(如 24 GB 及以下) |
| 权重常驻 | ~18 GiB(30B-A3B q4) | 更大模型 / 更高精度权重 |
max_seq |
8192 | 抬到 40960 → 镜像单槽 ≈ 1.98 GiB |
| 并发 state | 1--4 | serve 大 batch 同时开 CUDA → 槽位数 × 396 MiB |
也就是说:当「镜像总占用」相对「余量」不再是小量时(经验上 >10%),这道门就该重测。 另外,GPU 侧 L3 驻留的价值是「省显存换更长上下文 / 更高并发」,与 P2 依赖同一个前提,所以本判定一并覆盖它。
口径纪律:任何 GPU 数字不得当作 RK3588 的声明。本篇全部为 x86 + RTX 5090 实测,不能外推到 ARM 板端。
CPU 对照(迁移前基线)
- CPU 参考:CPU 侧无对应实现(GPU 显存口径)------ 迁移前的 CPU 引擎不存在 device 显存预算这一对象。
- 迁移要点 :无迁移(判「不作」):镜像单槽 ≈396MiB(占总量 1.2%)、峰值余量 12.98GiB ⇒ 显存非瓶颈;写清翻转边界(更小卡 / 更大模型 /
max_seq抬到 40960)。 - 真机验证 :未独立复现:需 30B-A3B + 500ms 采样
nvidia-smi峰值显存(max_seq=8192)。
六、小结
- 预注册门禁要可量化。如果门禁只写「显存是否瓶颈」,那永远可以论证两边;写成「量化镜像占用 vs 峰值余量」之后,结论是一张表就能定的。
- 收益上界比收益估计更省事。不需要实现块表来测「能省多少」------ 结构上界就是「镜像里没被写入的部分」,一行乘除即可。
- 「不做」也要入档,并写边界 。判否项同样是结论;不写边界,下一个机器上就会有人拿它当依据。(本系列 第 12 篇、第 13 篇 的三值量化、Tiling、Tensor Core 判否也是同一处理方式。)
- 同一个技术换硬件就可能翻转 。CPU 的 L3 收益来自 RAM 紧张(v2 方案实测 KV 底座 2.35 GB / RSS 2.66 GB,收益是「单层档」量级的降幅),GPU 上显存宽松 → 不成立。不照搬跨硬件结论是「收益必须实测界定」这条原则的核心要求。
相关篇目:第 12 篇、第 13 篇、第 19 篇、第 20 篇 源码与配套资源:本仓库 gitee.com/pei-xiaogua...; CPU 推理源码 gitee.com/pei-xiaogua...