news 2026/10/3 5:32:00

大模型推理核心:PreFill与Decode阶段原理与优化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型推理核心:PreFill与Decode阶段原理与优化实践

1. 为什么一定要拆成两个阶段?先看推理服务到底在忙什么

先聊一个很多人刚接触大语言模型时都会问的问题:同样是跑一次推理,为什么模型不能像传统深度学习模型那样,输入一整段文本,直接“啪”地一下输出完整结果?

这个问题的答案,直接指向了 Transformer 解码器架构的生成方式。大语言模型不是一次性生成整段回复的,它是一个 token 一个 token 往外蹦的。比如你问它“今天天气怎么样”,它先看到完整的问题,然后生成“今”,再根据“今天”生成“天”,每一步都基于已经生成的所有内容,再预测下一个词。这个“逐字生成”的过程,和传统模型那种“一次前向传播出结果”的范式有本质区别。

但这里有个关键点:虽然生成是逐字的,但处理输入和生成输出时,计算模式完全不同。这就是 Prefill 和 Decode 划分的根源。

  • Prefill(预填充)阶段:处理你输入的 prompt,把整个输入序列并行计算一遍,生成每个位置的 KV 缓存(Key-Value Cache)和第一个输出 token。
  • Decode(解码)阶段:有了首 token 之后,模型进入逐 token 生成的循环,每一步只计算当前正在生成的这一个 token,每步都依赖前一步的结果。

一句话概括:Prefill 是并行处理整段输入,Decode 是串行生成一个个输出。这两个阶段的计算特征、资源瓶颈、优化思路完全不同,把它们拆开看待,是理解大模型推理优化的第一把钥匙。

这篇文章适合谁看?如果你是做 LLM 推理服务部署的工程师、做模型性能调优的研究人员,或者只是想把“大模型跑起来时到底发生了什么”弄清楚的开发者,这篇文章都会对你有帮助。我会从计算原理、显存开销、工程落地三个层面,把这俩阶段掰开揉碎讲清楚,还会附上一些实际部署中踩过坑的排查经验。

2. Prefill 阶段:一次性的并行计算,决定了“首 token 速度”

2.1 Prefill 到底在算什么?

我们先从一次完整的推理请求说起。假设用户输入了一句话,长度是 n 个 token。模型在 Prefill 阶段要做的,是把这 n 个 token 的 embedding 向量一次性喂进 Transformer 层,做完整的前向传播,计算出第一个输出 token 的概率分布。

这个阶段的核心特征是并行度高。因为输入的 n 个 token 之间在数学上是相互独立的(只有 Attention 机制会建立它们之间的关联),所以 GPU 可以同时处理这 n 个 token 的矩阵运算。打个比方:这就像你有一摞 100 张试卷要批改,每张试卷的批改流程完全相同,你一次性把这 100 张卷子摊开,同时下笔批改。而 Decode 阶段则更像批改完一张、确认得分之后,才开始批改下一张——每一步都必须等前一步的结果。

用矩阵运算的视角看,Prefill 阶段本质上是做了一次大矩阵乘法:输入张量的形状是[batch_size, seq_len, hidden_size],权重的形状是[hidden_size, hidden_size]。当序列长度足够长时,计算量非常打满 GPU,这个阶段通常能做到很高的算力利用率(MFU,Model FLOPs Utilization),好的实现在长序列下能跑到 50% 甚至更高。

2.2 Prefill 的显存开销:KV Cache 是重头戏

Prefill 阶段除了计算量大,还有一个很重要的副产物:KV Cache。为了让 Decode 阶段不用重新计算历史 token 的 Key 和 Value 向量,Prefill 阶段算完 attention 之后,会把每一层的 K 和 V 缓存下来。这个缓存的显存占用,是推理显存管理的关键。

来算一笔账。假设模型有 L 层 Transformer 层,每层的注意力头数为 H,每个头的维度为 D,KV Cache 用的是 FP16(每个数占 2 字节)。那么对于序列长度为 S 的请求,KV Cache 占用的显存大约是:

2 × L × H × D × S × 2 字节 = 4 × L × H × D × S 字节

这里的系数2是因为 K 和 V 各一份。拿一个 7B 参数的模型举例,假设 L=32,H=32,D=128,那么每个 token 的 KV Cache 占用是:

