0.8MB 跑通 Qwen|第 11-3 篇:推理引擎里 KV 缓存的一生——内存、容量与淘汰

简介: 本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎开发,实测RK3588平台流畅运行Qwen3-VL-2B/8B等多版本。本文详解KV缓存“出生—续用/覆盖—死亡”三段式生命周期,揭示预分配、逻辑覆盖、进程级销毁本质,破除“命中即加速、淘汰即释放”认知误区。(239字)

系列:《0.8MB 跑通 Qwen:从零实现 ARM 零依赖纯 C 推理引擎》(30 天 × 90 篇) | 适配模型:Qwen3-VL-8B-Instruct(千问3_VL_8B_Instruct)· Qwen3-VL-2B-Instruct · Qwen3-30B-A3B | 测试设备:RK3588(4×Cortex-A76 + 4×Cortex-A55,aarch64)

系列总纲:《0.8MB 跑通 Qwen》30 天 90 篇 · 总纲(阿里云社区)

上一篇:11-2《前缀缓存键:怎么知道"一样"》 | 下一篇:第 12 天《磁盘 KV(L3 与跨进程恢复)》

真机实测通过:本文实验已在 RK3588 板端实测完成(2026-09;方法学与原始记录见仓库 docs 与《实验脚本》目录)

一句话导读:缓存的一生:推理引擎的 KV 区是加载时一次性预分配的内存,命中只是续用、不命中只是覆盖、进程一死全没——本篇沿"出生、续用或覆盖、死亡"三段生命周期,回答 KV 占多少内存、何时被淘汰、能否指望它救断电。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、KV cache、生命周期、内存淘汰、预分配

导语:KV 缓存的一生其实只有三个状态:模型加载时按 max_seq 一次性预分配,请求命中只是续用、不命中只是逻辑覆盖(内存并不还给 OS),进程一死全没。本篇沿出生、续用或覆盖、死亡三段,回答 KV 占多少内存、何时被淘汰,以及为什么进程内缓存救不了断电。

11-1、11-2 讲清了"命中时多快"。今天问三个更实在的问题:那些 KV 行占多少内存?不命中的时候它们去哪了?能不能指望它救断电? 答案一句话:KV 区是加载时一次性预分配的内存,命中只是"续用",不命中只是"覆盖",进程一死全没。

1. 知识点:KV 缓存的"生命周期"其实只有三个状态

把 KV 区当作一个对象,它的一生只有三个阶段:

  1. 出生(模型加载时):按 max_seq(本引擎 8192)一次性分配好整个 KV 区。Qwen3-VL-2B 是 28 层、q8 KV 每 (token, 层) 2112 字节(Day 9-1 的量),整个区 = 2112 × 28 × 8192 ≈ 484 MB——请求还没来,这笔内存就已经占下了(引擎启动日志的 max_seq=8192, kv_bs=32 就是它的出生证明);
  2. 续用或覆盖(每次请求):请求来了,11-1 的 ist_reset 二选一——命中就 cache_len[l] = keep(续用前段),没命中就 cache_len[l] = 0(逻辑清空,等待被新一轮 prefill 覆盖)。注意是"逻辑清空":内存没有还给 OS,旧的 KV 字节还躺在原地,只是从 cache_len 这个"指针"上解绑了;
  3. 死亡(进程退出):只有 st_qwen_inference_free / 进程退出才真正释放。所以进程内缓存救不了断电、救不了重启——那需要把 KV 落盘(Day 12 的磁盘 KV)。

推论(都反直觉但真实):

  • 命中不是"分配",是"跳过写入":命中和不命中,RSS 几乎一样——KV 区早就分配好了;
  • 淘汰不是"删除",是"下次覆盖":没有 LRU、没有逐出器。旧缓存被淘汰的唯一方式是下一个请求用新的 KV 把它盖掉;
  • 容量上限 = max_seq:KV 区不会超过 8192 token 的窗口,超长 prompt 在 serve 层就被钳制(DISKKV_MAX_TOKENS 8192 是 Day 12 的主题之一)。

2. 对应代码:生命周期在哪儿实现

出生:推理状态初始化时按 max_kv_slots(= max_seq)分配各层 KV(引擎启动日志"inference state ready (max_seq=8192, kv_bs=32)")。KV 区字节数的算法见 Day 9-1(q8 2112 B/(token·层)),可以自己在引擎日志里对着 max_seq 复核。

续用/覆盖:11-1 的 ist_reset(vllm_server.c 第 405 行起)。命中分支只改 cache_len/seq_len(第 412–413 行),不碰内存;未命中分支把 cache_len 清零(第 420–421 行)——两分支都没有 malloc/free,这就是"预分配 + 覆盖"的代码证据。

