https://x.com/i/article/2119948219887341568
UCSD CSE 291A 第 13 讲:Prefix Caching 与 Prefill–Decode 解耦,分布式 LLM 推理怎样少做重复计算
第 13 讲:Distributed LLM Serving I Prefix Caching and P–D Disaggregation
加州大学圣地亚哥分校 CSE 291A · Systems for LLMs and AI Agents(Fall 2026,教师 Yiying Zhang)在 2026 年 11 月 10 日的 Lecture 13 里,专注一件事:单机、混批推理已经不够时,怎样用前缀缓存和 Prefill–Decode 解耦,在分布式 GPU 上少做重复计算,也少让两个阶段互相拖累。讲义副标题写得很直白:Prefix Caching and P–D Disaggregation。按课表,这是「分布式 LLM Serving」两讲中的第一讲;第二讲在 11 月 12 日,才会转到长上下文和 MoE 服务。
这门课不做 agent 框架入门,是大模型和 agent 的后端系统:推理、KV 缓存、调度、隔离和评测。这一讲的主阅读是 Preble(ICLR 2025);附加阅读点名 DistServe、Mooncake、NVIDIA Dynamo、POD-Attention 等。下面的机制和数字都来自该讲讲义及其引用图注,不是独立复测。
这一讲在解决什么浪费
开场提纲列出两条避免浪费的路径。一条是前缀缓存:相同的提示前缀不必再整段重做 prefill,同时要判断「去那台已经有缓存的机器」是否值得排队等待。另一条是把 prefill 和 decode 放到不同资源上。两条路都取决于 KV 状态住在哪、怎样迁移。
动机来自「写提示变得越来越贵」。课上引用类似 Karpathy 的观点:只靠提示往往比微调便宜一个数量级,于是系统提示、工具说明、长文档、多轮 agent 轨迹,都会把共享前缀拉得很长。讲义用 SGLang 等工作负载示意:工具问答、虚拟环境、代码生成、视频问答、长文档问答等,会形成一棵提示树——大量请求共享树干,再分叉到各自的叶子。
前缀为什么值得缓存,以及缓存有多贵
课上给出一个量级(引用 arXiv:2309.06180 一系材料):Llama 2 7B、FP16 下,一条请求大约 4096 个 token 的 KV 可到约 2GB。缓存不再是小小的备忘,更是显存级资产。
和传统缓存的对比也被写进讲义:传统缓存里,计算和状态常常可以分开,任意数据块都可能有用,成本也相对好估计;LLM 服务里的 KV 或前缀缓存,往往是算力和状态放在一起,主要是前缀才有复用价值,执行前要花多少计算、占多少显存也更难预知。
真实负载的特征,课上引用系统视角的研究和 Azure LLM Inference Trace:提示长、输出短,共享度高,波动大。于是盲目轮转等于每台 GPU 都重算整棵树;完全去追最长前缀,又可能把流量砸到少数热门 GPU 上,造成负载不均。
Preble:把复用和负载写成分布式调度
Preble(ICLR 2025,开源)被当作本讲的主系统:面向又长又共享的提示,把全局放置和本地公平调度放在一起设计。
全局调度叫 E2,也就是 Explore 加 Exploit。Exploit 是贪心复用:共享前缀足够长时,去已经有这段前缀的实例。直觉是前缀长度近似计算量;若共享前缀比其余独有部分更长,复用往往划算。对比一台全新机器,要同时付共享部分和独有部分的代价;回到旧机器,主要付独有部分,外加可能的驱逐代价。Explore 则是前缀感知的探路:为每张 GPU 估算负载代价,选最小的,避免全集群挤在同一棵热门前缀上。
架构草图是:分词之后,进入全局调度器和前缀树(全局与本地都有),再配合指标收集和再平衡;各 GPU 上做树匹配,并维护运行队列和公平等待队列。
本地则是感知前缀的公平调度。课上批评两类朴素策略:先到先服务用不了等待队列里的共享前缀;最长前缀匹配又可能饿死短前缀请求。Preble 的做法是轮转优先级组,组内再按先到先服务,在复用和公平之间找折中。
讲义给出的初始结果是讲义自报:Mistral-7B 跑在 2 张或 4 张 A6000 上,Llama 3 70B 跑在 8 张 H100 上;五类负载;对比 SGLang 和一种理想化的分区;指标包括平均时延、p99 时延和每秒请求数。相对基线,讲义称平均时延大约快 1.5 到 14.5 倍,p99 大约快 2 到 10 倍。课上还提到,2024 年前后生产侧对「更便宜的前缀共享」更感兴趣,并把 Moonshot 等写成受 Preble 启发一类叙述。
这一节的结论是:复杂提示让服务更贵,负载变得更长、更共享;要把缓存命中和负载均衡一起调度,而不是只在单机上做本地淘汰。
混批里,Prefill 和 Decode 怎样互相拖累
后半讲进入混批问题。一次很长的 prefill,会推迟已经在路上的 decode 的下一步。两条出路分别是:把长 prefill 切成小块,和 decode 交错执行;或者把两个阶段分到不同的 worker 上。权衡很清楚:共享算力、调度更细,还是隔离之后再付 KV 传输的开销。两者也可以组合。
课上用 DistServe 和 Hao AI Lab 的材料做了简短铺垫,并强调度量口径:每秒请求数只是成本的代理;更好的口径是 goodput,也就是满足服务目标的每秒请求数,更接近「每个合格请求花多少钱」。设计目标写成:用尽量少的 GPU 服务更多请求,同时满足各类应用的首个 token 时延(TTFT)和每个输出 token 的间隔(TPOT)。讲义里的举例是:聊天的 TTFT 可以放到大约 1 秒,搜索或编程大约 200 毫秒;编程的 TPOT 可以紧到大约 50 毫秒。这些都是讲义举例,不是普遍标准。
为什么两个阶段不能一视同仁,课上引用了 Sarathi 一系观察:prefill 主要受算力限制,一个请求就容易打满算力;decode 主要受访存限制,要很大的批次才吃得满算力。混在一起(1 个 prefill 加多个 decode)时,讲义用 LLaMA-13B、1 张 A100、提示长度 1024 的示意:新到的 prefill 可以把一步 decode 拉长约 12.6 倍(相对纯 decode)。这就是混批时的互相拖累。
资源与并行度还会被绑死。为了压低 TTFT 和 TPOT,常常靠加 GPU、提高张量并行或流水线并行。讲义示意:1 张 GPU 很难同时压过例如 TTFT 200 毫秒、TPOT 50 毫秒一类虚线;加到 4 张后两边都缩短,但钱也几乎线性上去,而且同一套并行策略要同时服务两类目标,很难按阶段单独选型。
DistServe:拆开阶段,按达标吞吐来配比
DistServe(OSDI 2024)的方案就是把 prefill 和 decode 拆开:请求先到 Prefill Worker,再把 KV 交给 Decode Worker。
讲义里有一笔示意账(2 个 prefill worker 配 1 个 decode worker;P90 TTFT 不超过 400 毫秒,P90 TPOT 不超过 40 毫秒)。共置时,prefill 侧大约到 3 请求/秒才能压住 TTFT,decode 侧大约 1.6 请求/秒才能压住 TPOT,系统达标吞吐取较小值,大约是每 GPU 1.6 请求/秒。拆成 2 加 1 之后,只做 prefill 大约 5.6 请求/秒,只做 decode 大约 10 请求/秒,于是
也就是大约每 GPU 3.3 请求/秒。这是讲义用来说明「拆开后可以各自打满瓶颈」的例子,前提是 KV 传输和池子配比跟得上。
讲义接着用四个问题组织设计:并行策略怎么配,用仿真加穷举搜索;KV 怎么高效传输,看 GPU 和 worker 怎么放置;突发怎么应对,用由 decode 主动拉取的传输;流水线空泡怎么减小,靠切块。拉取式传输的细节是:初始化时在 prefill 侧打开进程间通道,把句柄拷到 decode 侧;真正传输时由 decode 去拉 prefill 显存里的 KV;实现上提到 CUDA kernel 和 NCCL,并与张量并行组对齐。
评测侧展示了 ShareGPT、HumanEval、LongBench 上「输入远长于输出」的分布差异。例如讲义图中,ShareGPT 平均输入约 755.5、输出约 200.3;LongBench 输入约 1738.3、输出约 90.7。一种共置配比很难伺候所有应用。
讲义也写明了拆开何时会亏:最低 GPU 数会上升,单卡模型至少要一卡做 prefill、一卡做 decode;短请求省下的时间盖不住 KV 传输;突发不均时一侧空闲、一侧排队。因此应当先和同一资源池里的切块 prefill 对照,而不是默认拆开一定更好。
产业延伸页提到 NVIDIA Dynamo(感知 KV 的路由、P/D worker、NIXL 传输,页脚致谢 DistServe),SGLang 在大规模专家并行上的 PD 部署案例,以及 vLLM 的解耦 prefill:代理先把 prefill 的 max_tokens 设为 1,再把原来的 max_tokens 交给 decode,并用 drop_select 拉取 KV、跳过重算。
切块 Prefill:不拆池时,同一步里小步往前走
与解耦并列的是 Sarathi-Serve 路线:把长提示按块算完,每块复用已有 KV,并和 decode 混在同一次迭代里,限制单步占用设备的时间。
讲义强调:切块改变的是执行粒度,并不缩小最终 KV 的体积。块太大,单步并行度高,但这一步会拖很久;块太小,decode 更能插队,但启动次数多,前缀也要反复读。第一个 token 仍要等最后一块 prefill 算完;交错进来的 decode 还有可能拉长 TTFT。课上提醒:Sarathi 对外强调的收益,常常假设旁边已经有并发的 decode 可以一起排进同一步。
两条线怎么合,以及下一讲做什么
讲义把复用和解耦合在一起,写成四步:prefill 路由偏向可复用的前缀;decode 选显存和时延还有余量的目的地;异步传 KV 时跟踪所有权和生命周期;驱逐不得和传输或在途的 decode 抢同一块状态。
项目上的建议是:用固定轨迹,控制前缀共享比例,对比轮转、只追局部性、代价感知路由,再扫带宽、队列负载和上下文长度,报告 TTFT、TPOT、服务目标达成率、传输字节和缓存命中。
本讲带走的三点是:前缀复用是分布式放置问题;把 prefill 和 decode 分开,才能按阶段单独选资源;传输开销和负载倾斜决定收益大不大。下一讲才会进入长上下文状态和稀疏专家(MoE)服务,那才是张量并行、流水线并行、专家并行、序列并行和长 KV 迁移的主场;这一讲几乎不展开那些。
一点读后感
这一讲把大模型服务从单机持续组批,推进到两张调度图:一张管相同前缀放哪台机器,一张管算力饥渴的 prefill 和访存饥渴的 decode 是否还该抢同一张卡。Preble 和 DistServe 不是互相替代,而在提醒:只追命中率会不公平,只追拆分会付传输开销。读数字时要记住,它们来自特定模型、GPU 和服务目标;工程上更该带走的,是达标吞吐这个口径,以及先对照切块 prefill、再决定是否拆池的比较框架。
来源:Lecture 13 课件 · 课程主页 · 阅读材料 · 课程项目