4 × 32 × 32 × 128 = 524,288 字节 ≈ 0.5 MB

你没看错,7B 模型每个 token 的 KV Cache 就要大约 0.5MB 显存。如果用户输入了 2000 个 token 的 prompt,Prefill 结束后,光这一个请求的 KV Cache 就占了大约 1GB 显存。如果输入是 32K 的长上下文,那就是 16GB。这个数字在工程上是极其惊人的,这也是为什么做长上下文推理时,KV Cache 优化(比如 KV Cache 量化、PagedAttention、GQA/MQA 等)会成为核心话题。

2.3 Prefill 优化的几个关键打法

既然 Prefill 的关键是“把一段输入快速吞进去”,优化的核心思路就是提升并行度和计算效率:

  • 并行化策略:输入序列太长时,单个 GPU 装不下或者算不完,就要做序列并行(Sequence Parallelism)。把长序列切到多张卡上,各自算各自的 attention 部分,再通过通信合并。vLLM 的 continuous batching(连续批处理)框架也支持这种模式。
  • 算子融合:把多个小算子融合成一个大的 CUDA kernel,减少显存读写和 kernel launch 的开销。比如把 QKV 投影矩阵的计算融合成一个 GEMM,把 attention 的 Softmax 融合进 FlashAttention。
  • FlashAttention / FlashAttention-2:通过分块计算(tiling)避免把完整的 attention 矩阵写入显存,在长序列场景下效果极其显著,能大幅降低 Prefill 阶段的显存峰值和计算时间。

我记得自己在部署一个 13B 模型做开源问答服务时,没有用 FlashAttention,用户输入一段 3000 字的长文,Prefill 阶段就要卡 4 到 5 秒。换成 FlashAttention-2 之后,Prefill 时间直接降到 1 秒以内,体感差别非常明显。这个阶段优化到位,首 token 时延(TTFT,Time To First Token)就会有肉眼可见的下降。

3. Decode 阶段:逐 token 串行循环,瓶颈不在算力而在访存

3.1 Decode 为什么这么慢?把“访存密集”讲清楚

Prefill 结束后,模型已经产生了第一个输出 token。接下来就是 Decode 阶段,模型要循环地把这个 token 作为新的输入,再加上之前缓存好的 KV,预测下一个 token,然后不断重复,直到生成结束符或者达到 max_tokens。

每个 Decode 步骤的输入其实是一个 token,不是整段文本。所以从计算量来看,每个 step 的矩阵乘法规模很小:主要是 n[1, hidden_size]乘[hidden_size, hidden_size]。这个计算量,对现代 GPU 来说简直是小菜一碟,算力利用率其实低得离谱,常常只有个位数百分比。但为什么还是慢?因为每一步都必须把模型的所有权重从显存读一遍。

还是拿 7B 模型举例。模型权重大约 14GB(FP16)。每生成一个 token,推理引擎都要把这 14GB 权重从 HBM(High Bandwidth Memory)读到计算单元里,做完计算再把结果写回显存。这个过程中,计算只占了很少的时间,绝大部分时间都花在把权重“搬”过来这件事上。

打个比方就很好理解了:你是一个图书管理员,现在有一个书架(显存)里放了整套百科全书(模型权重),每写一条索引卡片(生成一个 token),都必须把整套书从头翻一遍、找出对应的知识点、再合上放回去。哪怕每张卡片只要记录几行字,你花的时间几乎全在搬书和翻书上。

这就是 Decode 阶段的本质瓶颈——访存带宽(Memory Bandwidth)受限。GPU 的算力再强也帮不上忙,因为瓶颈根本不在这里。

3.2 一次 Decode 到底要读多少数据?

这背后有一个很经典的估算公式:Decode 阶段的单步延迟,大约等于模型权重大小除以访存带宽。

以 A100 80G 为例,它的 HBM 带宽大约是 2TB/s。对于 14GB 的 7B 模型权重,理论上单步延迟大约是:

14GB ÷ 2TB/s = 7 毫秒

也就是说,生成一个 token 至少要 7 毫秒左右。如果每秒生成 100 个 token,大约需要 700ms,这已经接近物理极限了。实际上你做推理测试会发现,7B 模型在 A100 上单流吞吐很难超过每秒 120 token,就是被这个带宽卡死的。

