灵魂拷问:大模型推理为什么要PD分离?看完这篇你就知道了!!

前言

随着DeepSeek爆火,面试中也越来越高频出现,因此训练营也更新了DeepSeek系列技术的深入拆解。包括MLA、MTP、专家负载均衡、FP8混合精度训练,Dual-Pipe等关键技术,力求做到全网最硬核的解析~

在 LLM 推理计算中 Prefill 和 Decode 两个阶段的计算/显存/带宽需求不一样,通常 Prefill 是算力密集,Decode 是访存密集。

一些场景中 P 和 D 两者分开计算可提升性能。vLLM 是一种主流的推理框架,本文主要围绕其 PD 分离场景做讨论。

PD 分离方法出现的简单推演:

先是有整体部署的方案: 一个推理服务实例里面起一个模型,适合数据长度固定场景,推理服务可通过增加 batch size 提升吞吐/性能。

但这种方式对于 Transformer 架构(里面包含自回归计算过程)效率却并不高。

为了提升资源效率,在大语言模型中设计出了 KV cache 减少重复计算,模型计算过程能拆成两步:

  • prefill(一次运算,算力消耗大)
  • decode(自回归多次迭代,存储密集)

这两个过程如果放一个实例(硬件资源固定)中会出现 P 阶段显存利用率低、D 阶段算力使用不高的问题,为了进一步提升系统效率又衍生出了 P 和 D 分开计算的解决方法。

图片

01、vLLM PD分离方案现状

开源的 vLLM0.8.x 版本的 PD 分离功能依靠 KV transfer 来完成(代码 PR),能够支持 1P1D 场景的运行。

https://github.com/vllm-project/vllm/pull/10502

其工作关键:顺序执行 P 和 D 计算;用一个 kv transfer 线程交换 kv cache 信息;通过 proxy 控制交互过程。

软件流程图如下所示:

图片

这种方式是一种生产者(producer)和消费者(consumer)模式,P 将运算完成的 kv 值以非阻塞的方式插入 buffer 中,D 通过阻塞模式去获取 buffer 中的 kv 值。

buffer 是双端队列(LookupBuffer),buffer 之间的数据传递需要通过 pipe 完成,主要是解决远端数据传递(Remote transfer ),pipe 可选择 pynccl 或者 mooncacke store。

图片

方案当前比较简洁,所以操作(部署)比较简单。代码实现上只需要把 transfer 配置传递给 LLM,P 与 D 基本相同,区别在于 rank 信息不一样。

参考(vllm/examples/offline_inference/disaggregated_prefill.py):

# 相同的输入   prompts = [        "Hello, my name is",        "Hi, your name is",        "Tell me a very long story",    ]    sampling_params = SamplingParams(temperature=0, top_p=0.95)# prefill 运算    ktc = KVTransferConfig.from_cli(        '{"kv_connector":"PyNcclConnector","kv_role":"kv_producer","kv_rank":0,"kv_parallel_size":2}'    )    llm = LLM(model="meta-llama/Meta-Llama-3.1-8B-Instruct",              kv_transfer_config=ktc,              max_model_len=2000,              gpu_memory_utilization=0.8)    llm.generate(prompts, sampling_params)    print("Prefill node is finished.")    prefill_done.set()#  decoder 运算:    ktc = KVTransferConfig.from_cli(        '{"kv_connector":"PyNcclConnector","kv_role":"kv_consumer","kv_rank":1,"kv_parallel_size":2}'    )    llm = LLM(model="meta-llama/Meta-Llama-3.1-8B-Instruct",              kv_transfer_config=ktc,              max_model_len=2000,              gpu_memory_utilization=0.8)    prefill_done.wait()    outputs = llm.generate(prompts, sampling_params)

总体来看功能处于探索阶段,所以支持功能比较少,比如 xPyD、TP/PP、Chunk Prefill 等,其调度未考虑负载均衡、集群性能等问题(V1 版本上适配讨论中)。

02、设计需要考虑的问题

PD 分离设计时需要考虑一些问题:

图片

图片

图片

03、现有/可行方案的分析讨论

(1)Connector-Base 方案

<该方案是社区讨论方案,更新时间(2025/3/25) 在线文档>

https://docs.google.com/document/d/1uPGdbEXksKXeN4Q9nUm9hzotqEjQhYmnpAhidLuAsjk/edit?tab=t.0#heading=h.z11h6wpmv17d

每个 vLLM 进程都创建一个 connector 链接器,然后创建两类 connector 分别配置到 scheduler、worker 上面。

图片