谁还记得上一轮:ctx->last_ids(vllm_server.c 第 1338–1349 行在每次请求成功后把"prompt+生成"的 token 序列存下来)——它和 cache_len 一起,构成"缓存键 + 缓存状态"两件套。

观察窗:管理面 /admin/api/status(vllm_admin.c 第 363 行起)把引擎活着的证据摊开:busy(忙不忙)、active/queued(几个请求在跑/在等)、total_requests(活过多少请求)、rss_mb(本进程内存,来自 /proc/self/status,第 70 行的读取器)、mem_avail_mb(板子还剩多少内存)。想亲眼验证"KV 预分配、命中不涨内存",就用它。

3. 改动后果:换了个话题,缓存键立刻失效——板端对照

口径:RK3588 / Qwen3-VL-2B / 2026-09 / serve 同进程贪婪(前缀复用默认开)。

实验:同进程连发两个"完全不同上下文"的长请求

请求 prompt 内容 prefill token 数 prefill 耗时 [KV-PREFIX] 命中?
R1:长事实文 + Alice 问题 事实填充 + 提问 2044 46.30 s 无(首轮无缓存可比)
R2:完全不相关的 fox 长文 + 提问 另一主题长文 4324 167.22 s 无(LCP < 16)

判读:

  1. 换话题 = 缓存键失配:R2 与 R1 的 token 序列从头就不一样(kv_lcp 返回 <16),keep=0,ist_reset 走清零分支——旧缓存被逻辑清空,R2 全量重算;
  2. R2 的 prefill 代价和它的长度成正比:4324 token 花了 167.22s(其中注意力占 63.3%),等于把 11-1 省下的钱原样又花了一遍——这不是 bug,是"前缀复用只在话题连续时才有效"的边界:真实多轮对话恰好是连续的,但换人/换任务/后台批处理时,这个优化帮不上忙;
  3. 对照 11-1 那组命中数字(2044→31 token):同一个优化,命中时省 27×,不命中时一分不省——前缀缓存不是"加速器",是"去掉重复劳动的加速器"。

实验:RSS 观察(KV 预分配的实证)

同一台板子,/admin/api/status 的 rss_mb 读到的实测值:

时刻 rss_mb(/admin/api/status) 说明
模型加载完、尚未请求 2407 权重 mmap 未触页;KV 区已预分配
R1(短问答)之后 3336 第一次前向把权重触页进 RSS(+929 MB)
R2(复用命中)之后 3361 只 +25 MB(增量 KV 行 + 输出缓冲)

R1→R2 复用命中的那几秒,RSS 只涨了 25 MB——因为 KV 区是"出生"时就买断的,命中只是续用。真正让 RSS 从 2407 跳到 3336 MB 的是模型权重的首次触页(VQF mmap 懒加载,Day 3 的机制),不是 KV。

4. 学员调试任务

  • A 档(板端动手):复刻实验二:加载后 curl -s http://127.0.0.1:PORT/admin/api/status | jq '.rss_mb,.mem_avail_mb,.busy,.total_requests',发一短一长两个请求,再 curl 一次,记录 rss 涨跌并解释每一项变化来自权重触页还是 KV 分配。再复刻实验一(换话题),确认日志里没有 [KV-PREFIX]。
  • B 档(纯读源码):读 ist_reset 两分支(vllm_server.c 405–438),回答:① 命中与未命中分支的 cache_len 各被设成什么?② 为什么两分支都不释放内存?③ 若你把 max_seq 调小一半重编,KV 区内存怎么变(用 Day 9-1 的 2112 B 公式算)?

预期输出:你能画出 KV 缓存"出生(预分配)→ 续用/覆盖 → 进程死"的一生,并解释"为什么进程内缓存救不了重启"。

收尾

  • 本篇源码点名:vllm_server.c(ist_reset 405–438、last_ids 1338–1349)、vllm_admin.c(/admin/api/status 363 行起、rss_mb 405–406、/proc 读取器 70 行)、main.c(max_seq 默认与 --no-prefix-kv)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:进程内缓存再快,进程一崩/一重启就清零。Day 12 上磁盘 KV(--disk-kv):把 KV 快照落盘、重启后按 LCP 找回——复现仓库报告里那个"13.1× 跨进程恢复"。