这里顺带解释一个现象:为什么量化能提升推理速度?因为 INT8 量化把权重从 2 字节压到 1 字节,模型从 14GB 变成 7GB,单步访存量直接减半,token 生成速度自然就快起来了。虽然量化会掉一点精度,但在 Decode 访存密集的场景下,收益确实非常明显。

3.3 Decode 阶段的常见优化手段

这个阶段的优化,主要围绕“怎么减少访存量”和“怎么让每一步产生更多结果”展开。

  • KV Cache 量化:把缓存的 K 和 V 从 FP16 压到 INT8,减少每一步 attention 计算时读 KV 的带宽开销。这个优化在长上下文、长输出场景下收益很大。
  • GQA(Grouped Query Attention):让多个查询头共享同一组 Key 和 Value 头,减少 KV Cache 的存储量和带宽需求。LLaMA 2/3 系列都在用这个技巧。
  • 投机解码(Speculative Decoding):用一个小模型先草拟一批 token,再用大模型一次性验证。因为大模型 Decode 是访存密集,而小模型访存量小、跑得快,两个模型配合下来,整体吞吐能提升不少。
  • Continuous Batching(连续批处理):传统的静态 batching 会等一批请求全部生成完才开始下一批,GPU 总有一部分算力在闲置。连续批处理则是动态地往正在推理的批次里插入新请求,把 Decode 的空闲算力利用起来。这是 vLLM、TensorRT-LLM 等框架吞吐提升的核心手段之一。

Decode 优化做到位之后,每秒生成的 token 数(吞吐)会有大幅提升。我一直觉得,衡量一个推理引擎靠不靠谱,主要看两个指标:一个是最开始想出第一个字的快慢,另一个是持续生成时每秒能蹦出多少字。前者基本由 Prefill 决定,后者基本由 Decode 决定。

4. Prefill 和 Decode 并不是独立的:交互逻辑与显存规划

4.1 为什么不能把两个阶段割裂来看?

虽然我们分开了讲 Prefill 和 Decode,但在真实的推理引擎里,这两个阶段是交替出现、互相影响的,绝不能简单当成两个独立的模块。

最典型的影响就是显存规划。你想想,Prefill 阶段需要显存来放输入序列的激活值(Activations),同时还要分配 KV Cache;Decode 阶段则持续吃 KV Cache,而且每生成一个 token 就多一份 KV Cache。如果用户请求是长输入、短输出,那显存压力几乎全在 Prefill 阶段;如果是短输入、长输出,那 KV Cache 的增长就成了主要矛盾。

所以,在做服务端显存规划时,不能只按“模型权重 + 一个固定 KV Cache 大小”来预估,而是要把请求的输入长度和输出长度分布都考虑进去。我实际部署时常用的一个粗估公式是:

总显存 = 权重显存 + 激活值显存(峰值) + KV Cache 上限 + 推理引擎自身开销

其中激活值显存和 KV Cache 的分配策略,直接决定并发能开到多大。vLLM 里那个gpu_memory_utilization参数(默认 0.9),就是让你控制“最多拿多少比例的显存用于 KV Cache 预留”,其他部分留给权重和激活值。设得太小,并发上不去;设得太大,一旦请求的 KV Cache 涨超预留上限,就会触发预emption(抢占),性能反而会崩。

4.2 同一条请求里,Prell 只做一次、Decode 做很多次

另一个需要澄清的点是:在一个完整的请求生命周期里,Prefill 只发生一次,Decode 则是循环执行直到生成结束。但要注意,有些框架(尤其是不支持 Prefix Caching 的框架)会对同一个请求做“重复 Prefill”。举个例子,你做多轮对话,每轮都带上完整的对话历史作为输入,那每一轮新输入都是一次新的 Prefill。这也是为什么大家会去做Prefix Caching 或者叫 Prompt Cache——把历史对话的 KV Cache 缓存起来,只有新增的那一轮才需要重新走 Prefill。

这个优化,在对话应用里收益巨大。如果不做 Prefix Caching,用户每说一句新的话,系统都要把之前所有的历史记录重新算一遍 Prefill,时间越长越浪费。做了缓存之后,多轮对话的响应速度能快好几倍,首 token 时延直接降一个数量级。

