1. 从一次“首字卡顿”说起预填充与解码到底在干什么如果你用过大模型 API大概率遇到过两种截然不同的体验一种是输入一段长 prompt 后界面半天不吐字等第一个字出来后又开始流畅滚动另一种是短问题秒回但长回答越到后面越慢。这两种现象背后其实就是大模型推理的两个核心阶段——预填充Prefill和解码Decode在起作用。简单说预填充阶段负责“读懂”你输入的全部 token一次性并行计算并生成 KV Cache解码阶段则基于这个缓存一个 token 一个 token 地自回归生成输出。预填充决定了首 token 延迟TTFT解码决定了每 token 输出时间TPOT。而 KV Caching 就是把预填充阶段算出来的 Key/Value 张量缓存下来避免解码时重复计算这是所有现代推理框架的标配优化。这篇内容面向想自己动手跑推理、调参数、看指标的开发者。我会用 TaoToken 的统一 API 通道作为接入层把 config.toml / settings.json 的配置骨架、KV Caching 相关参数、请求验证步骤完整走一遍。你不需要自己维护多套 Key也不用在多个厂商的 SDK 之间来回切换一个 Key 就能把预填充和解码的观测做起来。2. TaoToken 前置统一 Key 与 API 通道准备在动手写配置之前先把接入层理清楚。TaoToken 提供的是统一 API 通道官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。它的作用是让你用同一套 Key、同一套请求格式去调用不同的大模型省掉为每个模型单独申请和切换凭证的麻烦。你需要先拿到一个 API Key。进入控制台的 API Keys 页面创建即可https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建后复制保存后面配置文件里会用到。注意 Key 只显示一次丢了就重新生成。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面列出了兼容 OpenAI 风格的请求路径和参数。我们这次实战主要用到两个能力一是标准的 chat completions 接口用来观察预填充和解码的耗时二是模型对话页面用来快速验证模型是否正常响应https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你后续要做长期编码或 Agent 类任务可以关注 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它更适合需要持续调用、批量推理的场景。本篇先聚焦单次请求的推理流程观测。环境变量建议这样设置避免把 Key 硬编码进代码export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api这样后面无论是 Python 脚本还是推理框架的配置都能通过环境变量读取安全且方便迁移。3. 可复制配置config.toml 与 settings.json 骨架这一节给出两份可直接复制的配置。第一份是推理框架侧的 config.toml用于控制 KV Caching、批处理和显存相关参数第二份是客户端侧的 settings.json用于定义请求参数和解码策略。3.1 config.toml推理框架侧 KV Caching 骨架下面这份 config.toml 以通用推理框架的参数命名为例重点标注了与预填充、解码、KV Cache 直接相关的字段。你可以根据自己使用的框架vLLM、SGLang、LMDeploy 等做字段名映射。[server] host 0.0.0.0 port 8000 api_base https://taotoken.net/api api_key_env TAOTOKEN_API_KEY [model] name your-model-name dtype bfloat16 max_model_len 8192 gpu_memory_utilization 0.90 [kv_cache] enable true block_size 16 num_gpu_blocks 2048 swap_space_gb 8 cache_dtype auto [batching] enable_continuous_batching true max_num_seqs 64 max_num_batched_tokens 4096 [prefill] chunked_prefill true max_prefill_tokens 2048 [decode] temperature 0.7 top_p 0.9 top_k 50 repetition_penalty 1.05 max_tokens 1024几个关键点解释一下。kv_cache.enable true是核心开关关掉它解码阶段会退化成每次重算全部历史 KV速度会明显下降。block_size对应分页注意力的页大小16 是常见默认值。cache_dtype auto让框架自动选择缓存精度通常跟模型 dtype 一致。chunked_prefill开启后超长 prompt 会被切成多个 chunk 分步预填充避免一次性占满显存。max_num_batched_tokens控制单批预填充的 token 上限调大能提升吞吐但会增加显存峰值。max_num_seqs是连续批处理的最大并发序列数直接影响解码阶段的并行度。3.2 settings.json客户端请求与解码参数客户端侧用 settings.json 管理请求参数方便在不同实验之间切换{ base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, model: your-model-name, default_params: { temperature: 0.7, top_p: 0.9, top_k: 50, max_tokens: 512, stream: true }, timeout: { connect: 10, read: 120 }, retry: { max_attempts: 3, backoff_seconds: 2 } }stream: true很重要流式返回能让你实时观测 TTFT 和 TPOT。read超时设 120 秒给长输出留足时间。retry用于网络抖动时的自动重试。3.3 参数对照表参数所属阶段作用建议值block_sizeKV Cache分页注意力页大小16num_gpu_blocksKV CacheGPU 上缓存块数量按显存调chunked_prefill预填充长 prompt 分块处理truemax_prefill_tokens预填充单次预填充上限2048max_num_seqs解码连续批处理并发数64temperature解码采样随机性0.7top_p解码核采样阈值0.9注意不同推理框架的字段名可能不同比如 vLLM 用--block-sizeSGLang 用--page-size。核心概念一致映射时对照官方文档即可。4. 验证请求观测预填充与解码的实际表现配置写好后用一段 Python 脚本发请求同时记录 TTFT 和总耗时。这样你能直观看到预填充和解码各自花了多久。import os import time import json from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api ) prompt 请用 300 字解释大模型推理中预填充和解码的区别并说明 KV Caching 的作用。 start time.time() first_token_time None token_count 0 stream client.chat.completions.create( modelyour-model-name, messages[{role: user, content: prompt}], temperature0.7, top_p0.9, max_tokens512, streamTrue ) for chunk in stream: if chunk.choices[0].delta.content: if first_token_time is None: first_token_time time.time() - start token_count 1 print(chunk.choices[0].delta.content, end, flushTrue) total time.time() - start tpot (total - first_token_time) / max(token_count - 1, 1) print(f\n--- 指标 ---) print(fTTFT首 token 延迟: {first_token_time:.3f}s) print(f总耗时: {total:.3f}s) print(f输出 token 数: {token_count}) print(fTPOT每 token 时间: {tpot:.4f}s) print(fTPS每秒 token 数: {1/tpot:.2f})运行后你会看到类似这样的输出TTFT首 token 延迟: 0.482s 总耗时: 6.731s 输出 token 数: 287 TPOT每 token 时间: 0.0218s TPS每秒 token 数: 45.87TTFT 主要反映预填充阶段的耗时包括 prompt 编码和 KV Cache 构建。TPOT 反映解码阶段的效率数值越小说明每个 token 生成越快。如果 TTFT 很高但 TPOT 正常说明预填充是瓶颈可以考虑开启 chunked_prefill 或减小 max_prefill_tokens。如果 TPOT 偏高检查 KV Cache 是否真正启用以及 batch size 是否合理。再做一个对比实验把kv_cache.enable设为 false重跑同一请求。你会发现 TPOT 明显上升因为解码时每次都要重算历史 KV。这个对比能帮你确认 KV Caching 确实在生效。5. 本篇常见错排查实际配置过程中有几个坑出现频率很高这里集中列一下。第一个坑KV Cache 没生效TPOT 异常高。检查 config.toml 里kv_cache.enable是否为 true以及框架启动日志里有没有 “KV cache enabled” 之类的提示。有些框架在显存不足时会自动降级关闭缓存需要看日志确认。第二个坑TTFT 波动大忽快忽慢。这通常是预填充阶段排队导致的。如果并发请求多预填充会排队TTFT 就会拉长。可以调大max_num_batched_tokens或开启 chunked_prefill 来缓解。另外检查客户端是否用了连接池频繁建连也会增加延迟。第三个坑长 prompt 直接 OOM。预填充阶段显存占用与 prompt 长度成正比。如果 prompt 超过max_model_len或者 KV Cache 块不够就会 OOM。解决办法是调小max_prefill_tokens开启 chunked_prefill或者降低gpu_memory_utilization给缓存留更多空间。第四个坑流式返回中断。检查 settings.json 里的read超时是否够长以及网络是否稳定。如果用了反向代理确认代理没有缓冲流式响应。第五个坑解码结果重复或循环。这是解码策略问题不是 KV Cache 问题。调低 temperature或者加 repetition_penalty能有效缓解贪心解码导致的循环。提示排查时优先看框架启动日志和请求日志大部分问题在日志里都有明确线索。不要一上来就改参数先定位是预填充还是解码阶段的问题。6. 继续深入从单次请求到持续推理把上面的配置跑通后你已经能观测到预填充和解码的实际表现也验证了 KV Caching 的效果。接下来如果想做更系统的推理服务可以关注几个方向连续批处理调优、分页注意力的块大小调参、以及不同解码策略对 TPOT 的影响。如果你需要长期跑编码类或 Agent 类任务建议了解一下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它在持续调用场景下更省心。日常快速验证模型响应可以直接用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。需要新建或轮换 Key 时控制台入口在这里https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。我自己的习惯是先用模型对话页面确认模型通不通再用脚本跑 TTFT/TPOT 指标最后根据指标调 config.toml 里的 KV Cache 和批处理参数。这样一轮下来预填充和解码的瓶颈在哪基本就清楚了。
