0.8MB 跑通 Qwen|第 11-1 篇:大模型推理中多轮对话的浪费——上一轮 prefill 算完就被扔了

简介: 本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台高效运行Qwen3-VL多模态模型;核心突破是默认启用前缀KV复用,使多轮对话prefill耗时骤降27倍,显著消除历史重复计算,大幅提升端侧长会话效率。(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 篇 · 总纲(阿里云社区)

上一篇:10-3《诚实的坑:哪些场景稀疏无收益》 | 下一篇:11-2《前缀缓存键:怎么知道"一样"》

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

一句话导读:多轮对话的浪费:无状态 API 每轮把全历史重发一遍、推理引擎把已算过的 prefill 重新算一遍,越聊越长白算越多——引擎靠什么让第二轮只补算新增的尾巴,从 reset 分支读前缀缓存命中的秘密。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、多轮对话、前缀缓存、KV cache、prefill

导语:多轮对话里最贵的浪费:无状态 API 每轮把全历史重发一遍,引擎就把已算过的 prefill 又算一遍,越聊越长、白算越多。本篇从 reset 分支切入看清这笔浪费有多大,并引出默认开启的前缀 KV 复用——同进程两轮实测 prefill 从 46.4s 降到 1.7s。

10 天里我们优化了"单次请求"的每个环节——可真实服务是多轮对话:客户端每轮都把全历史重发一遍,引擎每轮都把历史重新 prefill 一遍。今天先看清这笔浪费有多大,再引出引擎的解法:前缀 KV 复用(默认开)。

1. 知识点:TTFT 里最贵的那段,多轮时被重复支付

一次聊天的第一段耗时(TTFT,首 token 时间)大致由四段组成:

  1. 排队 + 网络(HTTP 请求进来、调度);
  2. 分词(prompt → token id);
  3. prefill(对整段历史做一次前向,把 KV 写进缓存)——10-1 讲过,这是大头:2K 上下文在本板就要 ~46s(见第 3 节实测);
  4. 首词采样。

问题出在"无状态 API"模型上:OpenAI 兼容接口每次请求都要带完整消息历史(messages 数组)。于是第二轮请求的 prefill,会把第一轮已经算过的历史从头再算一遍——第一轮的 KV 明明还躺在内存里,却被当作"新请求"清零重来。

这就是"多轮对话的浪费":越聊越长,每轮白算的 prefill 越多。第二轮白算一轮历史、第三轮白算两轮历史……长会话里,模型大部分时间在重算自己刚算完的东西。

2. 对应代码:浪费发生在 reset,解法是"reset 时保留一段"

先看"每轮请求开头"发生了什么(vllm_server.c 第 405 行起 ist_reset):

static void ist_reset(VLLMServerCtx *ctx, int keep_lcp) {
   
    ...
    int reuse = (ctx->prefix_kv || ctx->disk_kv) &&
                keep_lcp >= PREFIX_KV_MIN_LCP && !g_l3_evict &&
                keep_lcp <= st->cache_len[0];
    if (reuse) {
   
        for (int l = 0; l < nl; l++) st->cache_len[l] = keep_lcp;
        st->seq_len = keep_lcp;
        fprintf(stderr, "[KV-PREFIX] reuse %d-token KV prefix, prefill rest\n",
                keep_lcp);
        ...
        return;               /* 保留前 keep_lcp 个 token 的 KV */
    }
    for (int l = 0; l < nl; l++) st->cache_len[l] = 0;  /* 全清:全部重算 */
    ...
}

看清两件事:

  1. 不开复用时(reuse 为假),请求开始就把 cache_len 全清零——上一轮辛苦写下的 KV 行逻辑上立即作废,新一轮从头 prefill。这是"浪费"的代码级实锤;
  2. 开复用时,引擎允许"保留前 keep_lcp 个 token 的 KV 行",只 prefill 尾巴(第 1321 行 st_qwen_model_prefill_batch(st, ids + keep, n_ids - keep) 只喂了剩余部分)。而 g_prefix_kv 默认就是 1(main.c 第 1649 行,--no-prefix-kv 可关)——默认开,不是需要用户想起来才有的功能。

"怎么知道要保留多长(keep_lcp 从哪来)"是下一篇 11-2 的主角(前缀最长公共子序列 LCP)。本篇只需要接受一个事实:保留多少、清零多少,由"上一轮请求的 token 序列"与"这一轮的"比对决定。

3. 改动后果:同进程两轮,prefill 46.4s → 1.7s

口径:RK3588 / Qwen3-VL-2B / 2026-09 / serve 同进程 /v1/completions 贪婪(前缀复用默认开)。第一轮 prompt = 2044-token 事实长文 + 问题(Alice 的颜色);第二轮 = 第一轮全部 + 模型回答 + 新问题(Bob 住哪),即真实多轮客户端的重发形态。上下文 n 与耗时取引擎日志 [PREFILL-TIMING] 的真实值。

轮次 prefill token 数 prefill 耗时 请求总耗时(含 decode) 模型输出
第一轮(全量) 2044 46.36 s 50.73 s Alice likes the color blue.
第二轮(复用 2034) 31 1.73 s 5.89 s Bob lives in Tokyo.

引擎日志如实记录了命中的那一刻:

[KV-PREFIX] reuse 2034-token KV prefix, prefill rest
[PREFILL-TIMING] n=2044 total=46355.9ms | GEMM=26224.7ms(56.6%) ATTN=18584.2ms(40.1%)
[PREFILL-TIMING] n=31 total=1726.2ms  | GEMM=992.4ms(57.5%) ATTN=716.8ms(41.5%)

判读:

  1. 第二轮只 prefill 了 31 个新 token(问题增量),却是在 2065 token 的完整上下文上采样回答的——正确作答证明保留的 2034 行 KV 真的参与了注意力,不是摆设;
  2. prefill 从 46.36s 降到 1.73s(≈27×);请求总耗时(含每轮固定的 decode 几秒)50.73s→5.89s(≈8.6×)。会话越长、历史占比越高,这个倍数越大;
  3. decode 没有被省:第二轮仍需在完整上下文上逐词生成——复用省的是 prefill,不省 decode(decode 的优化是 Day 10 稀疏与 Day 13 推测解码的主场)。

旁证(仓库文档,x86 基准机 8B/2026-08-28,优化配置与边界说明.md §3.2):前缀复用的进程内验证为 prefill 加速 4.99×(253 token 复用 231 token),且输出与全量逐字节一致。本板这轮 27× 之所以更高,是因为第一轮上下文比例更大(2044 复用 2034)——复用率决定加速比。

4. 学员调试任务

  • A 档(板端动手):按第 3 节口径自己跑一轮"同进程两轮":先发一个长 prompt,再发"长 prompt + 回答 + 追问"。记录两轮日志里的 [PREFILL-TIMING] n= 与 [KV-PREFIX],算你自己板子的 prefill 加速比。把第一轮 prompt 拉长到 4K 再跑一次,观察加速比是否变大(复用率更高)。
  • B 档(纯读源码):读 ist_reset(vllm_server.c 第 405 行起),回答:① reuse 成立需要哪 4 个条件同时满足?② reuse 分支里 cache_len[l] = keep_lcp 与"直接不 reset"有什么不同?(提示:新一轮的尾巴还要继续写 KV,cache_len 必须从 keep 处续写。)

预期输出:你能讲清"多轮浪费 = prefill 被重复支付",并用一组自己的板端数字量化"复用后 prefill 降了 X 倍"。

收尾

  • 本篇源码点名:vllm_server.c(ist_reset 第 405 行起、reuse 门 408–410、[KV-PREFIX] 415、增量 prefill 1321)、main.c(g_prefix_kv=1 默认 1649)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:复用保留了 2034 个 token——引擎怎么知道"这两轮的共同前缀恰好是 2034"?11-2 拆最长公共前缀(LCP)与那条"一模一样就不复用"的反直觉安全规则。
相关文章
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 14-2 篇:单 SSE 流与串行队列——推理引擎的"单机守则"
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多尺寸模型(2B/8B/30B),基于RK3588实测。本文详解“单机守则”:推理状态串行锁定、SSE流独立输出、HTTP与推理线程职责分离,揭示为何默认串行是稳定前提而非性能缺陷。(239字)
|
1天前
|
调度 C++ 索引
0.8MB 跑通 Qwen|第 12-3 篇:复现 13.1×——推理引擎的跨进程恢复实测怎么做(含口径拆解)
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588平台运行Qwen3-VL多模型;详解跨进程KV恢复机制,揭示prefill/TTFT/wall三口径差异,13.1×加速比可复现、可验证。(239字)
|
1天前
|
缓存 内存技术
0.8MB 跑通 Qwen|第 9-2 篇:推理引擎的 flash_attn_single_q_q8_neon——单 q 单遍扫全 KV
本系列《0.8MB跑通Qwen》聚焦ARM零依赖纯C推理引擎,精读`flash_attn_single_q_q8_neon`内核:通过Q单次量化、q8 KV单遍扫描、在线softmax与尾部归一,显著降低带宽与内存开销,实现在RK3588等端侧设备高效运行Qwen3-VL系列模型。(239字)
|
1天前
|
人工智能 运维 IDE
阿里云Qoder CN是什么?Qoder CN全家桶包含哪些云产品?
阿里云Qoder CN(原通义灵码)是面向研发与办公的国产AI智能体全家桶,覆盖编码、办公、终端、云端及数字员工全场景,含Qoder CN、QoderWork CN、CLI、QoderWake CN、Cloud Agents CN和Mobile六大子产品,支持多模型、高合规、Credits统一计费。阿里云Qoder CN官网:https://t.aliyun.com/U/fEiOLV
|
1天前
|
人工智能 自然语言处理 数据可视化
阿里云万小智怎么样?真正的AI建站,实测一句话企业官网上线(成本15元)
阿里云万小智3.0是AI驱动的一站式建站工具,用户仅需用自然语言描述需求(如“创建科技公司官网”),AI即可自动生成完整网站,支持可视化编辑与AI对话优化,首站上线成本低至15元,操作便捷高效。
|
1天前
|
数据建模 网络安全
阿里云申请 SSL 证书需要多少钱?不同类型证书收费详解
阿里云SSL证书价格因品牌、验证等级(DV/OV/EV)及域名类型(单域/通配符/多域)差异显著:DV单域低至145.5元/6个月,OV/EV可达上万元/年;另享每年20张免费DV测试证书(3个月有效期)。实时价格以官网为准。
25 0
|
1天前
|
缓存 自然语言处理 安全
CLM 与后缀缓存复用:让模型自己管上下文
CLM(上下文语言模型)将上下文管理从外部规则升级为模型原生能力,支持像编辑文件一样重写、删除、重排上下文;配套SCR缓存复用技术,在性能持平下降低35%算力。论文尚未同行评审,代码开源但含非商用许可。
34 0
|
1天前
|
人工智能 弹性计算 自然语言处理
阿里云万小智 AI 建站标准版与高级版有什么区别?版本选购对比分析
阿里云万小智标准版(980元/年)与高级版(1980元/年)均支持AI建站、多语言、源码下载及安全防护;差异在于:高级版提供10G数据库(标准版1G)、100G创意存储(标准版10G)、明确2核2G+50M带宽ECS、3个备案码、不限邮件+自接短信、三倍AI灵感值,适合中高负载业务。
|
2天前
|
缓存 C++
0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
本文实测RK3588板端冷启动仅1.0–1.2秒,核心在于mmap直挂VQF权重文件:不全量读取,仅映射+校验头部,权重页由内核按需缺页加载,配合预量化布局,较safetensors全量读快33倍。(239字)
 0.8MB 跑通 Qwen|第 3-1 篇:推理引擎的 mmap 直挂——为什么权重加载可以只要一秒多
|
3天前
|
编译器 C语言
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局
本文详解C11 `_Static_assert` 在内存布局守卫中的关键作用:针对mmap直挂场景,通过编译期断言钉死VQF格式三结构体(200/432/64字节),杜绝因对齐差异导致的静默错位。真机RK3588实测验证,实现错误前移。
0.8MB 跑通 Qwen|第 2-1 篇:推理引擎的 C11 `_Static_assert`——让编译器守卫你的内存布局

热门文章

最新文章