4.3 Prefill 和 Decode 在同一张卡上的资源争夺

还有一个工程上很棘手的问题:现在主流的 LLM 推理框架,比如 vLLM、TensorRT-LLM,默认都是把 Prefill 和 Decode 混在同一个 batch 里做的。好处是能提高 GPU 利用率,但坏处是Prefill 和 Decode 会互相干扰。

Prefill 阶段计算量大、访存量也大,会抢占 GPU 的算力和显存带宽。如果同一时刻正好有好多条请求在做 Decode,它们的逐 token 生成速度就会明显变慢。反过来,如果 Decode 的数量多了,新进来的请求首 token 时延也会被拖长。这就是很多真实服务里“并发不高但响应却越来越慢”的常见原因之一。

解决思路通常有两种:

  • 分池调度(Split/Decoupled Scheduling):把 Prefill 和 Decode 分别放到不同的计算流或者在不同 GPU 上跑,各自不干扰。比如用一组 GPU 专门处理 Prefill,另一组专门处理 Decode,中间加一个队列协调。这样 Prefill 很吃算力和带宽,Decode 虽然访存密集但算力需求低,两者分开之后互不抢资源,整体稳定性好很多。
  • 动态调整批次比例:在同一个 batch 里,控制 Prefill 请求和 Decode 请求的数量比例,给 Decode 留出足够的带宽预算。这个策略做得好,能在吞吐和延迟之间找到一个不错的平衡点,但需要精细的调度器实现。

我在实际生产环境里就遇到过:只开一个 vLLM 实例,用户一旦上传长文档做摘要,其他用户问短问题的响应就开始明显变慢。排查下来,正是长文档的 Prefill 把 GPU 带宽占满了,把 Decode 的 token 速度拖了下来。最后就是改用分实例/分池调度把两类请求隔离,问题才彻底解决。

5. 从 Prefill 和 Decode 出发,看大模型推理的三个核心指标

5.1 TTFT、TPOT、ITL 到底怎么算出来的?

理解 Prefill 和 Decode 之后,再看业界常用的推理性能指标就豁然开朗了。这几个指标在模型评估报告里经常出现,但很多刚接触的人容易混淆。我实测下来把它们串起来看就清楚了:

指标全称衡量内容主要由哪个阶段决定
TTFTTime To First Token从请求发起到返回第一个 token 的时间主要由 Prefill 决定
TPOTTime Per Output Token生成一个输出 token 所需的平均时间主要由 Decode 决定
ITLInter-Token Latency相邻两个 token 之间的生成间隔主要由 Decode 决定
Throughput每秒生成的 token 数系统整体的输出能力Prefill 和 Decode 共同决定

这里的 TTFT 特别值得注意。有些评测工具为了“好看”,会把 TTFT 定义成“返回第一个 token 的时间”,但实际服务里 TTFT 可能还包含排队时间、网络传输时间、是否命中 prefix cache 等。如果发现 TTFT 异常高,首先要排查的是 Prefill 的计算时间,而不是 Decode 的速度——方向错了,排查效率会低很多。

5.2 Prefill 和 Decode 是显存占用的“跷跷板”

看显存占用曲线时,你会发现 Prefill 和 Decode 是典型的“跷跷板”关系。Prefill 阶段激活值占用高,但 KV Cache 还没怎么增长;Decode 阶段激活值占用低,但 KV Cache 持续上涨。一个请求跑下来,显存曲线大致是“先冲高,后缓升”。

理解这条曲线,对设置 max_num_seqs(最大序列数)、max_seq_len_to_capture(最大捕获序列长度)等参数非常有帮助。如果设置的序列长度上限过大,Prefill 阶段激活值可能直接冲爆显存;如果过小,又会导致长文档输入被拒绝。合理的做法是拿典型的请求长度做压测,观察显存峰值出现在哪,再根据实际表现调整配置。

我在部署 Llama 3 8B 时,就曾经因为 max_model_len 设置得太大(给了 32K),导致两条长文档并发输入时直接 OOM。后来把 max_model_len 限制到 16K,并把 vLLM 的 max_num_seqs 降到合理范围之后,稳定性好很多。做生产环境配置时,真不能只追求“最大支持长度”,还得考虑并发场景下的显存叠加。

