资讯动态

长上下文推理瓶颈与Prefill-as-a-Service资源拆分实践

发布时间:2026/8/28 20:40:09 来源:尧图企业网站定制
长上下文推理正在成为大语言模型落地中最容易被低估的性能瓶颈。当上下文长度从 8K 扩展到 128K推理系统的算力占用和显存占用并不是线性增长而是近似平方级放大。摩尔线程发布《MTT S5000 Prefill-as-a-Service 技术白皮书》核心思路是把 Prefill 阶段从整条推理链路中独立出来作为一种可调度的服务形态交付。这个方向直接指向一个长期存在但经常被忽视的问题在 GPU 上处理大模型请求时Prefill 和 Decode 的资源模型差异太大混在一起必然会产生浪费。这篇文章不逐页摘录白皮书原文而是从推理服务架构入手拆解 Prefill-as-a-Service 要解决什么问题、资源拆分为什么能降低成本、服务链路如何设计、部署验证有哪些坑以及哪些指标能提前暴露风险。读完以后即使不接触白皮书原文也能依据本文的工程思路来评估自己的推理服务是否适合拆分。1. 长上下文推理的成本瓶颈首先来自 Prefill 与 Decode 的资源错配1.1 Prefill 和 Decode 的时间占比完全不对称大语言模型生成响应是一个自回归过程。用户输入的 prompt 会一次性进入模型在隐藏层中逐层计算得到每个位置的状态和缓存这一步称为 Prefill。随后模型开始逐 token 生成结果每个新 token 都要依赖前面已经生成的所有 token这一步称为 Decode。从时间上看两者的关系并不对称。同样一段上下文Prefill 需要处理所有输入 token 的注意力计算是一次性的“并发扫描”Decode 则是串行的每生成一个 token 就需要一次完整的前向计算。可以这样类比Prefill 像把整页纸一次性扫描进系统Decode 像按顺序逐字读写。在短上下文场景里例如 1K 到 4K tokenPrefill 的耗时常常被忽略。但在长上下文场景里例如 64K 到 128K token 的文档问答、代码仓库分析、多轮长对话Prefill 阶段会因为输入 token 数量太大而变得非常耗时。可以做一个粗略估算大模型前向计算一次所需的浮点运算大致与模型参数量和 token 数成正比。一个 70 亿参数的模型处理 128K 个 token理论计算量已经达到 10 的 15 次方 FLOPs 量级这还没有算注意力机制的额外开销和中间激活值成本。当这类请求同时到达时单台 GPU 很容易在 Prefill 阶段形成排队。用表格对比两种阶段对比维度Prefill 阶段Decode 阶段输入全部上文 token例如 128K单个新生成的 token计算形态大批量矩阵乘、并行注意力小批量矩阵乘、串行注意力主要瓶颈算力FLOPs显存带宽、KV Cache 读取耗时随上下文长度近似线性增加每 token 延迟固定随生成数量累计对延迟敏感度首 token 延迟敏感每 token 间隔敏感这张表是整个 Prefill-as-a-Service 架构的理论前提两种阶段对 GPU 资源的需求并不相同。1.2 KV Cache 是长上下文场景的显存黑洞除了计算长上下文还会带来一个显存问题即 KV Cache。在注意力计算过程中模型需要把每个 token 的 Key 和 Value 缓存下来供后续 token 生成时复用。如果不缓存每生成一个 token 都需要重新计算前面所有 token 的键值成本更高因此主流推理引擎都选择缓存。KV Cache 大小可以近似表示成KV Cache 大小 ≈ 2 × 隐藏层维度 × 层数 × 序列长度 × KV 头压缩因子 × 数据类型字节数以一个 7B 参数模型为例假设隐藏层维度 4096、层数 32、采用分组查询注意力后 KV 头数为 8FP16 存储则每个 token 大约需要 128KB。当序列长度到 128K 时单个请求的 KV Cache 就已经是 16GB 量级。这还只是一个请求如果并发请求数上升KV Cache 会迅速占满显存。更关键的是Decode 阶段每次生成新 token都需要把这些 KV 数据从显存读一遍。128K 长度的请求在实际生成过程中等于反复对几十 GB 的数据做串行读取这种访问模式对显存带宽的消耗非常大。这就是长上下文推理成本高企的真正原因。计算量增大显存占用增大最终落到 GPU 上是算力、显存容量、显存带宽三个维度同时承压。1.3 混跑场景下算力和显存带宽都得不到充分利用传统推理服务通常把 Prefill 和 Decode 放在同一个 GPU 上执行。请求进入后先在这个 GPU 上完成 Prefill再在同一个 GPU 上完成所有 Decode token。这个设计实现简单但资源利用上存在明显错配。Prefill 阶段的计算量很大通常会把 GPU 的计算单元打满但在这个阶段模型权重和 KV 数据被读取的次数相对集中对显存带宽的占用并不稳定。Decode 阶段恰好相反计算量小真正的高开销来自每次迭代都要把权重和 KV Cache 读一遍。如果两种请求混在同一批中会出现两种典型问题算力需求高的 Prefill 请求把 GPU 计算单元占住导致 Decode 请求得不到及时调度或者 Decode 请求长期占用显存带宽让后续 Prefill 请求排队。无论哪种GPU 都很难持续保持高利用率。更麻烦的是长上下文请求的 Decode 阶段可能持续很长时间。一个 128K 上下文的请求生成 4096 个新 token如果每个 token 需要几十毫秒整个请求就要持续数分钟。这段时间里它占用的 GPU 无法服务其他高优先级请求资源被锁定在一个慢速阶段中。这种错配说明问题不只是 GPU 性能不够而是资源模型不匹配。拆分服务本质上是把两种资源模型分别交给不同的硬件承担。2. 拆分 Prefill 的底层逻辑计算密集型与访存密集型解耦2.1 Prefill 阶段为什么是算力驱动Prefill 阶段在计算特征上是典型的“计算密集型”。所有输入 token 组成高维矩阵模型执行的是大矩阵乘法和多头注意力中的并行计算。计算强度也就是“每次内存访问配套多少次浮点运算”在 Prefill 阶段通常很高。大量输入 token 会提高矩阵块的复用率GPU 的矩阵计算单元可以长时间保持满负荷。如果只考虑 Prefill 工作负载决定吞吐量的核心指标是 GPU 峰值算力以及算力在持续运行时的稳定度。也是因为这一点Prefill 阶段出现大量并发请求时可以做连续批处理。多个请求共享输入阶段把 token 合并成更大的 batch矩阵乘的规模越大单位 token 的计算成本越低。vLLM、SGLang 这类推理引擎中Prefill 请求往往会被打包进同一个 batch配合分页 KV Cache 来提高吞吐。如果只把 Prefill 放在一个算力强的 GPU 上它可以长时间处于高算力利用状态单位时间能处理更多输入 token。这样算力资源不必为后续的慢速 Decode 过程空等。2.2 Decode 阶段为什么是带宽驱动Decode 阶段每个 step 只生成一个 token输入是从单个 token 对应的向量开始计算量很小。但模型仍然需要访问全部权重以及当前请求全部历史 token 的 KV Cache。在长上下文场景中KV Cache 规模远大于输入向量本身。因此每次生成一个 token 的时间主要由“从显存读取权重和 KV Cache 的耗时”决定。更准确的说法是Decode 阶段的算术强度很低瓶颈在显存带宽而不是峰值算力。如果分开评价Prefill 适合用“每秒钟处理多少输入 token”来衡量Decode 则要看“每秒钟能完成多少次显存读取”。当一个 GPU 同时承接两类请求这两类指标会互相干扰。举一个极端情况如果把 Prefill 和 Decode 放在同一张 GPU 上而这颗 GPU 的算力很强、显存带宽相对有限那么 Decode 请求在长上下文压力下会把带宽耗尽。这时即使纯 Prefill 请求进入也会感觉 GPU“变慢了”因为访存已经成为整机的主要矛盾。2.3 从“单服务处理完”到“服务化拆解”的转变传统的单服务设计是这样一个请求链路请求进入 - Prefill 计算 - 迭代生成 token - 返回完整结果整个生命周期在一个进程和一个 GPU 上下文里完成。拆分成 Prefill-as-a-Service 后链路变成了请求进入 - Prefill 服务计算并生成 KV Cache - 将 Cache 传给 Decode 服务 - Decode 服务逐 token 生成 - 返回结果看起来只是多了一个“传 Cache”的环节但可行性需要两个前提Prefill 和 Decode 可以在不同 GPU 上运行。KV Cache 可以序列化并且能从 Prefill 服务传递到 Decode 服务。当一个请求完成 Prefill 后输出不是最终文本而是一份“状态缓冲区”通常包含各层的 Key、Value 张量以及采样所需的位置信息和随机数状态。Decode 服务拿到这份状态后就可以从第 0 个新 token 开始生成不需要重新读入原始 prompt。这种设计把原本耦合在单个 GPU 上的“高算力需求”和“高带宽需求”拆成两段。Prefill 服务可以按算力需求扩容Decode 服务可以按显存带宽或 KV Cache 容量扩容两个队列各自独立不会因为一种请求阻塞而拖累另一种请求。这就是“Prefill-as-a-Service”字面意思的来源Prefill 不再只是一个内部步骤而是被抽象成一种可以独立部署、独立调度、独立计费的服务。3. Prefill-as-a-Service 的服务链路由与关键设计3.1 一次请求流转的完整链路一个可工作的 Prefill-as-a-Service 至少需要三个部分入口网关接收请求判断走 Prefill 服务还是直接从已有 Cache 续写。Prefill Worker执行 Prefill 计算生成 KV Cache 和中间状态。Decode Worker读取 KV Cache执行逐 token 生成。入口网关的调度逻辑可以简化成这样def route_request(prompt, session_state): if session_state is None: # 第一次处理走 Prefill kv_cache prefill_service.execute(prompt) session_id cache_store.register(kv_cache) return decode_service.generate(session_id) else: # 已有缓存直接从 Decode 续写 return decode_service.generate(session_state.session_id)这段伪代码展示了一个关键点网关并不关心 KV Cache 的物理形态只关心“当前请求有没有可复用的 Prefill 结果”。在有状态场景中例如多轮对话、流

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价