长上下文推理提速:Prefill加速核心原理与vLLM/SGLang实战对比

📅 2026/8/27 8:54:28
长上下文推理提速:Prefill加速核心原理与vLLM/SGLang实战对比
上周调一个 RAG 问答服务我把一份 60 页的产品文档丢给模型结果等了将近半分钟第一个字才慢慢悠悠出现。生成的答案只有几百字但“读文档”的时间比“写答案”的时间长了两个数量级。页面上的加载条一动不动用户早就关掉页面了。这个等待时间就是大模型推理里的 TTFTTime To First Token首 token 延迟。它由 Prefill预填充阶段和第一次 Decode解码共同决定。而在长上下文场景下TTFT 几乎完全被 Prefill 吞掉了。换句话说长上下文推理的体验瓶颈已经从过去大家熟悉的 Decode 阶段转移到了 Prefill 阶段。这篇文章会围绕 Prefill 加速展开。先解释 Prefill 和 Decode 的本质区别再拆解长上下文 Prefill 变慢的底层原因然后对比 SGLang 和 vLLM 两个主流框架对 Prefill 的内置优化最后给出可以直接复制的 vLLM/SGLang 部署命令、TTFT 测量脚本和常见问题排查表。无论你是做 RAG 应用、Agent 工具还是负责线上推理服务优化这篇文章都值得收藏。需要先说明的是标题里提到的“47 倍速”是特定硬件、特定模型和特定上下文长度下的测试值不代表所有场景都能复现。对工程师来说更有价值的不是记住一个倍数而是理解收益从哪来以及在自己环境里怎么测量、怎么复现。1. 这篇文章真正要解决的问题很多人刚接触大模型推理时会把注意力全部放在 Decode 阶段因为 Decode 是一步步生成 token 的串行过程看起来最耗时。这个认知在短上下文场景下基本成立但一旦进入长上下文场景情况就反过来了。你可以把大模型生成回答的过程想象成两步第一步是“阅读理解”模型把用户提供的全部内容读一遍建立上下文理解第二步是“逐字作答”每个字都依赖前面所有内容。第一步就是 Prefill第二步就是 Decode。短文本时“阅读理解”一眨眼就完了大家自然只关心“逐字作答”有多快。但文本一旦变长比如几万字、几十万 token第一步的计算量和显存消耗会急剧膨胀直接把第二步的时间占比压下去。这篇文章要解决的核心问题有三个Prefill 阶段为什么在长上下文下会成为主要瓶颈底层是计算、显存还是调度问题。SGLang 和 vLLM 里有哪些现成的 Prefill 加速能力默认情况下你究竟吃到了多少。生产环境里如何配置启动参数、如何量化模型、如何测量 TTFT以及遇到 OOM、超时、精度下降时怎么排查。需要说明的是这篇文章不绑定某个特定的第三方加速库而是把“Prefill 加速”这一整类方案的原理和接入方式讲清楚。无论你最终选择 vLLM、SGLang还是自研推理层理解 Prefill 的优化逻辑都能直接指导你的部署决策。2. 基础概念Prefill、Decode 与 TTFT 的关系2.1 Prefill 和 Decode 的本质区别大模型推理基于自回归机制输入和输出并不是一次性并行完成的而是分为两个阶段。Prefill 阶段模型接收完整的输入序列计算出每个 token 的注意力权重和中间状态生成 KV Cache。这个过程属于典型的密集矩阵计算可以充分利用 GPU 的并行能力。你可以把它理解成一次大规模的“批量读取”一次性把所有输入处理完。Decode 阶段模型每轮只生成一个 token生成结果会作为下一轮输入的一部分继续计算。由于每步都依赖前一步的结果Decode 天然是串行的无法通过无限堆算力来缩短单步延迟。这也是过去大家最熟悉的推理瓶颈。但“串行”不代表“慢到无法接受”。单个 token 的 Decode 延迟通常在几十毫秒级别生成 500 字也就几秒钟。而在长上下文下Prefill 一次性要处理几万 token即使并行计算能力再强总耗时也可能达到几十秒甚至几分钟。2.2 TTFT 到底包含什么TTFT 的工程定义是从请求发出到收到第一个新生成 token 的时间。它的构成可以写成TTFT ≈ Prefill 时间 第一次 Decode 时间在短文本场景下Prefill 时间可以忽略不计TTFT 约等于一次 Decode 时间。但在长文本场景下Prefill 时间占比可能超过 90%所以很多性能分析文章会把 TTFT 近似看作 Prefill 时间。严格来说这不精确但足以说明问题要降低 TTFT重点就是降低 Prefill 时间。理解这一点对排查问题很有用。如果用户反馈“转圈很久才出第一个字”你要看的是 Prefill 相关指标而不是盯着 Decode 阶段每秒钟生成多少个 token。2.3 KV Cache显存压力的来源为了不重复计算历史 token 的注意力结果推理框架会把已经计算好的 Key 和 Value 缓存下来这就是 KV Cache。它的规模跟序列长度成正比序列越长KV Cache 越大。长上下文场景下KV Cache 的体积会主导显存占用。举个例子一个 7B 参数的模型权重可能只占十几 GB但处理 100K token 上下文时KV Cache 的大小可能超过权重本身。这就导致了一个两难要么限制上下文长度要么牺牲并发能力要么做量化压缩。KV Cache 策略也直接影响 Prefill 的显存分配。vLLM 的 PagedAttention 之所以重要就是因为它在 KV Cache 上引入了类似操作系统内存分页的机制减少了显存碎片提高了缓存利用率。3. 长上下文 Prefill 慢在哪里三个核心瓶颈3.1 计算瓶颈注意力机制的复杂度Transformer 架构中自注意力机制的计算量随序列长度呈二次方增长。虽然 FlashAttention 等优化手段已经把实际计算效率提升了一个量级但长序列下的计算总量依然非常庞大。在 Prefill 阶段模型需要计算输入序列中每一对 token 之间的注意力分数。序列长度从 1K 增加到 32K注意力计算量会增长约 1000 倍。这种增长是结构性的单纯堆 GPU 只能缓解不能消除。3.2 显存瓶颈KV Cache 爆炸前面提到KV Cache 随序列长度线性增长。但生产环境里还有一层问题动态请求会带来不可预测的显存分配。如果框架对 KV Cache 管理不够精细就会频繁出现显存碎片甚至预留了大量显存却用不上。解决显存瓶颈有两个方向。一是改进 KV Cache 管理策略比如分页、动态分配、缓存复用二是压缩 KV Cache比如低精度量化、KV eviction淘汰机制。不同框架在不同方向上的成熟度差异很大这也是选择框架时要重点考察的。3.3 调度瓶颈长请求阻塞效应在线推理服务通常采用 Continuous Batching连续批处理让多个请求共享 GPU 计算资源。但长上下文请求会让调度策略变得很棘手。假设批处理中有一个请求需要 Prefill 50000 token其他请求只需要 Prefill 100 token。如果调度器不加以区分短请求可能被长请求的 Prefill 计算阻塞等待时间大幅上升。而如果强制把长 Prefill 拆分成多个小段又会增加调度开销可能导致 GPU 利用率下降。Chunked Prefill分块预填充就是针对这个问题提出的后面会详细解释。调度层面的优化往往比单纯的 kernel 优化更容易被忽略但对线上体验的影响非常直接。3.4 精度选择的影响Preifill 阶段的计算密度高对数值精度更敏感。常见的精度格式包括 FP32、FP16、BF16 和各类量化格式。FP32精度最高显存占用和计算量巨大生产环境基本不用于大模型推理。FP16范围有限但如果激活值过大可能导致溢出训练中常用推理中不如 BF16 鲁棒。BF16指数位与 FP32 相同范围大精度略低是当前大模型推理的主流选择。INT8/INT4 量化大幅降低显存和计算量但可能带来精度损失需要评测后使用。在长上下文 Prefill 场景中BF16 通常是性价比最高的选择。它的数值范围足够大不容易出现溢出问题精度损失在可接受范围内。量化则更适合显存受限或需要高并发的场景但必须用真实业务数据做效果评测不能只看推理速度。4. 加速 Prefill 的核心优化思路如果把 Prefill 加速方案按技术层次拆开大致可以分成以下几类。实际生产环境里的收益往往不是来自单一技术而是多个层面叠加的结果。4.1 算子融合与 Kernel 优化代表技术是 FlashAttention 系列。它通过融合注意力计算中的多个算子减少 GPU 显存读写次数大幅提升计算效率。在 Prefill 阶段注意力计算是主要耗时点FlashAttention 的收益非常明显。这类优化通常由推理框架直接集成。使用 vLLM 或 SGLang 时默认就会启用高性能 kernel不需要额外配置。需要关注的是某些参数设置可能会关闭这些优化比如 vLLM 的--enforce-eager会禁用 CUDA Graph从而影响执行效率。4.2 分块预填充Chunked PrefillChunked Prefill 的思路是把一个很长的 Prefill 请求拆成多个小段让调度器可以更灵活地插入 Decode 请求避免长请求阻塞整个批次。这种策略在混布场景下非常有用。比如一个长文档处理请求和一个短问答请求同时到达如果没有分块短问答必须等待长文档 Prefill 全部完成使用 Chunked Prefill 后调度器可以在长文档处理间隙插入短问答的计算短请求的 TTFT 会明显下降。但分块不是越细越好。分块过细会增加调度开销和 kernel 启动次数反而降低整体吞吐。分块大小需要根据模型、硬件和请求分布来调优。4.3 前缀缓存Prefix Caching实际业务中很多请求的 Prompt 前缀是相同或高度相似的。比如 RAG 场景里系统提示词、检索到的公共文档片段、Agent 的工具说明都可能重复出现。Prefix Caching 的作用就是缓存这些已经计算过的前缀对应的 KV Cache新请求到达时直接复用跳过重复计算。vLLM 提供--enable-prefix-caching参数SGLang 通过 RadixAttention 实现自动前缀复用。这里的关键差异是SGLang 的 RadixAttention 把前缀缓存组织成树状结构可以处理更复杂的前缀复用模式而不仅限于从开头连续匹配。对 Prefill 加速来说前缀缓存是见效最快、风险最低的手段之一。因为它不改变计算精度只跳过重复计算效果完全符合预期。4.4 并行策略单卡显存不够时可以通过张量并行、序列并行等方式把 Prefill 计算分布到多张 GPU 上。vLLM 和 SGLang 都支持通过--tensor-parallel-size或--tp参数指定并行度。并行策略的主要难点在于通信开销。张量并行的粒度越细单卡计算量越小但卡间通信量越大。一般建议先看单卡能不能跑不行再上 TP不要盲目堆卡。4.5 低精度计算与量化在 Prefill 阶段使用低精度计算可以同时降低显存带宽压力和计算延迟。常见做法包括权重 INT8/INT4 量化、KV Cache 量化、激活值量化。量化选型必须结合评测。AWQ 和 GPTQ 是两种主流的权重量化方法它们对推理速度的提升路径不同。GPTQ 更强调权重误差最小化AWQ 则通过保护重要权重通道来降低精度损失。实际选型时建议在同一份业务数据集上跑一次效果评测再决定用哪种格式。4.6 关于投机解码的误区很多人会问投机解码能不能用来加速 Prefill。答案是否定的。投机解码的核心思路是让小模型先草拟多个 token再由大模型验证它的前提是 Decode 阶段逐个生成 token 的低效。Prefill 阶段本身就是并行计算一次性处理完整输入不存在“逐个生成”的低效问题所以投机解码不适用于 Prefill 加速。理解了这一点就不会在 Prefill 优化方向上走偏。5. SGLang 与 vLLM 的 Prefill 加速机制对比5.1 vLLM 的核心能力vLLM 是目前生态最成熟的大模型推理框架之一其对 Prefill 的优化主要体现在以下几个层面PagedAttention借鉴操作系统分页思想管理 KV Cache减少显存碎片提升显存利用率。Continuous Batching请求级动态调度不再等一个 batch 全部结束才调度新请求。Chunked Prefill把长 Prefill 拆分成小块与 Decode 请求交错执行降低尾延迟。Prefix Caching通过--enable-prefix-caching开启复用相同前缀的 KV Cache。CUDA Graph通过捕获 GPU kernel 执行序列来减少 CPU 调度开销默认启用--enforce-eager可以关闭。vLLM 比较适合已经熟悉 OpenAI 生态、想要快速接入生产环境的团队。它的社区活跃度高遇到问题基本都能搜到解决方案。5.2 SGLang 的核心能力SGLang 是专门面向复杂 LLM 应用场景的推理框架它在 Prefill 加速上的一个亮点是 RadixAttention。RadixAttention 用树状结构组织 KV Cache支持任意位置的前缀复用而不仅仅是从开头匹配。在 Agent 场景中多条对话共享相同的工具定义、系统提示词、知识库前缀RadixAttention 可以最大化缓存命中率。SGLang 还引入了结构化并发调度能够更高效地处理 Prefill、Decode 混合请求。对需要长上下文和复杂调用模式的场景SGLang 的调度策略有优势。5.3 怎么选对比维度vLLMSGLang生态成熟度高文档丰富社区庞大中等但增长很快前缀缓存线性前缀缓存RadixAttention 树状前缀复用与 OpenAI API 兼容性好适配 OpenAI 接口好同样提供兼容接口调度策略Continuous Batching Chunked Prefill结构化并发调度适合场景通用生产部署、快速接入Agent、复杂前缀复用、长上下文高并发实际项目里我见过不少团队先部署 vLLM遇到前缀缓存命中率上不去的场景后再切到 SGLang 做对比。如果你还不确定最稳妥的做法是在同一台机器上部署两个框架用自己的数据集跑一遍 TTFT 和吞吐测试选数据更好的那个。6. 实操vLLM 部署长上下文模型并验证 TTFT6.1 环境准备先用一个最小环境跑通流程。下面是我的建议环境版本请以实际项目为准操作系统Ubuntu 20.04 或 22.04Python3.10 及以上GPUNVIDIA 显卡推荐显存 24GB 以上CUDA11.8 或 12.1 均可建议与 PyTorch 版本匹配依赖管理conda 或 venv安装 vLLMpython -m venv vllm-env source vllm-env/bin/activate pip install --upgrade pip pip install vllm如果网络条件允许也可以使用官方 Docker 镜像docker run --runtime nvidia --gpus all \ -p 8000:8000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ vllm/vllm-openai:latest \ --model Qwen/Qwen2.5-7B-Instruct6.2 启动 OpenAI 兼容服务以 Qwen2.5-7B-Instruct 为例启动命令如下python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --max-model-len 32768 \ --gpu-memory-utilization 0.9 \ --tensor-parallel-size 1 \ --enable-prefix-caching关键参数解释如下参数作用生产建议--max-model-len控制模型最大输入长度显存充足时按业务需求设置不要盲目调大--gpu-memory-utilization控制 GPU 显存利用率单卡部署建议 0.85-0.95留出余量--tensor-parallel-size张量并行卡数单卡能跑就不要用多卡--enable-prefix-caching开启前缀缓存有公共前缀场景务必开启--enforce-eager关闭 CUDA Graph仅调试或显存紧张时使用启动后看到类似下面的日志说明服务已就绪INFO: Started server process [12345] INFO: Uvicorn running on http://0.0.0.0:80006.3 理解--enforce-eager的影响这是经常被问到的问题。--enforce-eager的作用是关闭 CUDA Graph 捕获让模型以 eager 模式执行。关闭 CUDA Graph 的好处是显存占用更低模型加载更快启动时更容易通过 CUDA 检查坏处是每次推理的 kernel 启动开销变大吞吐和延迟都会受影响尤其是短请求场景下影响更明显。生产环境默认不建议加这个参数。它更适合调试自定义模型或 kernel 时GPU 显存实在装不下模型时遇到 CUDA Graph 捕获失败时如果你因为显存不够而使用--enforce-eager更应该考虑的是量化模型或者减小--max-model-len而不是牺牲推理性能。6.4 部署量化模型显存不够跑 7B 模型时可以用 AWQ 或 GPTQ 量化版本。示例python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct-AWQ \ --quantization awq \ --max-model-len 32768 \ --gpu-memory-utilization 0.9 \ --enable-prefix-caching量化模型的主要收益是降低显存占用提高并发吞吐。但务必用业务数据做精度评测尤其注意长文本理解、数学推理、代码生成等对精度敏感的任务。6.5 用 Python 脚本测量 TTFT启动服务后用下面的脚本测量首 token 延迟。这个脚本使用流式请求记录从请求发出到收到第一个增量内容的时间# 文件路径measure_ttft.py import time import requests BASE_URL http://localhost:8000/v1/chat/completions payload { model: Qwen/Qwen2.5-7B-Instruct, messages: [ {role: system, content: 你是一个技术文档助手请基于文档内容回答问题。}, {role: user, content: 请阅读以下文档并总结要点 大模型推理优化实践。 * 2000} ], max_tokens: 512, stream: True, temperature: 0.0 } start time.time() resp requests.post(BASE_URL, jsonpayload, streamTrue, timeout300) first_token_time None for line in resp.iter_lines(): if not line: continue line line.decode(utf-8) if not line.startswith(data:): continue data line[5:].strip() if data [DONE]: break if first_token_time is None: first_token_time time.time() - start break print(fTTFT: {first_token_time:.3f}s)输出示例TTFT: 3.452s这个数字只对当前环境有意义不要直接与其他硬件上的测试值横向对比。更好的做法是固定硬件、固定模型、固定 Prompt 长度对比不同框架或不同参数配置的相对差异。6.6 如何判断 Prefill 优化是否生效如果你想确认 Prefix Caching 是否生效可以连续请求两次相同前缀的 Prompt第二次的 TTFT 应该显著低于第一次。这可以验证缓存是否命中。如果想要更精确的 Prefill 耗时可以查看 vLLM 的日志和指标比如平均 TTFT、prefill 耗时分布等。生产环境建议把指标接入 Prometheus 或 Grafana而不是只看单次请求。7. 实操SGLang 部署与参数调优要点7.1 安装与启动SGLang 的安装同样简单python -m venv sglang-env source sglang-env/bin/activate pip install --upgrade pip pip install sglang[all]启动服务python -m sglang.launch_server \ --model-path Qwen/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 8001 \ --tp 1 \ --mem-fraction-static 0.9SGLang 默认启用 RadixAttention 前缀复用不需要额外参数。启动成功的日志里会出现类似Server is up的输出。7.2 SGLang 的调优要点SGLang 在长上下文和复杂前缀复用场景下表现更好。如果你遇到下面这些情况值得试试 SGLangAgent 场景中同一份系统提示词被大量请求共享。RAG 场景中多轮对话共享同一批知识库片段。大量请求的 Prompt 前缀来自一个固定模板线性前缀缓存命中率低。SGLang 的--mem-fraction-static参数控制显存利用比例需要根据模型大小和预期并发量调整。设置过高可能导致显存不足设置过低则浪费显存。7.3 客户端验证SGLang 同样提供 OpenAI 兼容接口可以把上一节测量 TTFT 的脚本里的BASE_URL改成http://localhost:8001/v1/chat/completions逻辑完全一致。如果同时部署了 vLLM 和 SGLang建议先固定模型、固定 Prompt连续跑 5 到 10 次取 TTFT 的中位数和 P95再对比两者差异。只看一次结果很容易被冷启动、缓存状态等噪音干扰。8. 常见问题与排查思路8.1 常见问题排查表问题现象可能原因排查方式解决方案启动时显存不足模型权重 KV Cache 超出显存查看 GPU 显存占用和模型大小开启量化、减小--max-model-len、调整显存利用率长 Prompt 请求超时Prefill 计算时间过长查看服务日志确认耗时集中在 Prefill开启前缀缓存、分块预填充、适当增加并行度相同前缀请求 TTFT 没有下降前缀缓存未开启或未命中检查启动参数和日志中的缓存命中信息添加--enable-prefix-caching优化 Prompt 模板一致性使用--enforce-eager后吞吐下降CUDA Graph 被关闭对比开启前后的吞吐指标尽量移除该参数改用量化或减小上下文长度量化模型输出质量下降量化精度损失用业务数据集做离线评测换用 AWQ/GPTQ 中更合适的一种或回退到 BF16多卡启动报通信错误NCCL 配置或网络问题查看 NCCL 日志检查节点间网络验证多卡通信调整NCCL_*环境变量昇腾等非 NVIDIA 硬件上无法启动框架未提供对应后端查询官方文档确认硬件支持矩阵使用官方支持的框架版本或等待兼容适配8.2 大模型精度问题补充很多团队在 Prefill 加速时会踩精度选择的坑。简单说显存足够时BF16 是生产环境首选。显存紧张时先考虑 KV Cache 量化或权重量化不建议一上来就用 FP16因为 FP16 的数值范围在极端激活值下可能溢出。量化模型上线前必须做评测评测数据要尽量贴近真实业务分布不能只跑几个通用 benchmark。8.3 前缀缓存只对“确定性前缀”有效前缀缓存依赖请求之间前缀完全一致。如果系统提示词里带了随机时间戳、用户 ID、Trace ID 之类的动态内容前缀缓存会完全失效。实际项目中不少团队开了 Prefix Caching 后收益很小排查半天发现是 Promp 模板里混入了随机字段。修复方式是把动态字段固定到 Prompt 的末尾或者放进独立的 metadata 字段保证重复内容集中在开头。9. 最佳实践、工程建议与后续学习方向9.1 先测量再优化不要凭感觉决定优化方向。固定的测试环境、固定的 Prompt 集、固定的指标口径是所有 Prefill 优化的前提。建议至少保存三组数据优化前基线、优化后结果、不同框架对比结果。这样既方便排查问题也方便向团队汇报收益。9.2 按场景选择框架通用生产部署、团队对 OpenAI 生态最熟悉优先 vLLM。Agent 场景、复杂前缀复用、长上下文高并发优先 SGLang。两个框架都部署一遍用自己的数据做 A/B 测试是最稳妥的做法。9.3 关注容量规划Prefill 加速之后显存和带宽瓶颈可能转移到 KV Cache 管理上。上线前要计算清楚最大并发请求数、单请求平均上下文长度、峰值上下文长度的乘积确认显存规划有余量。9.4 安全与权限边界推理服务部署时必须考虑服务不能暴露在公网无认证访问至少加 API Key 或网关鉴权。用户输入需要进行长度限制防止恶意超长 Prompt 打满显存。对量化模型或新版本框架的上线要准备一键回滚方案。9.5 后续学习方向Prefill 加速是一个还在快速演进的方向。如果你想继续深入可以关注以下几个方向FlashAttention 最新版本的实现细节理解 kernel 融合的底层逻辑。KV Cache 量化与压缩技术包括 KV eviction、token merging 等。多模态模型的长上下文 Prefill 特性。国产硬件上的推理适配进展非 NVIDIA 硬件上的框架支持变化很快以官方文档为准。建议先在自己的测试环境里用一份长文档和一套固定的评测 Prompt把 vLLM 和 SGLang 的 TTFT、吞吐、显存占用三个指标完整测一遍再根据结果决定生产选型。纸上谈兵永远不如真实数据有说服力。