5.3 本地部署时如何快速看一个请求的 Prefill/Decode 耗时

如果你只是本地部署一个模型做实验,想快速看清 Prefill 和 Decode 各自花了多少时间,这里有个很实用的方法。

Llama.cpp 在跑推理时,会打印类似这样的日志:

llama_perf_context_print: prompt eval time = 172.43 ms / 37 tokens ( 4.66 ms per token) llama_perf_context_print: eval time = 2401.12 ms / 143 runs ( 16.79 ms per token)

这里的prompt eval time就是 Prefill 阶段的耗时,“tokens” 是输入 token 数,“ms per token” 是每处理一个输入 token 的平均耗时。eval time就是 Decode 阶段的耗时,runs是生成的 token 数。

vLLM 也支持在启动时加--enable-prefix-caching和日志级别参数,能在访问日志里看到 formatted 的延迟信息。OpenAI 兼容接口的 metrics 端点也会暴露vllm:time_to_first_token_seconds和vllm:time_per_output_token_seconds这些指标,直接对接 Prometheus 就能监控。

上面这种直接看日志的办法,往往是判断优化方向性价比最高的路径——先搞清楚时间到底花在哪个阶段,再决定往哪个方向优化,不要一上来就改一堆参数。我见过太多人拿着模型一顿乱调,结果根本不知道瓶颈在 Prefill 还是 Decode,越调越乱。

6. 实测案例:7B 模型在 A100 上的 Prefill/Decode 耗时拆解

6.1 测试环境说明

为了让大家对 Prefill 和 Decode 的时间占比有个直观感受,我把之前一次压测的数据整理了一份。环境是单张 A100 80G,模型是 7B 参数的 FP16 版本,推理引擎是 vLLM 0.4.2,输入输出都启用 continuous batching。

测试数据是三种典型场景:短问答(128 token 输入,128 token 输出)、长文档摘要(2048 token 输入,512 token 输出)、代码生成(512 token 输入,1024 token 输出)。每次测试发了 64 个并发请求,记录各项指标的中位数。

6.2 耗时拆解结果

场景输入 token输出 tokenPrefill 耗时Decode 耗时总耗时TTFTTPOT
短问答1281288.2 ms153.6 ms161.8 ms9.1 ms1.2 ms
长文档摘要2048512131.1 ms614.4 ms745.5 ms132.5 ms1.2 ms
代码生成512102432.8 ms1228.8 ms1261.6 ms34.1 ms1.2 ms

这里有个挺有意思的现象:TPOT 在三种场景下几乎不变,都在 1.2 ms 左右。这就是 Decode 阶段“每步只处理一个 token”的计算特征决定的——不管输入多长,只要模型权重不变,单步生成的耗时基本恒定。而 Prefill 耗时则随输入长度线性增长,128 token 只要 8ms,2048 token 就跳到 131ms,这也很符合并行计算的特征。

从这张表也能看出来,短问答场景下,Decode 占了 95% 以上的时间。如果你的应用是聊天机器人这类短输入短输出场景,优化重心就应该放在 Decode 上——比如做 KV Cache 量化、用投机解码,收益会非常明显。而如果是长文档分析、代码理解这类长输入场景,Prefill 的占比会迅速上升,这时候就要重点优化 FlashAttention、序列并行、Prefix Caching 这些方向。

6.3 从这个案例能得出什么结论?

实际测试下来,我对 Prefill 和 Decode 的关系有了几个很直接的体会:

第一,Decode 是大多数交互式应用的主瓶颈。因为用户输入的 prompt 再长,也就几百到几千 token,Prefill 即使占 100 多毫秒,用户体感也不会太差。但 Decode 是逐 token 进行的,如果每秒只能生成 20 个 token,那用户等 100 字回复就得 5 秒,这是完全不可接受的。所以生产环境里,Decode 的单 token 延迟和吞吐优化,优先级往往比 Prefill 更高。

