06 · DeepSeek V4.1 Flash 推理端到端走查:一个 Agent 请求穿过 recipe、KV 缓存、Prefill 与 Decode

📦 GitHub: https://github.com/geekchow/deepseek-v41-flash-inference-guide

你在哪里 :最后一站。所有组件的内部都已经看过,现在把它们缝成一个连续的故事。

读完本文你将知道:编程 Agent 第三轮请求从发出到收到工具调用的每一跳------跨越哪个边界、携带什么数据、组件内部做了什么、产出了什么。


1. 带数据标注的全景图

三种形式,按需选用:

下图回答:示例请求在每一跳上携带的具体数据是什么。
Decode Prefill VE Prefill LB 3FS Entrypoint Recipe Agent Decode Prefill VE Prefill LB 3FS Entrypoint Recipe Agent #mermaid-svg-Z45StpcJfsJv42q9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Z45StpcJfsJv42q9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Z45StpcJfsJv42q9 .error-icon{fill:#552222;}#mermaid-svg-Z45StpcJfsJv42q9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z45StpcJfsJv42q9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z45StpcJfsJv42q9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z45StpcJfsJv42q9 .marker.cross{stroke:#333333;}#mermaid-svg-Z45StpcJfsJv42q9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z45StpcJfsJv42q9 p{margin:0;}#mermaid-svg-Z45StpcJfsJv42q9 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Z45StpcJfsJv42q9 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Z45StpcJfsJv42q9 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Z45StpcJfsJv42q9 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Z45StpcJfsJv42q9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Z45StpcJfsJv42q9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Z45StpcJfsJv42q9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Z45StpcJfsJv42q9 .sequenceNumber{fill:white;}#mermaid-svg-Z45StpcJfsJv42q9 #sequencenumber{fill:#333;}#mermaid-svg-Z45StpcJfsJv42q9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Z45StpcJfsJv42q9 .messageText{fill:#333;stroke:none;}#mermaid-svg-Z45StpcJfsJv42q9 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Z45StpcJfsJv42q9 .labelText,#mermaid-svg-Z45StpcJfsJv42q9 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Z45StpcJfsJv42q9 .loopText,#mermaid-svg-Z45StpcJfsJv42q9 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Z45StpcJfsJv42q9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Z45StpcJfsJv42q9 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Z45StpcJfsJv42q9 .noteText,#mermaid-svg-Z45StpcJfsJv42q9 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Z45StpcJfsJv42q9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Z45StpcJfsJv42q9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Z45StpcJfsJv42q9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Z45StpcJfsJv42q9 .actorPopupMenu{position:absolute;}#mermaid-svg-Z45StpcJfsJv42q9 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Z45StpcJfsJv42q9 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Z45StpcJfsJv42q9 .actor-man circle,#mermaid-svg-Z45StpcJfsJv42q9 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Z45StpcJfsJv42q9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 约 220 步 Messages JSON thinking + edit_file + 截图 1 100,000 token max_tokens=65536 2 哈希链匹配 3 L1 = 96,896 4 SWA 目录:D-17 有快照 命中 96,896 5 未命中 3,104,64--256K 档 6 配额最低的 DP rank 7 截图 1280×800 8 图像 embedding 9 757 块,GDR 逐层换入 10 编码器算 3,104 token 解码器重放 140 token 11 新块 758--781 12 Block KV + SWA KV 13 DSpark:草稿 5 验证约 3.6,接受约 3.2 14 新 token 15 token ID 16 thinking_delta / input_json_delta 17 新块 + SWA@100,608 18 stop_reason=tool_use 19

为保持可读,图中省略了 Decode LB(在 Prefill 与 Decode 之间)和 VE LB(在 Prefill 与 VE 之间),它们的决策见下文第 ③④⑥ 跳。


2. 逐跳叙述

① 客户端 → deepseek-recipe:协议变成 token

进 :一个 POST /v1/messages 请求,stream: true,思考模式开启,带 edit_file 工具定义、前两轮历史(含历史推理和 DSML 工具调用)、本轮工具结果(终端报错,里面恰好有 '<|User|>')、一张 1280×800 截图和一句指令。

内部 (详见 recipe 深潜):

  1. MessagesRequest.convert → 统一 Conversation;模型名解析为 V4.1 模板;温度 1.0、top_p 1.0。
  2. render_conversation → prompt 文本 + special_ranges。系统段写入推理强度、DSML 工具调用说明和工具 schema;历史轮次原样保留推理内容,保证与第 2 轮序列逐 token 对齐;结尾 <|Assistant|><think>。
  3. 分段编码:特殊区间直接映射 ID(如 <|User|> = 128803),普通文本段只用基础词表批量并行编码。报错里的 '<|User|>' 被编成普通 token------伪造控制 token 失败。
  4. resolve_max_tokens(100000, context_limit) → 65,536。