Scheduler connector:它负责调度 KV 缓存传输操作,决定哪些标记(tokens)需要从连接器加载 KV 缓存,或者将 KV 缓存保存到连接器。与调度器同进程;

Worker connector:负责 kv cache 传输操作,与 worker 同进程。

图片

异步传输执行步骤:

  • Step 1: router 发送 prefill 请求到 prefiller 实例
  • Step 2: router 通知 prefiller_connector 给哪个 decoder 发送数据
  • Step 3: Prefiller 运行 P 运算,并且把结果存到 P connector 的 buffer 里面
  • Step 4: Prefiller 发送(pushes)KV cache 到 D_connector

step3 和 step4 并行执行:

  • Step 5: D connector 通知 P connector 已获得 KV cache 值
  • Step 6: P connector 通知 router,KV 完成了 push 操作
  • Step 7: router 发送 decode 请求到 decoder 实例
  • Step 8: decoder 实例从 D connector 的 buffer 加载 KV cache
  • Step 9: 运行 decode 计算

Scheduler 相关侧的代码修改:

  • connector 携带状态(stateful)
  • connector 可以调用 allocate_slots 和 freee

allocate_slotsfree 函数传递给连接器,以便其能够为键值对(KV)缓存传输预留 GPU 缓冲区。

这使得连接器能够透明地将外部键值对缓存注入到 GPU 中,并将其作为前缀缓存块,从而使调度器可以将它们视为普通的前缀缓存块进行处理。

改动点:get_computed_blocks

# In get_computed_blocks, we call the connector at the scheduler side (we call it scheduler # connector) to determine the KV cache of what tokens need to be loaded from the connector: def get_computed_blocks(            self, request: Request) -> tuple[list[KVCacheBlock], int]        # After querying the GPU prefix cache        computed_blocks, num_computed_tokens = self.connector.get_external_prefix_cache_blocks(            request,            computed_blocks,            num_computed_tokens,        )        return computed_blocks, num_computed_tokens

改动点:scheduler_output