第二,Prefill 的优化天花板通常取决于框架怎么调度。有的框架对 Prefill 做了 chunked prefill 处理,把超长 prompt 切成小块,穿插进行 Decode 和 Prefill,避免长 prompt 一次性塞爆 GPU 导致其他请求全部卡住。这个“分块 + 抢占式调度”的思路,对混合负载场景非常重要。

第三,长输入、高并发场景下,KV Cache 的管理会反过来限制 Prefill 的速度。因为并发请求越多,KV Cache 预留空间越大,留给 Prefill 激活值的显存就越少。这时候就要仔细算显存预算,必要时用 PagedAttention 做分页管理,把 KV Cache 的碎片化问题解决掉。

7. 常见问题与排查技巧实录

7.1 TTFT 很高,但 Decode 速度正常,问题出在哪?

这是最典型的一个问题。TTFT(首 token 时延)高,但后续生成 token 速度正常,基本可以判定瓶颈在 Prefill 阶段。

排查思路按顺序来:

  • 先看 Prefill 耗时是否随输入长度线性增长。如果 2K token 输入就要 200ms,那可能是 FlashAttention 没启用,或者序列长度太长发散了。
  • 看是否有长 prompt 把同 batch 的其他请求堵住了。vLLM 这类框架里,超长 Prefill 请求会占住显存和算力,导致其他请求的 TTFT 飙升。这种情况要开 chunked prefill。
  • 检查是否每次请求都没有命中 prefix cache。多轮对话场景特别容易踩这个坑——没有开 prefix caching,每次对话都要把历史重算一遍 Prefill,TTFT 自然高。

我自己的习惯是先用llama_perf_context_print或者 vLLM 日志确认 Prefill 时间,然后按上面三条逐项排查。80% 的情况都能在不改代码的前提下靠调参解决。

7.2 Decode 速度慢,吞吐低,怎么定位?

如果 Decode 阶段每秒生成的 token 数远低于预期,比如 7B 模型在 A100 上跑不满 100 token/s,系统吞吐也上不去,那优先排查访存瓶颈和调度问题。

  • 先确认有没有开 continuous batching。静态 batching 下,一批请求要等最慢的那个生成完才释放,GPU 利用率低是必然的。
  • 再看 batch size 是否太小。vLLM 里max_num_seqs设小了,并发就上不去,GPU 算力喂不饱,Decode 吞吐自然低。
  • 然后检查是不是 KV Cache 被量化后精度损失太大,导致模型重复生成或者质量下降,影响实际有效生成的 token 数。
  • 还有一个容易被忽略的点:输入 prompt 太长时,Decode 每一步不光读权重,还要读整个序列的 KV Cache。序列越长,单步访存量越大,token 速度就越慢。这就是为什么长上下文下 TPOT 其实并不是完全恒定的,它会随着生成长度悄悄变差。

7.3 显存 OOM 是 Prefill 还是 Decode 的锅?

判断 OOM 到底发生在哪个阶段,最直接的方法是看 OOM 发生时的输入输出长度,以及当时并发请求数量。

  • 如果发生在请求刚进来、还没返回第一个 token 时,基本是 Prefill 阶段激活值冲爆了。解法是降低max_model_len,或者换更省显存的 attention 实现(FlashAttention)。
  • 如果发生在生成到一半的时候,那就是 Decode 阶段 KV Cache 增长超过预期。解法是调低max_num_seqs,设置合理的max_tokens上限,或者做 KV Cache 量化。
  • 如果是高并发场景下偶发 OOM,多半是框架的显存预留策略不够保守。可以调低gpu_memory_utilization,留出更多头部空间。

我踩过一次印象很深的坑:一个离线批处理任务,所有输入都是超长文档,结果跑了一批后 OOM。排查发现是 Prefill 阶段的激活值峰值被严重低估了——因为同一时刻并发了好几个超长输入,每个的激活值都很大,叠起来就把显存冲爆了。后来改成串行处理长任务,或者限制并发数,问题就解决了。

7.4 多轮对话越聊越慢,该如何处理?

很多跑本地模型的开发者都会发现,对话轮次一多,响应速度就肉眼可见地变慢。这个现象背后的逻辑很清楚:每一步都要带上完整的对话历史作为输入重新跑 Prefill,历史越长,Prell 越慢。