出:100,000 个 token ID + 采样参数。

② Entrypoint:96,896 个 token 不必重算

内部 (详见 Entrypoint 与 KV 存储):

  1. 命中上限 L − 134 = 99,866。
  2. 对本地 CHECK 角色的 kvcache-store 做哈希链前缀匹配(只查元数据):第 2 轮留下的 757 个块全部命中 → L1 = 96,896。这一步在路由前完成,无法隐藏。
  3. 查 SWA 内存目录:区间 [96,768, 96,896] 内,D-17 上有位置 96,896 的快照 → 命中 = 96,896。

出:请求 + 命中长度 96,896(未命中 3,104)。

③ Prefill LB:按"算力时间"而不是"请求个数"分配

内部 (详见 负载均衡器族):

  1. 总上下文 100,000 → 64--256K 档。
  2. 该档中剩余配额最多的引擎主动拉取这个请求;没有前缀亲和------任何引擎都能从 3FS 读到这 757 块。
  3. 组内交给配额负载最低的 DP rank。
  4. 单卡内 FIFO;用 cost(q = 3104, c = 96896) 对照本步时间预算(DP 组内 all-reduce MIN 后的值)求解 chunk 大小:3,104 < 32,768 且预算足够 → 一个 chunk 完成。

④ VE:截图变成 embedding

prefill 引擎遇到 <|image|> 占位,经 VE LB 随机选一台 VE 机器、交给 patch 最少的 GPU(按假设 patch_size = 16,本图 80 × 50 = 4,000 个 patch)。VE 卡执行 pad → ViT → downsample → aligner,返回 embedding 填入占位处。CPU 侧预处理已由 MultimodalProcessor 异步完成。

⑤ Prefill Service:只算 3,104 个 token

内部 (详见 Prefill 篇):

  1. KV 换入:Fetch 进程从 3FS 读 757 块(每块同时向 11 个节点发请求、收齐 10 个即解码)进主机共享内存;引擎换入线程以 GDR 逐层搬进 HBM,与计算重叠。按 890 B/token 估算约 86 MB。
  2. SWA 拉回:从 D-17 经 400G VLAN 拉回 1,415,040 字节(20 层 × 134 token × 528 B)。
  3. 编码器层 :对 3,104 个新 token 前向,MoE 部分在 EP4 上由 EPLB 均衡(实测 moe_lb ≈ 1.07)。
  4. 解码器层 :只对 prompt 最后 140 个 token 做 SWA Bounded Replay,窗口截断在段内,产出 decode 用的近似解码器 SWA KV,不进前缀缓存------prefill 总计算量接近减半的来源。
  5. 落盘:新增第 758--781 块(覆盖到 99,968)写入 3FS;SWA 快照存于 99,968,登记目录。

⑥ Decode LB → Decode Service:长请求去"轻"的卡

Decode LB 同时看每张卡的并发数和 KV 总长:我们的请求 KV 约 10 万 token,属于"长请求",被送到 KV 总长最小的卡。Block KV 与 SWA KV 经同一条 P→D 路径传过去。

⑦ Decode Service:约 220 步生成约 700 个 token

内部 (详见 Decode 篇 与 调度篇):

  • EP64DP64:每卡 7 个专家(384 + 64 冗余),每步 MoE 访存大幅降低;所在 rank 约 60 个并发请求。
  • 每步:草稿头猜 5 个 token + 置信度 → GPU 上的 TileLang kernel 按全批期望收益分配验证名额(平均约 3.58 个)→ 主模型批量验证,平均接受约 3.21 个。
  • CPU 的 schedule(N+1) / post-process(N−1) 与 GPU 的 forward(N) / sample(N) 完全重叠,GPU 无空洞。
  • 约 700 ÷ 3.2 ≈ 220 步;以每用户约 246 token/秒计,约 2.8 秒。

⑧ deepseek-recipe → 客户端:token 流变成协议事件