"""	Before returning the scheduler output, we call the scheduler connector to:	calculate the KV cache of which tokens need to be saved to the connector	and prepare the metadata to tell the worker connector the KV cache of what tokens need to be saved / loaded."""        scheduler_output = SchedulerOutput(            scheduled_new_reqs=new_reqs_data,            scheduled_cached_reqs=resumed_reqs_data + running_reqs_data,            num_scheduled_tokens=num_scheduled_tokens,            total_num_scheduled_tokens=total_num_scheduled_tokens,            scheduled_spec_decode_tokens=scheduled_spec_decode_tokens,            scheduled_encoder_inputs=scheduled_encoder_inputs,

Worker 侧相关侧的代码修改:

  • 在模型运行之前异步加载所有层的键值对(KV),在模型运行之后等待所有层的键值对保存完成。
  • 在 attention 操作中,在执行 attention 之前检查该层的键值对是否加载完成,并在注意力操作之后发起键值对缓存的保存

改动点:gpu_model_runner.py

# In gpu_model_runner.py, prepare the worker connector’s input in _prepare_inputs()    def _prepare_inputs(        self,        scheduler_output: "SchedulerOutput",    ) -> tuple[FlashAttentionMetadata, torch.Tensor,               Optional[SpecDecodeMetadata]]:        # This will reset the state of connector        self.connector.parse_connector_meta(scheduler_output.connector_meta)        ......        # Run the decoder.        # Use persistent buffers for CUDA graphs.        with set_forward_context(attn_metadata, self.vllm_config, self.connector):            hidden_states = self.model(                input_ids=input_ids,                positions=positions,                intermediate_tensors=intermediate_tensors,                inputs_embeds=inputs_embeds,)

改动点:forward_context.py/set_forward_context()

"""In forward_context.py/set_forward_context(), asynchronously fire KV cache load operation layer-by-layer before model execution, and blocking wait for the KV cache save operation after model execution:"""    connector.start_load_kv_async(static_forward_context)    try:        yield    finally:        connector.wait_for_save_kv()

改动点:vllm/attention/layer.py

# In vllm/attention/layer.py: check for load before attn and async save after attn:                forward_context: ForwardContext = get_forward_context()                attn_metadata = forward_context.attn_metadata                self_kv_cache = self.kv_cache[forward_context.virtual_engine]                forward_context.connector.wait_for_load_kv(self)                self.impl.forward(self,                                  query,                                  key,                                  value,                                  self_kv_cache,                                  attn_metadata,                                  output=output)                forward_context.connector.start_save_kv_async(self)

V1 版本的适配考虑

解决方案(Woosuk):

图片

这个方案是在 P 和 D 上各创建一个 scheduler。优势:更好适配 Chunked prefill + disaggregated prefill。不足:需要维护两类 scheduler。

(2)英伟达 Dynamo 方案

Dynamo 架构分为内外两层:外层根据全局资源的情况完成请求的分发,内层以 PD 分离为基础构造实例。

Dynamo 通过 KV Cache 为核心链接内、外层,系统架构图如下:

图片

运行逻辑

外层运行逻辑:http 请求接受、分发请求、P/D 计算、请求返回,外层需要处理全局计算资源。

关键要素:

  • 前端(Frontend):采用 OpenAI 类似 http 请求服务,响应用户请求
  • 路由(Router):根据一定的策略,把前端请求分配到不同 worker
  • 处理单元(Workers): 有 prefill/decode 两种 worker 处理 LLM 计算逻辑

图片

部署示意图

请求分配到 woker id 后到进入分离式服务(disaggregation server),decode 和 prefill 在不同 worker 中,他们之间通过 KV block 完成信息交互,两种 work 之间交互处理流程如下图所示。

图片

Dynamo 运行高效的一个关键是借助了 NIXL 来提速。

The key to high-performance disaggregation is efficient KV transfer. Dynamo leverage NIXL to transfer KV cache directly from the VRAM of prefill engine to the VRAM of decode engine. In addition, the KV transfer is non-blocking, allowing GPU forward pass to serve other requests in addition to the KV transfer.

在 P 与 D 之间有个队列(Queue)主要用于负载均衡(当存在远端交互时),流程图如下所示:

图片

步骤 1: 当 worker 收到一个请求时,它首先决定是否在本地还是远程进行 prefill 处理,并分配 KV block。如果是在远程进行计算,它会将一个远程 prefill 请求推送到预填充队列(prefillQueue)中。

步骤 2: P worker 然后从 prefillQueue 拉取请求,读取 KV block,并计算 prefill 内容,并将计算后的block值写回。

步骤 3: Decode worker 完成剩余的解码工作。

(3)Mooncake 集成方案

mooncake 是 PD 分离应用比较早也是规模比较大的成功例子,关键核心是构建了一个 KV Cache 为中心的解决方案。

Transfer Engine 支持 TCP、RDMA (InfiniBand/RoCEv2/eRDMA/NVIDIA GPUDirect)和 NVMe - over - Fabric(NVMe - of)协议,提供从 DRAM、VRAM 或 NVMe 转移数据的统一接口,隐藏与硬件相关的技术细节。

图片

Mooncake Store(Mooncake 存储):基于 Transfer Engine,为 LLM 推理提供分布式 KV Cache 存储引擎,提供对象级 API(PutGetRemove)。

方案中的 put() 和 get() 操作,具体如下:put() 操作涉及将 KV 缓存从 GPU 分页缓存传输到推理机的本地 DRAM 中。get() 操作包括两个步骤。

在第一步中,当 vLLM 前端接收到请求时,它会向 Mooncake Store 发送一个拉取请求,以异步方式将KV缓存拉取到其本地 DRAM 中。

在第二步中,在将请求添加到运行队列之前,KV 缓存会从本地 DRAM 传输到 GPU 分页缓存。

整体工作流程如下:

图片

vLLM 与 Mooncake 的整合方案(设计文档)

https://docs.google.com/document/d/1Ab6TMW1E2CdHJJyCrpJnLhgmE2b_6leH5MVP9k72sjw/edit?tab=t.0

调度层设计:proxy 系统结构可以采用如下形式,控制流与数据流分离。通过独立的 KV Cache 存储来分离预填充(prefill)节点和解码(decode)节点。

这种解耦确保了预填充节点无需等待解码节点来获取 KV Cache。

图片

控制路径可以通过直接的 HTTP 调用或消息队列来实现。设置完成后,解码实例(Decode instances)会从消息队列或直接从 API 调用中接收请求,然后从 KV Cache 存储中获取 KV Cache,跳过预填充阶段,直接进行解码阶段。

考虑实现功能:

1.零拷贝的方式直接读取并传输 KV 缓存到其目标位置:

目前 KV 缓存是从 vLLM 的内部页面缓存中复制到一个临时缓冲区,然后再发送到 Decode node 或 KV 缓存存储中。这一过程涉及多轮数据复制,在高性能场景下可能会出现问题。

图片

2.全局 KVCache 重复使用:

在 DisaggLLMEngine 里面维护一个全局 prefix,提升系统效率。

<未完待续>

(4)SGLang 方案

SGLang 的 PD 分离方案(社区版)是在原有的调度事件循环(**scheduling event loop)**的基础上,增加一个非阻塞式的发送者/接受者模式完成 PD 分离的实现。

当前合入的 PR 来看已完成相应接口设计。虽然内容还比较简单,但可以参考其框架实现的思路。

具体的实施:在原有的 P/D 实例中增加相应的服务(Server)来处理请求,同一个请求创建两个信息跟踪角色:在 Prefill 里面创建**“sender”、在 Decode 创建“reveiver”。**

根据 Prefill 和 Decode 实例不同创建不同处理队列,并把处理流程分为了多个阶段,每个阶段有一个队列负责。这两个角色在各自的队列里面流转。

Prefill创建三个队列:

  • 引导队列(BootstrapQueue): 为请求创建 sender 实例并与 decode 进行握手,等待 decode 的资源准备(kv);
  • 等待队列(Waiting Queue): 等待资源进行 P 前向运算,完成 P 的前向运算后事件进入下一个队列;
  • 非阻塞查询队列(Infight Queue): 查询 KV 是否传输完成,完成后返回请求;

Decode 创建三个队列:

  • 资源分配队列(PreallocQueue): 为请求创建 reveiver 实例,与 Prefill 握手,并创建 KV 存储;
  • KV 传输队列(TransferQueue): 队列中请求需要获取 Prefill 计算完成后的 KV 值,单个请求实例获得 KV 值后传递到等待队列;
  • 等待队列(WaitingQueue): 等待凑批后进行 D 运算。把队列中的请求(获得了 KV 值)构建一个批次实例(PrebuiltExtendBatch),对批次进行融合后在进行 D 的前向计算。

图片

P 与 D 的互动机制:

  • 创建 request 时,P 与 D 会进行一次握手确认;
  • D 创建完成 KV 好后会通知 P,让其 sender requset 进入等待队列;
  • 当 P 计算完前向触发 KV 传输;
  • P 非阻塞的轮巡检查 KV 是否传输完成(不直接与 D 互动);

目前 KV 值的传递采用非阻塞式的后端进程方式进行处理,P 和 D 中分别用 KVSender、KVReceiver 进行数据的收发,当前列出的传递方式:逐层传递、按照 chunk 为单位传递。

用非阻塞的传递不会影响主进程继续处理新的请求,但可能存在网络流量冲突的问题,目前未考虑/解决。

图片

整个 P/D 分离信息的处理流程是在 scheduler 里面,所以需要增加 event loop 处理逻辑。(python/sglang/srt/managers/scheduler.py)

<参考>

https://github.com/sgl-project/sglang/pull/4654/files#diff-c3b8cc39d10c245933a25aa9c2fd6397f6b31ed8d85c0ecbb926c1f42afdd178

def event_loop_normal_disagg_prefill(self):    passdef event_loop_normal_disagg_decode(self):    pass

**使用方式:**server 启动新增了两个入参,通过入参来选择 P 和 D 实例,调度总入口应该是会通过 Bootstrap server port 来构建。

        # Disaggregation        parser.add_argument(            "--disaggregation-mode",            type=str,            default="null",            choices=["null", "prefill", "decode"],            help='Only used for PD disaggregation. "prefill" for prefill-only server, and "decode" for decode-only server. If not specified, it is not PD disaggregated',        )        parser.add_argument(            "--disaggregation-bootstrap-port",            type=int,            default=ServerArgs.disaggregation_bootstrap_port,            help="Bootstrap server port on the prefill server. Default is 8998.",        )

目前 SGLang 的 PD 官方版本还有许多工作带展开,参考其 PD 分离的路标计划(2025/3/31):

图片

方案参考<SGLang社区讨论方案在线文档,更新时间(2025/4/9)>

https://docs.google.com/document/d/1rQXJwKd5b9b1aOzLh98mnyMhBMhlxXA5ATZTHoQrwvc/edit?tab=t.0#heading=h.i3s2t1j0e1ik

最后

为什么要学AI大模型

当下,⼈⼯智能市场迎来了爆发期,并逐渐进⼊以⼈⼯通⽤智能(AGI)为主导的新时代。企业纷纷官宣“ AI+ ”战略,为新兴技术⼈才创造丰富的就业机会,⼈才缺⼝将达 400 万!

DeepSeek问世以来,生成式AI和大模型技术爆发式增长,让很多岗位重新成了炙手可热的新星,岗位薪资远超很多后端岗位,在程序员中稳居前列。

在这里插入图片描述

与此同时AI与各行各业深度融合,飞速发展,成为炙手可热的新风口,企业非常需要了解AI、懂AI、会用AI的员工,纷纷开出高薪招聘AI大模型相关岗位。
在这里插入图片描述
最近很多程序员朋友都已经学习或者准备学习 AI 大模型,后台也经常会有小伙伴咨询学习路线和学习资料,我特别拜托北京清华大学学士和美国加州理工学院博士学位的鲁为民老师给大家这里给大家准备了一份涵盖了AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频 全系列的学习资料,这些学习资料不仅深入浅出,而且非常实用,让大家系统而高效地掌握AI大模型的各个知识点。

这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费

AI大模型系统学习路线

在面对AI大模型开发领域的复杂与深入,精准学习显得尤为重要。一份系统的技术路线图,不仅能够帮助开发者清晰地了解从入门到精通所需掌握的知识点,还能提供一条高效、有序的学习路径。

img

但知道是一回事,做又是另一回事,初学者最常遇到的问题主要是理论知识缺乏、资源和工具的限制、模型理解和调试的复杂性,在这基础上,找到高质量的学习资源,不浪费时间、不走弯路,又是重中之重。

AI大模型入门到实战的视频教程+项目包

看视频学习是一种高效、直观、灵活且富有吸引力的学习方式,可以更直观地展示过程,能有效提升学习兴趣和理解力,是现在获取知识的重要途径

在这里插入图片描述
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
在这里插入图片描述

海量AI大模型必读的经典书籍(PDF)

阅读AI大模型经典书籍可以帮助读者提高技术水平,开拓视野,掌握核心技术,提高解决问题的能力,同时也可以借鉴他人的经验。对于想要深入学习AI大模型开发的读者来说,阅读经典书籍是非常有必要的。
在这里插入图片描述

600+AI大模型报告(实时更新)

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。
在这里插入图片描述

AI大模型面试真题+答案解析

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下
在这里插入图片描述

在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费

### PD (Placement Driver) 的分离操作及相关配置 PD 是 TiDB 集群中的一个重要组件,主要负责元信息管理、调度以及负载均衡等功能[^1]。在某些场景下,为了提高系统的可用性和可维护性,可以考虑将 PD 实例与其他服务(如 TiKV 和 TiDB)分离部署。 #### 分离的原因与优势 - **高可用性**:通过独立部署 PD,即使其他组件发生故障,PD 仍然能够正常运行并维持集群状态。 - **资源隔离**:减少因计算密集型任务对 PD 性能的影响,从而提升整体稳定性。 - **易于扩展**:当需要增加或替换 PD 节点时,分离架构更便于管理和调整。 #### 如何实现 PD分离? 以下是具体的操作指南: ##### 1. 准备工作 确保已安装最新版本的 TiUP 工具,并初始化所需的环境变量。如果尚未完成这些步骤,则需先按照官方文档指引设置好基础环境[^5]。 ##### 2. 创建单独的 PD 实例 利用 `tiup cluster` 命令来定义一个新的拓扑文件,在其中指定仅包含 PD 组件的部分。例如下面是一个简单的 YAML 文件片段用于描述单个 PD 节点的信息: ```yaml pd_servers: - host: 192.168.x.y name: pd-01 ``` 随后执行创建命令启动该实例: ```bash tiup cluster deploy <cluster-name> v6.5.0 /path/to/topology.yaml --user root [-i ~/.ssh/id_rsa] ``` ##### 3. 更新现有集群指向新的 PD 地址 对于已经存在的 TiDB/TiKV 成员来说,它们默认会尝试联系旧版或者本地嵌入式的 PD;因此有必要手动修改其配置参数使其改用外部提供的地址列表代替内部机制决定的目标位置。这通常涉及到编辑对应进程的服务端口映射关系以及重新加载生效后的更改内容。 以 TiKV 设置为例,打开它的配置文件找到 `[pd]` 部分做如下改动: ```toml [pd] endpoints = ["http://<new-pd-host>:2379"] ``` 保存之后重启关联的服务单元即可应用最新的连接路径设定。 ##### 4. 测试验证新布局下的功能表现 最后一步非常重要——确认整个迁移过程顺利完成并无任何异常状况出现之前不要轻易投入使用生产环境中去。可以通过多种方式来进行全方位检测比如但不限于以下几种手段之一: - 使用 SQL 查询接口访问任意一张表的数据记录; - 查看系统日志是否存在错误提示消息; - 执行 EXPLAIN ANALYZE 检查查询计划是否合理高效[^4]。 以上就是关于如何实施 PD 分离的一些基本指导思路和技术细节说明。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值