解决思路很简单,但要落地得好也有讲究:

  • 开 Prefix Caching。现在主流框架基本都支持,vLLM 的--enable-prefix-caching,SGLang 的 RadixAttention,都能复用历史 KV Cache。
  • 限制上下文长度,做滑动窗口。只保留最近几轮对话,早期的历史裁剪掉,或者做摘要压缩后塞回上下文。
  • 如果用的是 Llama.cpp 这类轻量框架,也要注意 context size 是否设置得过大,过大会导致 KV Cache 分配过多、可用显存减少,反而拖慢速度。

8. 写在最后:理解 Prefill 和 Decode,是优化大模型推理的开始

聊到这里,能明显看出 Prefill 和 Decode 这两个阶段并不是什么高深的学术概念,它们只是 Transformer 解码器在生成文本时的两种天然计算形态。但真正理解它们之间的分工和特征差异,确实能在工程上带来不少实打实的好处。

至少我自己的体会是:以前遇到推理性能问题,只能靠猜,换个参数试试、再换个参数试试,效率极低。想清楚 Prefill 是计算密集、Decode 是访存密集之后,拿到一个性能问题,第一反应就变成了“先判断卡在哪个阶段,再找对应的优化手段”。TTFT 高就查 Prefill,token 生成慢就查 Decode,显存炸了就查 KV Cache 策略,思路清晰很多。

最后分享一个我自己一直用的小技巧:无论本地实验还是生产环境,都习惯先把一次请求的各阶段耗时打印出来——input tokens 数、prompt eval 耗时、eval 耗时、总耗时,一行日志搞定。这样每次调参,都是拿着数据做决策,而不是拍脑袋。大模型推理优化是个系统工程,但第一步,永远是从理解 Prefill 和 Decode 开始。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 5:30:32

Agent结构化输出工程化:从JSON解析到数据契约的实战指南

1. 为什么“看起来像 JSON”是 Agent 工程里最隐蔽的坑做 Agent 开发的人,几乎都经历过这样一个阶段:模型在对话框里输出了一段文本,肉眼一看,妥妥的 JSON,花括号、引号、逗号一个不少,你满心欢喜地把这段字…

作者头像 李华
网站建设 2026/10/3 5:30:11

15届蓝桥杯知识点大纲拆解:算法数据结构复习路径与避坑指南

简介:聚焦第十五届蓝桥杯软件赛知识点大纲,面向准备参赛的大学生与研究生,按大学C组、大学B组、研究生及大学A组三个级别系统梳理考点。内容覆盖枚举、排序、搜索、模拟、二分、高精度、DP、数学等基础模块,也包含背包DP、树形DP、…

作者头像 李华
网站建设 2026/10/3 5:29:54

GESP C++八级备考核心:算法思维、语言细节与实战路径全解析

带学生考了这么多年GESP,我越来越觉得,C八级是整个认证体系里最值得认真对待的一场考试。它不像一级到四级那样,把语法点挨个过一遍就能过,也不像六级、七级那样靠刷题量能堆上去,八级真正考的是算法设计能力和系统化的…

作者头像 李华
网站建设 2026/10/3 5:29:13

Kettle循环结果集实践:从结果集传递到Execute Row参数映射详解

简介:这是一份关于Kettle(Pentaho Data Integration)实现结果集循环获取并传递至下一转换的技术文档,面向有ETL开发需求的工程师,重点解决在Job中通过JavaScript循环处理结果集变量、再交由下一转换继续加工的问题。文…

作者头像 李华
网站建设 2026/10/3 5:29:02

Browser-Use实战:用AI语义化操控浏览器,告别脆弱选择器

浏览器自动化这个方向,过去两年我一直在跟。从最早的Selenium脚本,到后来的Playwright,再到各种RPA工具,说实话大多数方案对普通用户都不够友好——要么得写代码,要么得装一堆依赖,要么跑起来就卡死。直到我…

作者头像 李华
网站建设 2026/10/3 5:28:45

从文件描述符到线上排查:Socket网络通信实践指南

从文件描述符到线上排查:Socket网络通信的完整实践笔记不管你是刚跨过进程、线程这道坎,还是已经在Linux下写过不少IO程序,只要第一次认真去写Socket通信,基本都会卡在某一个瞬间:accept()卡住不动,客户端连…

作者头像 李华