StreamProcessor 收到一串 InferenceChunk.tokens(每个约 3 个 token):

  1. 先发 message_start,usage 里 input_tokens = 3104、cache_read_input_tokens = 96896------第 ② 步的命中结果原样反映在账单上;
  2. 推理段输出 thinking_delta;推理里用普通 token 写出的 </think> 仍属推理内容;
  3. 遇到真正的 </think>(ID 128822)→ 关闭 thinking 块;遇到 |DSML|(ID 128825)开启的 invoke → 工具名一出现就发 content_block_start {type: tool_use, name: edit_file};
  4. 参数按 DSML 边界直接转成 JSON 片段,逐个发 input_json_delta;不完整的 UTF-8 字符和被切断的标签由缓冲与增量 KMP 处理;
  5. EOS → message_delta {stop_reason: tool_use} → message_stop。

(以上事件序列可用 examples/recipe_stream_demo.py 在本地复现。)

⑨ 收尾:为第 4 轮铺路

decode 期间每跨过一个 128 边界就在显存内拷贝一次 SWA 窗口;请求结束(序列约 100,700)时用 100,608 处的拷贝落存到 decode 机器内存并登记目录;新块写入 3FS。Agent 执行 edit_file 后发起第 4 轮,命中长度最多可达 100,608。

变体回放:D-17 宕机

第 ② 步 SWA 目录查不到可用快照 → 命中 L1 − 128 = 96,768;第 ③ 步配额按 3,232 个 token 计算;第 ⑤ 步编码器层把命中前缀的最后 128 个 token 与 3,104 个新 token 一起前向,那 128 个 token 只重建 SWA KV、Block KV 原样复用。其余各跳不变。代价:多算 128 个 token。


3. 数字账本

量 值 出处
prompt 长度 L 100,000 示例设定
命中上限 99,866(L − 134) 命中策略
Block KV 命中 L1 96,896(757 块) 第 2 轮长 97,000
SWA 快照位置 96,896 ⌊96,999/128⌋×128
未命中 / 实算 3,104(变体 3,232)
换入 KV 体积 ≈ 86 MB 96,896 × 890 B
SWA 快照体积 1,415,040 B 20 × 134 × 528
解码器重放 140 token KEEP_TOKENS
prefill 后新增块 第 758--781 块 ⌊100,000/128⌋ = 781
decode 步数 ≈ 220 700 ÷ 3.2
生成耗时 ≈ 2.8 秒 700 ÷ 246
账单 input 3,104 + cache_read 96,896 recipe usage
下一轮可命中 最多 100,608 ⌊100,699/128⌋×128

4. 回到 Why

第 1 章提出的痛点,在这一个请求里都被解决了:

  • 长且重复的 Agent 输入:10 万 token 只算了 3,104 个------共享 3FS 前缀缓存 + 精确的命中策略;
  • 缓存贵且与均衡冲突:Block KV 放独立存储集群,任何 prefill 都能命中,均衡器只管算力;SWA KV 只存 1.35 MiB 单副本,丢了只多算 128 token;
  • prefill 与 decode 脾气相反:prefill 用 EP4 小组按时间预算切 chunk,decode 用 EP64 大组 + DSpark 吃满访存带宽;
  • MoE 负载不均:EPLB 把最慢卡压到平均值的 1.07--1.09 倍;
  • 多协议与安全:recipe 让 Messages 客户端得到与官方一致的行为,并且无法通过文本伪造控制 token;
  • 多厂商硬件:同一套 DeepInfer 引擎跑在英伟达 SM100 和昇腾 950 上。

这就是单集群每天 155 万亿 token、每 GPU·小时 38 美元背后的完整链条。


下一篇:07 下一步 | 返回:00 总览


📚 返回专栏目录

相关推荐
前进的李工1 小时前
Agent 学习笔记 04:KV Cache 与上下文缓存
笔记·学习·缓存·agent·注意力机制·kv cache·prompt cache
John_Hui19 小时前
第 5 篇:Ubuntu 安装 Redis
数据库·redis·缓存
l1t20 小时前
DeepSeek总结的ALTER FUNCTION SET work_mem:无需全局更改即可修复 PostgreSQL 磁盘溢出
sql·缓存·postgresql
咖啡八杯20 小时前
Redis 工具封装:RedisCache 对 Operations 体系的二次封装
java·数据库·redis·缓存·框架·springboot·若依
l1t1 天前
DeepSeek总结的PostgreSQL 中的 SQL 与 PL/pgSQL:计划缓存
sql·缓存·postgresql
hongyangcao1 天前
Cloudflare GLM-5.3 串号事故复盘:KV 缓存页记账错了一个字段,155 条回复答进了别人的会话
人工智能·缓存·ai
MetaLite2 天前
Spring 同一个类里调用 @CacheEvict,为什么缓存没清掉?
java·spring·缓存
hweiyu002 天前
Redis命令:SMOVE
redis·缓存