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× 跨进程恢复"。
相关文章
|
12天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
7950 15
|
11天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
1753 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
11天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
1800 12
|
9天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
25天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
3800 10
|
19天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
2029 1

热门文章

最新文章