相关文章
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 7-2 篇:推理引擎的混合精度路由——flags 决定谁用 Q8、谁用 Q4
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实现实测。核心创新是混合精度路由:prefill阶段用Q8保精度,decode阶段用Q4省带宽,通过flags比特位动态调度,兼顾速度与质量。(239字)
|
1天前
|
编解码 应用服务中间件 API
# 0.8MB 跑通 Qwen|第 19-3 篇:视频帧与媒体模块——推理引擎默认不启用的 H.264 独立模块
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型(2B/8B/30B),在RK3588上实测。视频支持分两层:帧序列路径已落地;MP4/H.264解码为独立未完成模块,API清晰、默认不构建、不演示未验证功能,体现严谨工程边界。(239字)
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 13-2 篇:草稿 + 验证——推理引擎里 spec decode 的实现
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上运行Qwen3-VL多模态模型;详解speculative decode实现——基于n-gram草稿、批量验证与无损回放,确保输出位级一致,兼顾 correctness 与工程可控性。(239字)
|
1天前
|
缓存 C语言 内存技术
0.8MB 跑通 Qwen|第 8-2 篇:在线 softmax——推理引擎的注意力为什么不能先算完 e^x 再除
本系列手搓0.8MB零依赖纯C推理引擎,适配Qwen3-VL多模型,在RK3588(aarch64)真机实测。本文详解softmax溢出陷阱:fp32下exp&gt;88.7即inf,剖析朴素/稳定/在线三版实现,揭示“减max”数学等价性与在线rescale的流式优势。(239字)
|
1天前
|
机器学习/深度学习 区块链 C++
0.8MB 跑通 Qwen|第 10-1 篇:大模型推理的长上下文为什么必须稀疏——O(n²) 的注意力 vs O(n·k) 的"只看该看的"
本系列手搓0.8MB纯C推理引擎,零依赖跑通Qwen3-VL多模型。本文聚焦长上下文瓶颈:RK3588实测显示,注意力耗时占比从18%飙升至71%。提出块稀疏方案——将O(n²)注意力压至O(n·k),实现每词成本与上下文长度无关,为端侧大模型长文本推理提供关键路径。(239字)
|
1天前
|
NoSQL C语言 C++
0.8MB 跑通 Qwen|第 15-1 篇:推理引擎入口架构——参数 → 分发(serve/自检)→ 退出
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测适配Qwen3-VL-2B/8B及30B-A3B模型,在RK3588板端完成真机验证。本文详解`main()`入口如何通过命令行参数分发至serve、离线转换或自检三种命运,并用GDB抓取真实调用链,厘清初始化与退出逻辑。(239字)
|
1天前
|
人工智能 开发者
阿里云Token Plan个人版:四个版本区别对比、费用价格、Credits计费用量及问题解答FAQ
阿里云Token Plan个人版含Lite/ Essential/Standard/Pro四档:Lite(¥39,1.15万Credits)适合轻量使用;Essential(¥79)用量翻倍;Standard(¥139,推荐)起赠Harness权益;Pro(¥499)适配高并发与海量调用。权益、额度、并发数逐级提升,详见官网。
|
2天前
|
JSON Java Linux
0.8MB 跑通 Qwen|第 3-3 篇:推理引擎的零依赖自研 util——"不引第三方也能活"的边界感
本文实测验证“零第三方依赖”的务实边界:OS已支持的(如Linux原生UTF-8路径)仅薄封装为宏;OS缺失且轻量关键的功能(FNV-1a校验、小端memcpy、平台工具宏)才自研。RK3588真机验证(2026-09),代码简洁、可移植、无冗余。
0.8MB 跑通 Qwen|第 3-3 篇:推理引擎的零依赖自研 util——"不引第三方也能活"的边界感
|
3天前
|
移动开发 编译器 Linux
0.8MB 跑通 Qwen|第 2-2 篇:推理引擎的平台层——一个头文件守住全部平台契约(vllm_platform.h)
本文实测于RK3588(2026-09),提出“平台层契约”设计:将NEON、mmap、绑核等平台依赖统一收口至`vllm_platform.h`,通过`ST_HAVE_NEON`等宏提供唯一真相,配合`#error`门闩实现错误前移——有守卫仅报1行错,无守卫则引发46处误导性编译失败。
0.8MB 跑通 Qwen|第 2-2 篇:推理引擎的平台层——一个头文件守住全部平台契约(vllm_platform.h)
|
1天前
|
人工智能 运维 前端开发
接入多家模型供应商之后,我总结的 7 条铁律
AI 应用接了第二家模型供应商之后,事情就不再是"多加一个接口"那么简单——不是调不通,是钱会算错。这篇讲我们定下来的 7 条规则:幂等键、任务落库时机、备用线路的切换边界、退款、凭据隔离、付款来源固化、密钥不进浏览器。

热门文章

最新文章