前段时间,同事跟我吐槽,他们用 4 张国产加速卡部署了一个 Qwen 模型,想让 AI 审查一份一万多字的合同,结果请求一直等不到响应,最后接口超时了。
这个问题我并不陌生。
此前,我在 8 张高端推理卡上部署 DeepSeek,也遇到过类似的情况。Agent 执行任务时需要压缩上下文,结果压缩请求频繁超时。后来换成 Kimi API,遇到特别长的上下文,响应速度也不总是理想。
硬件不一样,模型不一样,部署方式也不一样,结果却有些相似。
这让我想弄明白一件事:当 AI 处理一大段材料时,究竟慢在哪里?如果已经有多张推理卡,为什么还会卡在第一步?
先说结论:长文本推理确实有一个容易被忽略的瓶颈,叫 Prefill。但接口超时不一定都是它造成的。要解决问题,得先把耗时拆开,再决定应该优化哪一部分。
一、AI 还没开始回答,可能已经忙了很久
我们平时使用 AI,通常只看到一个过程:输入问题,等待回答。
但从模型的角度看,至少可以把工作分成两个阶段。
第一个阶段叫 Prefill,预填充。
可以把它理解成读材料。你问 AI 一个问题,同时附上一份万字合同。模型必须先处理合同内容和你的指令,建立后续生成回答需要的内部状态。
第二个阶段叫 Decode,解码。
材料处理完以后,模型才开始逐个生成 Token。你看到屏幕上不断出现文字,就是这个阶段。
这两个阶段的计算特点不一样。
Prefill 通常可以并行处理大量输入 Token,计算比较集中;Decode 则要根据已经生成的内容继续预测下一个 Token,一个接一个往外输出。
打个比方:
Prefill 像律师收到一摞材料,先把案卷看一遍;Decode 像律师开始写审查意见。
如果案卷特别厚,律师还没开始写,前面就已经花了不少时间。
所以,用户感觉 AI 一直没有反应,不一定是模型不会回答,也可能是它还在处理输入。
为什么输入越长,计算压力越大?
这里涉及一个关键技术:注意力机制,也就是 Attention。
模型读到一个词时,需要结合上下文判断它的含义。例如,合同里前面写了“甲方”,后面又出现“委托方”,模型需要结合上下文理解这些称呼之间的关系。
在标准的全注意力机制中,输入序列中的每个位置都需要与其他位置建立注意力关系。随着 Token 数量增加,注意力计算的开销会明显上升,其中一部分计算量随序列长度呈平方增长。
假设输入长度从 1 万个 Token 增加到 2 万个 Token,那么与长度平方相关的那部分注意力计算量,理论上可能增加到原来的 4 倍。
但这里有个容易误解的地方:这不等于整个模型的处理时间一定增加 4 倍。
实际耗时还取决于模型架构、硬件算力、注意力内核、并行策略和输入长度。不同模型的表现可能差很多,不能只凭一个公式判断。
不过,长输入会带来更大的计算压力,这是优化 Prefill 的重要原因之一。
还有一件事需要说明:一万多个中文字不等于一万个 Token。中文、数字、英文、合同编号和特殊符号经过不同模型的分词器处理后,Token 数量可能有明显差异。因此,排查问题时应记录实际输入 Token 数,而不是只看文档字数。
二、接口超时之前,先查清楚时间花在哪里
这里有一个常见误区:只要输入很长,超时就一定是 Prefill 太慢。
实际上,从发起请求到模型返回第一个 Token,中间可能发生很多事情:
- 请求在网关或服务队列中等待;
- 推理框架正在处理其他请求;
- 模型需要执行 Prefill;
- 多卡之间需要交换数据;
- 服务端生成了首个 Token,但网络或中间代理还没有及时返回。
因此,第一步不是换模型,也不是马上增加显卡,而是把时间测出来。
建议至少记录以下指标:
| 指标 | 通俗解释 | 主要排查方向 |
|---|---|---|
| 请求排队时间 | 轮到模型干活之前等了多久 | 并发量、调度器、服务负载 |
| TTFT | 从请求发出到收到第一个 Token 的时间 | 排队、Prefill、网络和首 Token 返回 |
| TPOT | 生成过程中,相邻 Token 平均间隔多久 | Decode 性能、显存带宽、并发 |
| 总耗时 | 从请求开始到完整回答结束 | 输入长度、输出长度、前面所有环节 |
| 输入 Token 数 | 模型实际需要处理多少输入 | 文档长度、历史对话、Prompt 设计 |
| 缓存命中率 | 以前处理过的内容有多少能够复用 | 前缀是否一致、缓存容量和驱逐策略 |
TTFT 是 Time To First Token 的缩写,也就是首 Token 延迟。
要注意,它并不等于纯粹的 Prefill 耗时。排队、调度和网络都会影响这个数字。
一个简单的排查实验
拿同一个模型做三组测试:
- 输入一段很短的文本。
- 输入完整合同。
- 保持合同不变,连续提问几次。
每组都测试多次,记录 TTFT、总耗时和服务端日志;同时记录输入 Token 数、并发量以及缓存是否命中。最好分别比较低并发和实际业务并发下的结果。
如果短文本也慢,就要先检查排队、服务状态、调度和网络,不要急着把问题归结为长文本。
如果短文本很快,完整合同明显变慢,而且服务端数据显示 Prefill 时间随输入长度显著增加,那么优化输入和 Prefill 才更有针对性。
如果第一次请求慢,后续请求明显变快,还要检查是不是前缀缓存发挥了作用。
这一步很重要,因为不同瓶颈需要不同的解法。否则,很容易花钱升级硬件,最后发现真正的问题只是请求排队,或者每次调用都把相同材料重新读了一遍。
三、先别动服务器,普通团队可以先做这三件事
如果业务只是让 AI 审查合同、分析报告或总结会议记录,我建议先从调用方式入手。
第一招:不要每次都把整份合同塞进去
假设你要检查一份一万多字的合同,真正关心的可能只是付款期限、违约责任、保密义务和争议解决条款。
如果每次都把全文交给模型,等于为了检查一个条款,让它重新处理整份材料。
更合理的办法是先建立合同索引,再根据审查任务定位相关条款。
例如:
- 检查付款风险,优先定位付款条件、账期、逾期责任和发票条款;
- 检查违约责任,定位违约情形、赔偿范围、责任上限和免责条款;
- 检查合同终止,定位解除条件、通知期限和终止后的义务。
这属于检索增强生成(RAG)或更简单的条款检索方案。
这里有个细节:合同审查不能只靠关键词搜索。付款条款可能影响违约责任,解除条款也可能影响赔偿。因此,应该先定位相关条款,再补充必要的关联条款,必要时让模型进行全文级的最终核对。
目标不是把合同越删越短,而是让模型优先处理真正相关的内容。
如果输入从 1 万 Token 减少到 2 千 Token,与序列长度平方相关的注意力计算部分,在其他条件相同的理想化比较下可以降到原来的约 4%。但这不是整个推理耗时的预测值,也不代表实际端到端延迟一定能缩短 25 倍。
实际效果需要测试,而且不能为了提速把关键合同条款删掉。
第二招:相同材料不要反复从头读
假设你要对同一份合同连续问五个问题:
- 有没有不合理的付款条件?
- 违约责任是否对等?
- 保密条款有没有漏洞?
- 合同能否提前终止?
- 哪些条款需要法务重点关注?
如果每次请求都包含完全相同的合同和系统提示词,模型可能需要反复处理大量重复输入。
这时可以考虑 前缀缓存(Prefix Caching)。
它的原理不复杂:模型第一次处理一段输入时,会生成后续计算需要的中间状态,也就是 KV Cache。只要后续请求中有足够长、完全匹配的前缀,系统就有机会复用已经计算好的部分,而不是重新处理全部内容。
可以把它理解为:第一遍认真读完合同并做好笔记,后面的问题不必每次都从第一页开始。
不过,前缀缓存不是把任意两段相似的文字都自动合并。
通常需要关注以下几点:
- 合同正文和固定提示词尽量放在稳定的位置;
- 不要每次都改变前缀中已有内容;
- 检查推理框架是否启用了相应功能;
- 观察缓存命中率和缓存占用情况;
- 确认模型、租户和请求之间的缓存隔离符合安全要求。
例如,SGLang 的 RadixAttention 支持按前缀复用 KV Cache;vLLM 也提供自动前缀缓存能力。具体参数和行为要以实际使用的版本为准。
这里还有一个容易忽略的问题:缓存主要减少重复输入的计算,不会自动让模型的全部回答过程变快。第一次处理新合同,仍然需要付出相应的 Prefill 成本。
第三招:大任务拆开做,最后再汇总
如果你的目标是生成一份完整的合同审查报告,就不一定要让一个请求完成所有事情。
可以拆成几个阶段:
- 提取合同目录、主体、金额和关键日期;
- 分别审查付款、违约、保密、知识产权和终止条款;
- 将各部分的问题、原文引用和风险等级汇总;
- 进行一次交叉核对,生成最终报告。
这样,每个子任务的输入更短,问题也更明确。
但拆分任务并不意味着总耗时必然更短。如果每个子任务都重复传入完整合同,反而可能增加总计算量。因此,最好让子任务共享必要的条款索引和原文引用,只传递完成当前任务所需的材料。
合同审查还必须保留原文证据。模型给出“存在违约风险”的结论时,应能定位到具体条款,而不是只输出一个没有出处的判断。
这三种方案的共同点是:先把调用设计好,再考虑怎样让服务器跑得更快。
对于很多文档分析任务,这往往是最值得优先尝试的方向。但具体能解决多少超时,仍然需要通过业务测试确认,不能直接套用一个固定比例。
四、调用方式优化之后,再看推理框架
如果输入已经尽可能精简、重复内容也能复用,但高并发时仍然很慢,就需要检查推理服务本身。
这一层主要有三个方向:分块调度、缓存策略和计算资源分工。
1. Chunked Prefill:别让一个长请求堵住所有人
想象一下,推理服务正在处理一份很长的合同。
这时,另一个用户只问一句“帮我解释一下违约金是什么意思”。
如果调度方式不合适,长请求可能占用大量计算资源,让短请求也跟着等待。
Chunked Prefill,也就是分块预填充,试图解决这个问题。
它把长输入拆成多个较小的计算块,让推理框架有机会在处理长输入的过程中安排其他请求,避免长 Prefill 一次性占用过多调度资源。
可以把它理解成:不让一个人抱着整车货物堵在收银台,而是分批处理,给其他人留出插队的机会。
vLLM 的官方文档说明,在 V1 引擎中,Chunked Prefill 在适用情况下默认启用,并通过 max_num_batched_tokens 等配置控制调度行为。实际效果仍取决于版本、配置和工作负载。[1]
但要注意,Chunked Prefill 主要解决的是调度和请求之间的资源竞争,并不等于让一份合同本身的计算量凭空消失。
如果系统只有一个请求,输入本身就特别长,那么分块也未必能显著缩短这个请求的 TTFT。
因此,它更适合解决“长请求拖慢其他请求”的问题,而不是所有长文本超时的万能解药。
2. Prefix Cache:让重复计算少发生几次
前面说过前缀缓存的基本原理。在推理框架中,它不仅是一个调用层面的优化,还涉及缓存管理策略。
vLLM 的自动前缀缓存通常围绕可复用的 KV Cache 块工作;SGLang 的 RadixAttention 则通过树形结构管理和匹配前缀。
两者都能减少重复前缀的计算,但具体的数据结构、缓存粒度、驱逐策略和调度行为不同,不能简单概括成“谁一定更快”。
尤其在 Agent 场景中,大量请求可能共享系统提示词、工具说明和任务模板。前缀缓存有机会减少这些重复内容的处理成本。
不过,真正值得比较的不是功能列表,而是你的业务命中率。
如果每次请求都换一份全新的合同,缓存能复用的内容可能有限;如果一个 Agent 围绕同一份材料连续工作,收益就可能更明显。
上线之前,建议用真实请求分布测一下缓存命中率、显存占用、TTFT 和整体吞吐量。缓存并不是越大越好,它还会占用有限的显存或其他存储资源。
3. Prefill 和 Decode 分开部署:让两个岗位各干各的
如果服务规模已经很大,还可以考虑把 Prefill 和 Decode 分别部署在不同的计算资源上。
这就是常说的 PD 分离,也就是 Prefill-Decode Disaggregation。
为什么要拆?
因为两个阶段的资源需求不完全一样。Prefill 更容易形成集中、密集的计算负载;Decode 则需要持续生成 Token,对显存带宽和逐 Token 的响应速度更加敏感。
如果让两种工作混在同一组设备上,系统就需要在它们之间分配资源。有些业务更关注首 Token 延迟,有些更关心生成过程是否稳定,统一调度不一定能同时满足所有目标。
PD 分离的思路是:
- 一组设备主要负责读取输入、完成 Prefill;
- 另一组设备主要负责 Decode;
- Prefill 完成后,把生成所需的 KV Cache 传递给 Decode 侧。
代价也很明确:系统更复杂了,还要承担 KV Cache 传输、网络通信、资源调度和故障处理的成本。
因此,PD 分离不是每个私有化部署项目都值得采用的方案。单机、小规模、低并发的服务,通常应该先评估更简单的优化方式。
月之暗面开源的 Mooncake 是这一方向中比较有代表性的项目,也是 Kimi 服务架构相关研究的重要组成部分。
在 USENIX FAST 2025 发表的论文中,研究团队使用真实请求轨迹进行评估,报告称 Mooncake 相比基线方案,在满足服务延迟目标的条件下,有效请求处理能力提升了约 59% 至 498%;论文还报告,在实际部署中,相关架构让 Kimi 在 A800 和 H800 集群上分别能够处理多 115% 和 107% 的请求。[2]
这些数字很有参考价值,但不能直接理解为“接入 Mooncake 后,任何系统都能提速几倍”。收益与请求长度、缓存复用、硬件、网络、基线方案和服务目标有关。
如果自己的服务每天只有少量合同审查请求,就不应为了追求论文里的峰值数据,先把系统复杂度翻上去。
五、再往上走:让多张卡一起读一份材料
前面讨论的优化,大多是在减少输入、复用计算,或者调整不同请求之间的资源分配。
还有一种思路,是让多张卡共同处理同一份长输入。
这类方案可以涉及上下文并行(Context Parallelism)等技术,但要区分:把模型分布到多张卡上,不等于把一份输入平均切开,每张卡独立读一段就能得到正确结果。
原因在于,模型处理上下文时,不同位置之间存在依赖关系。分块计算之后,还需要通过通信、数据交换或特定的注意力计算方式,保证各部分能够正确协作。
这种方法可能提高长上下文场景下的计算效率,但也带来额外开销:
- 卡与卡之间需要交换数据;
- 通信会消耗时间和带宽;
- 切分策略需要与模型架构匹配;
- 并行规模扩大后,通信开销可能抵消一部分计算收益。
所以,选择这种方案之前,要先确认当前推理框架、模型架构和硬件是否支持相应的上下文并行实现。
特别是国产加速卡环境,不同芯片的通信能力、算子支持和推理框架成熟度可能差异很大。不能直接把某个 NVIDIA 集群上的基准成绩套用到另一套硬件上。
如果团队已经拥有多卡服务器,并且长输入是稳定、频繁的业务负载,可以把它列入压测清单。但如果只是偶尔审查一份合同,优先考虑减少输入和避免重复计算,通常更容易落地。
六、真正改变游戏规则的方向:让模型不必精读每个 Token
前面这些方法,大体上仍然在优化现有的输入处理过程。
但还有一个更根本的问题:模型是否一定要让每个位置都与上下文中的所有其他位置建立注意力关系?
这也是稀疏注意力(Sparse Attention)受到关注的原因。
可以想象一下,人拿到一份两百页的合同,不会在检查付款条款时,把每一页都逐字精读一遍。通常会先浏览目录、搜索关键词,再集中阅读相关内容。
稀疏注意力尝试在模型内部减少需要进行精细注意力计算的位置或关系,从而降低长上下文处理的计算压力。
以 DeepSeek 的 DeepSeek Sparse Attention(DSA)为例,它通过可训练的索引机制,先识别与当前计算更相关的部分,再对选中的位置进行更精细的注意力计算。
这与简单地把合同删短并不一样。
Prompt 压缩是在模型接收输入之前,尝试减少输入中的内容;稀疏注意力则是在模型架构内部改变计算方式。
DeepSeek 在 2025 年发布的 V3.2 技术路线中引入了 DSA。相关模型资料也明确说明了这一架构变化。[3]
不过,不能因此就认为所有长文本任务都会自动变快。
实际收益还取决于稀疏注意力的实现、推理框架是否支持、内核优化是否到位,以及模型在目标任务上的表现。对于企业部署者而言,还要考虑模型升级和迁移成本。
另外,稀疏注意力减少的是部分计算关系,并不意味着模型从此可以完全忽略不相关的信息。合同审查尤其如此:某个条款看似与当前问题无关,却可能通过交叉引用影响最终结论。
因此,即使采用了更高效的注意力架构,也仍然需要验证合同审查的准确率、条款召回率和原文引用是否可靠。
七、Prompt 压缩也能提速,但合同场景要格外谨慎
还有一种值得了解的技术:Prompt 压缩。
例如微软研究团队提出的 LLMLingua,会利用较小的语言模型识别输入中的冗余内容,尝试在保留关键信息的同时减少 Token 数量。
在其公开实验中,研究团队报告了最高约 20 倍的压缩比例,并在特定测试任务中观察到较小的性能损失。[4]
但这里有两个限制。
第一,20 倍是特定实验条件下的结果,不是所有文档都能压缩 20 倍且保持原有质量。
第二,通用问答中的冗余内容,与法律合同中的关键措辞,不能一概而论。
比如合同中的“应当”“可以”“不得”,只差一个词,法律含义就可能不同。金额、日期、责任主体、例外条件和否定词也不能随意丢弃。
因此,如果要在合同审查系统中使用 Prompt 压缩,至少需要做三件事:
- 对金额、日期、主体名称、否定词、责任条款等关键内容设置保留规则;
- 让模型的审查结论能够回溯到未经压缩的原文;
- 用人工标注的合同风险案例,比较压缩前后的审查结果。
如果压缩让模型漏掉了真正重要的条款,即使首 Token 延迟下降了,也不能算业务优化成功。
对合同、法律、金融等高风险场景来说,速度必须与结果质量一起衡量。
八、到底该怎么选?给企业部署团队一份实用清单
说了这么多技术,真正落地时,不必一次性全部采用。
我更建议按下面的顺序推进。
| 优先级 | 方案 | 主要解决的问题 | 成本与限制 | 建议 |
|---|---|---|---|---|
| P0 | 测量 TTFT、排队时间和输入 Token 数 | 找出真正瓶颈 | 需要增加日志和监控 | 必做 |
| P1 | 检索相关条款、减少无关上下文 | 输入太长、计算量过大 | 需要保证条款召回完整 | 优先尝试 |
| P1 | 前缀缓存 | 重复处理相同提示词和材料 | 依赖前缀匹配、缓存容量和命中率 | 优先检查 |
| P1 | 分阶段审查与汇总 | 单个任务过长、重试成本高 | 需要处理跨条款关联和汇总准确性 | 适合文档工作流 |
| P2 | Chunked Prefill | 长请求影响其他请求的响应 | 不能消除长输入本身的计算量 | 适合有并发的服务 |
| P2 | 调整推理框架、缓存和批处理配置 | 调度、资源利用率不理想 | 需要压测和配置调优 | 有监控后再做 |
| P3 | PD 分离 | 大规模服务中的 Prefill/Decode 资源竞争 | 系统复杂度、网络传输成本增加 | 适合规模化部署 |
| P3 | 上下文并行 | 超长上下文下单次计算压力过大 | 通信开销和框架适配成本 | 需要验证硬件支持 |
| P3 | 稀疏注意力模型 | 从模型架构上减少注意力计算 | 模型和推理框架需要支持 | 关注技术演进 |
| 谨慎使用 | Prompt 压缩 | 减少输入 Token | 可能损失关键信息 | 高风险文档先做严格验证 |
如果是我来排查这次万字合同超时
我会按下面的顺序来做。
第一步:确认输入到底有多长。
记录实际 Token 数,检查是否把历史对话、系统提示词和工具描述也一起传进去了。很多时候,用户以为自己只提交了一份合同,模型实际收到的输入却远不止合同本身。
第二步:把请求时间拆开。
记录排队时间、Prefill 耗时、首 Token 返回时间、Decode 速度和总耗时。如果能够直接获取推理框架的 Prefill 统计,就不要只依赖客户端 TTFT 来推断。
第三步:对比短输入和长输入。
如果两者差距很大,再进一步确认 Prefill 是否是主要瓶颈。如果短输入同样超时,就先检查服务状态、网络、调度和网关。
第四步:尝试条款检索和前缀缓存。
先用最小改动验证收益。对于重复审查同一份合同的场景,重点检查缓存命中率;对于一次性处理大量新合同的场景,则重点测试检索能否减少输入,同时保持关键条款的召回率。
第五步:用真实业务并发压测。
至少对比优化前后的 TTFT、P95/P99 延迟、吞吐量、超时率和审查质量。不要只测单个请求,也不要只看平均值。
如果上述优化仍然不够,再考虑调整推理框架、采用 PD 分离或上下文并行。
这样做的好处是,每一步都有数据可以判断是否值得继续,而不是一开始就投入时间改造整套推理架构。
最后:长上下文优化,先别急着加卡
回到同事遇到的那个问题:4 张国产加速卡部署 Qwen,审查一份万字合同却超时。
仅凭这条信息,还无法断定问题一定出在 Prefill,也无法判断增加显卡是否有效。
但我们至少知道该从哪里开始查。
先测输入 Token 数和 TTFT,再看排队、Prefill 与 Decode 的耗时;确认瓶颈之后,再选择减少输入、复用缓存、调整调度或升级推理架构。
对于企业 AI 工程团队来说,这个顺序很重要。
因为硬件可以增加,模型可以更换,推理框架也可以升级,但如果业务每次都把大量重复材料塞给模型,再好的硬件也可能被低效的调用方式拖住。
优化长上下文,不是单纯让模型读得更快,而是让它少做不必要的计算,并把必须做的计算安排得更合理。
这才是从一次接口超时,走向稳定生产部署的第一步。
参考链接
[1] vLLM 官方文档:Chunked Prefill 设计说明 — https://docs.vllm.ai/en/latest/design/chunked_prefill.html [2] Mooncake:以 KVCache 为中心的分离式 LLM 服务架构(Kimi / 清华 MADSys,FAST 2025 最佳论文)— https://github.com/kvcache-ai/Mooncake [3] DeepSeek-V3.2-Exp:稀疏注意力(DSA)技术报告与开源实现 — https://github.com/deepseek-ai/DeepSeek-V3.2-Exp [4] LLMLingua:微软研究院 Prompt 压缩项目 — https://github.com/microsoft/LLMLingua
