LLM 推理的“双重人格”拆解:同一 GPU 上 Prefill 与 Decode 的 KV Cache 配置骨架
发布时间:2026/9/29 20:12:56来源:尧图网络
1. 同一块 GPU为什么像在跑两个模型你在产品界面敲下一句 prompt按下回车。几百毫秒后文字开始一个一个流出来看起来丝滑得像魔法。可如果你正在负责一个生产级 LLM 服务这几百毫秒的延迟可能直接决定用户留存率。表面上看只是“生成下一个 token”的循环背后却是一条现代计算里最精心设计的管道。更诡异的是同一个模型、在同一块 GPU 上、同一个请求里实际上在干两件完全不同的事瓶颈也完全相反。我起初以为 LLM 推理就是简单的自回归循环预测一个 token拼回去再预测下一个。后来系统性拆解了 vLLM、TensorRT-LLM 的实现细节才发现整个过程被清晰地切成 Prefill 和 Decode 两个阶段它们在算力需求、内存行为和硬件利用率上几乎是两个物种。这才是真正决定 TTFTTime To First Token和 ITLInter-Token Latency的底层机制。LLM 推理的本质是把“一次性并行处理整个 prompt”与“逐 token 串行生成”强行塞进同一套 transformer 架构。前者计算密集后者内存密集。理解这个切换你就再也不会把一次 generate() 调用当成黑箱。本文会交付一套可复制的推理服务配置骨架含 settings.json / config.toml 示例与验证动作帮你在 TaoToken 统一 Key/API 通道下复现模式切换并观测延迟与吞吐变化。适合谁读正在自建或调优 LLM 推理服务的后端/算法工程师被 TTFT 和 ITL 指标折磨、想搞清楚显存到底被谁吃掉的运维同学以及准备把本地 demo 推向生产、需要一套可观测配置骨架的开发者。2. Prefill 与 Decode 的机制拆解2.1 Prefill一次性并行处理整个 prompt神经网络不认识文字只认识向量。第一步永远是 Tokenization。现代 LLM 普遍采用 Byte Pair EncodingBPE从字符开始不断合并最常见的相邻对直到构建出约 5 万个 token 的词汇表。常见词如 “the” 是一个 token罕见词如 “unhappiness” 会被拆成 “un happi ness”。这一步直接影响成本——训练数据里覆盖不好的语言会被切得更碎相同语义的句子 token 数更多延迟和费用自然更高。接着每个 token ID 去 embedding table 查表得到一个高维向量比如 4096 维同时通过 RoPE 等位置编码注入顺序信息因为 attention 本身对位置无感。然后进入真正的 transformer 层堆栈通常 32 层以上。每层核心动作只有两个Self-Attention 让每个 token 产生 Query、Key、Value 三个视图用 Query 去“查找”其他 token 的 Key把匹配强的 Value 拉进来Feed-Forward Network 对每个 token 独立做一次两层 MLP完成真正的“知识加工”。这一整套前置处理就是 Prefill 阶段。模型必须一次性处理完你所有输入 token才能开始生成。所有 token 的 Q/K/V 可以并行计算attention 变成大规模矩阵乘法。GPU 此时被算力充分利用瓶颈是纯算术吞吐量。衡量指标就是 TTFT——用户看到第一个字之前的等待时间。2.2 Decode逐 token 串行瓶颈转向内存带宽第一个 token 出来后模式彻底切换。要生成第 51 个 token只需要为这一个新 token 计算 Query而之前的 Key 和 Value 已经缓存下来不需要重复计算。此时 GPU 算力其实闲置了瓶颈变成了内存带宽要不停地把权重矩阵和越来越大的 KV Cache 从 HBM 拉进计算单元。Inter-Token LatencyITL就是用户感知到的“流畅度”核心指标。这就像工厂流水线Prefill 是“一次性把所有原材料并行加工”Decode 是“每生产一个成品都要去仓库取一次零件”。两者的硬件画像完全相反这也是为什么同一块 GPU 在同一个请求里会表现出“双重人格”。2.3 KV Cache让 Decode 成为可能的操作系统级优化没有 KV Cache生成 1000 个 token 就要每一步重新算整个序列的 attention——复杂度直接变成二次方慢到无法使用。有了 KV CacheK 和 V 只算一次之后永远复用。代价是它会随着上下文长度线性增长一个 13B 模型每 token 大约吃掉 1MB 显存4K 上下文就直接吃掉好几个 GB。这也是为什么超长上下文会突然变贵、变慢——不是模型“没脑子”了而是缓存把显存占满了。维度Prefill 阶段Decode 阶段计算模式全序列并行矩阵-矩阵乘法单 token 串行矩阵-向量乘法硬件瓶颈计算密集GPU 利用率拉满内存密集算力闲置等 HBMKV Cache 行为首次填充持续追加每 token 增长优化方向连续批处理、投机解码分页注意力、量化缓存用户感知指标TTFT 首 token 延迟ITL token 间延迟量化是最高杠杆的推理加速手段之一。训练需要高精度推理不需要。FP16/BF16 已经能把显存和吞吐量各砍一半更激进的 INT8/INT4 量化GPTQ、AWQ还能再砍一半以上而在大多数 benchmark 上质量损失微乎其微。7B 模型从 FP32 的 28GB 直接压到 INT4 的 3.5GB——这才是笔记本 GPU 能跑大模型的真正原因。3. TaoToken 前置统一 Key 与 API 通道要把上面这套机制真正跑起来并观测你需要一个稳定的模型调用入口。TaoToken 提供统一的 Key/API 通道把不同模型的接入收敛成一套 OpenAI 兼容接口这样你切换模型、对比 Prefill/Decode 表现时不用反复改代码。先拿到访问凭证打开控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面创建一个 Key形如sk-xxxxxxxx。这个 Key 就是后面所有配置里的api_key。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的请求格式、参数说明和错误码。API 基地址是https://taotoken.net/api注意这个地址不加 UTM 参数直接用于代码里的base_url。注意Key 只创建一次即可不要写进前端代码或提交到 Git 仓库。生产环境用环境变量注入本地调试用.env文件并加入.gitignore。如果你只是想先验证模型对话是否通可以直接用模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 发一条消息确认 Key 有效、网络可达再进入下面的配置环节。4. 可复制配置骨架下面这套骨架分两部分settings.json负责服务端推理参数config.toml负责客户端调用与观测。你可以直接复制后按注释改。4.1 settings.json推理服务参数{ model: your-model-name, api_base: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, max_tokens: 512, temperature: 0.7, stream: true, prefill: { batch_size: 8, max_prefill_tokens: 4096, chunked_prefill: true, chunk_size: 512 }, decode: { max_num_seqs: 32, max_model_len: 8192, gpu_memory_utilization: 0.90, kv_cache_dtype: auto, block_size: 16 }, observability: { log_ttft: true, log_itl: true, log_kv_cache_usage: true } }几个关键参数解释。chunked_prefill打开后长 prompt 会被切成chunk_size大小的块分批做 Prefill避免一个超长请求把 GPU 独占、把其他请求的 Decode 饿死。gpu_memory_utilization控制 KV Cache 能占多少显存0.90 意味着留 10% 给权重和临时张量调太高会 OOM调太低会限制并发。kv_cache_dtype设为auto时框架会按模型精度自动选显存紧张可以手动降到fp8。4.2 config.toml客户端调用与观测[server] base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} timeout_seconds 120 [request] model your-model-name stream true max_tokens 512 [metrics] # 记录首 token 延迟与 token 间延迟 track_ttft true track_itl true # 采样窗口单位秒 sample_window 30 [workload] # 用于压测的 prompt 长度分布 prompt_tokens [128, 512, 2048, 4096] # 每个长度重复次数 repeat 5prompt_tokens这个数组是复现模式切换的关键短 prompt128几乎全在 Decode 阶段长 prompt4096会把 Prefill 的耗时放大到肉眼可见。跑完一轮你就能看到 TTFT 随 prompt 长度近似线性上升而 ITL 基本稳定——这正是两个阶段瓶颈不同的直接证据。4.3 环境变量与启动export TAOTOKEN_API_KEYsk-你的key python -m your_inference_server --config settings.json如果你用 Python 客户端直接调最小可运行片段如下import os, time from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY], ) start time.time() first_token_time None token_count 0 stream client.chat.completions.create( modelyour-model-name, messages[{role: user, content: 用三句话解释 KV Cache}], streamTrue, max_tokens256, ) for chunk in stream: delta chunk.choices[0].delta.content if delta: if first_token_time is None: first_token_time time.time() token_count 1 end time.time() ttft first_token_time - start itl (end - first_token_time) / max(token_count - 1, 1) print(fTTFT{ttft*1000:.1f}ms ITL{itl*1000:.1f}ms tokens{token_count})这段代码把 TTFT 和 ITL 分开计时是后面验证环节的核心工具。5. 验证请求与成功结果5.1 先确认通道可用curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: your-model-name, messages: [{role: user, content: ping}], max_tokens: 16 }返回里能看到choices[0].message.content就说明 Key 和通道都正常。如果返回 401检查 Key 是否复制完整返回 404检查base_url是否漏了/v1或写成了带 UTM 的地址。5.2 复现模式切换并观测用 4.2 里的prompt_tokens数组跑一轮把每次的 TTFT 和 ITL 记下来。预期结果大致是这样prompt tokensTTFTITL主导阶段128约 80ms约 25msDecode 为主512约 180ms约 26msPrefill 开始显现2048约 520ms约 27msPrefill 明显4096约 980ms约 28msPrefill 主导TTFT 随 prompt 长度近似线性增长因为 Prefill 要并行处理所有输入 tokentoken 越多矩阵越大。ITL 几乎不变因为 Decode 每步只处理一个新 tokenKV Cache 复用让单步成本稳定。这就是“同一 GPU、同一请求、两种工作模式”的可观测证据。5.3 观测 KV Cache 占用在服务端日志里打开log_kv_cache_usage你会看到类似kv_cache_usage0.42的字段。跑一个 4096 上下文的请求再跑一个 512 的对比这个数值。KV Cache 占用随上下文长度线性上升一旦接近gpu_memory_utilization上限框架就会开始拒绝新请求或抢占已有请求——这就是超长上下文变慢变贵的根因。6. 本篇常见错排查报错CUDA out of memory先降gpu_memory_utilization到 0.85再把max_model_len从 8192 降到 4096。如果还不行把kv_cache_dtype改成fp8。KV Cache 是显存大户上下文越长越明显。TTFT 正常但 ITL 抖动大多半是并发请求太多Decode 阶段在抢内存带宽。把max_num_seqs从 32 降到 16观察 ITL 是否变稳。连续批处理能提升吞吐但并发过高会让单请求的 ITL 变差这是吞吐与延迟的经典权衡。长 prompt 把短请求卡死打开chunked_prefill把chunk_size设成 512。这样长 prompt 的 Prefill 会被切片中间穿插其他请求的 Decode避免一个请求独占 GPU。base_url报连接错误确认写的是https://taotoken.net/api不要带 UTM 参数也不要漏掉/v1取决于客户端是否自动补全。Key 用环境变量注入别硬编码。流式输出中断检查timeout_seconds是否太短长输出场景建议 120 秒以上。同时确认streamtrue在客户端和服务端一致。想对比不同模型的表现直接在模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 切换模型发同样的 prompt观察 TTFT/ITL 差异再回到配置里调整参数。7. 长期编码与 Agent 场景的接入如果你不只是做单次推理验证而是要把这套配置接进长期的编码助手或 Agent 工作流建议走 Coding Plan 通道https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它更适合持续性的代码生成、多轮工具调用场景配合上面的settings.json骨架可以把 Prefill/Decode 的观测长期跑在 CI 或监控里。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。官网入口 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个我踩过的坑一开始我把gpu_memory_utilization拉到 0.95 想榨干显存结果长上下文请求一多就 OOM服务直接挂。后来降到 0.88 并打开chunked_prefill吞吐没降多少稳定性好了很多。KV Cache 的显存账宁可留余量别赌极限。
网站建设高端定制企业官网