2026-09-21

基于 sgl-project/rbg 的 PD 分离推理部署实践

用 RoleBasedGroup 把 SGLang Prefill/Decode 分离部署成一个可整体运维的单元:路由、启动依赖、多机 TP、KV 传输与灰度升级的落地方案。

Prefill 和 Decode 分离之后,真正棘手的是两侧的耦合:Router 必须在 Prefill/Decode 就绪后才能对外服务,扩缩容要保持两侧配比,升级不能让一个角色先变成孤岛。传统 StatefulSet/Deployment 把这些逻辑都丢给了外部脚本,而 sgl-project/rbg 把"一组角色 = 一个推理服务"建模成了 Kubernetes CRD,让 PD 分离部署有了一个可以整体操作的管理单元。

信息快照:2026-09-21,基于 RBG v0.7.0(v1alpha2 API)与 SGLang v0.5.9 示例。项目演进很快,生产前请核对官方文档。

RBG 解决什么问题

RBG 的核心抽象是 RoleBasedGroup:一个 CR 内声明多个 Role(如 router、prefill、decode),每个 Role 有独立的副本数、模板和生命周期策略。它带来三件 PD 分离场景里最需要的事:

  • 启动顺序:dependencies 字段声明 Role 依赖,decode 等 prefill 就绪、router 等两侧就绪,避免 Router 把流量打到未初始化的引擎。
  • 服务发现:控制器自动生成形如 <group>-<role>-<index>.s-<group>-<role> 的确定性域名,Router 配置直接写死 Prefill/Decode 实例地址,不需要自己维护 headless Service 拼接逻辑。
  • 整体运维:滚动更新、扩缩容、故障恢复都以 RBG 为单位协调,CoordinatedPolicy 还能控制多角色之间的 maxSkew 和推进节奏。

单机 PD 分离:最小可用拓扑

最小拓扑是三个 Role:Router(SGLang Model Gateway)、Prefill、Decode。以 Qwen3-0.6B 为例,核心配置如下:

apiVersion: workloads.x-k8s.io/v1alpha2
kind: RoleBasedGroup
metadata:
  name: sglang-pd-inference
spec:
  roles:
    - name: router
      replicas: 1
      standalonePattern:
        template:
          spec:
            containers:
              - name: router
                image: lmsysorg/sglang-router:v0.2.4
                command:
                  - python3
                  - -m
                  - sglang_router.launch_router
                  - --pd-disaggregation
                  - --prefill
                  - "http://sglang-pd-inference-prefill-0.s-sglang-pd-inference-prefill:8000"
                  - --decode
                  - "http://sglang-pd-inference-decode-0.s-sglang-pd-inference-decode:8000"
                  - --host
                  - "0.0.0.0"
                  - --port
                  - "8000"
                ports:
                  - name: http
                    containerPort: 8000

    - name: prefill
      replicas: 1
      standalonePattern:
        template:
          spec:
            containers:
              - name: prefill
                image: lmsysorg/sglang:v0.5.9
                command:
                  - python3
                  - -m
                  - sglang.launch_server
                  - --model-path
                  - "Qwen/Qwen3-0.6B"
                  - --host
                  - "0.0.0.0"
                  - --port
                  - "8000"
                  - --disaggregation-mode
                  - "prefill"
                  - --tp-size
                  - "1"
                resources:
                  requests:
                    nvidia.com/gpu: "1"

    - name: decode
      replicas: 1
      standalonePattern:
        template:
          spec:
            containers:
              - name: decode
                image: lmsysorg/sglang:v0.5.9
                command:
                  - python3
                  - -m
                  - sglang.launch_server
                  - --model-path
                  - "Qwen/Qwen3-0.6B"
                  - --host
                  - "0.0.0.0"
                  - --port
                  - "8000"
                  - --disaggregation-mode
                  - "decode"
                  - --tp-size
                  - "1"
                resources:
                  requests:
                    nvidia.com/gpu: "1"

完整示例见 pd-disagg-standalone.yaml,其中还包含 /dev/shm 的 Memory emptyDir(KV 传输的中间计算需要共享内存)、readiness/liveness 探针和 InPlaceIfPossible 滚动升级策略。

几个容易踩的坑:

  • 共享内存必须配。PD 分离的 KV 传输依赖 /dev/shm,示例里给到 30Gi 的 Memory emptyDir。漏掉这个配置,大 context 下会先在共享内存上失败,而不是在 GPU 显存上。
  • 探针初始延迟要给足。SGLang 加载模型权重需要时间,示例用 60 秒 readiness、120 秒 liveness。换成大模型时要按实际加载时间调整,否则会在启动阶段被反复重启。
  • Router 地址是确定性拼接。<name>-<role>-<index>.s-<name>-<role> 这个格式来自 RBG 的服务发现机制,改了 RBG 名字就要同步改 Router 参数。

多机 Tensor Parallel:LeaderWorker 模式

Decode 侧需要更大 TP 规模时,用 leaderWorkerPattern 代替 standalonePattern。每个实例由 1 个 Leader + N 个 Worker 组成,RBG 注入环境变量完成分布式初始化:

    - name: decode
      replicas: 4
      leaderWorkerPattern:
        size: 2   # 1 leader + 1 worker,tp-size=2
        restartPolicyConfig:
          type: None
        template:
          spec:
            containers:
              - name: sglang
                command:
                  - python3
                  - -m
                  - sglang.launch_server
                  - --disaggregation-mode
                  - "decode"
                  - --tp-size
                  - "2"
                  - --dist-init-addr
                  - $(RBG_LWP_LEADER_ADDRESS):6379
                  - --nnodes
                  - $(RBG_LWP_GROUP_SIZE)
                  - --node-rank
                  - $(RBG_LWP_WORKER_INDEX)
                resources:
                  requests:
                    nvidia.com/gpu: "1"

示例 pd-disagg-leader-worker.yaml 的配置是 Prefill 2 实例 × TP4、Decode 4 实例 × TP2,两侧 GPU 总量相等(各 8 卡)。这体现了 PD 分离的一个基本约束:两侧算力配比要按负载特征调,Prefill 偏计算密集,Decode 偏显存带宽密集,长输出场景 Decode 侧通常要更多实例。

RBG_LWP_LEADER_ADDRESS、RBG_LWP_GROUP_SIZE、RBG_LWP_WORKER_INDEX 是控制器注入的拓扑变量,不需要自己写 initContainer 拼 IP。这比手搓 StatefulSet + 环境变量脚本可靠得多,也是 RBG 相对原生原语的核心价值。

KV 传输后端:Mooncake

SGLang 默认的 KV 传输走引擎内置通道,跨节点高并发场景建议换 Mooncake Transfer Engine。RBG 提供了现成示例,关键差异只在两个启动参数:

                  # Prefill 侧
                  - --disaggregation-mode
                  - prefill
                  - --disaggregation-transfer-backend
                  - mooncake

Decode 侧只需 --disaggregation-mode=decode,不需要 transfer backend 参数。完整示例见 sgl-pd-disagg-with-mooncake-te.yaml。

注意这个示例依赖外部部署的 Mooncake 服务(mooncake-store/standalone-mooncake-store.yaml),不是 sidecar 模式。生产环境里 Mooncake Master 的可用性和网络带宽是 PD 分离的新增故障域,要纳入监控。

灰度升级与扩缩容

RBG 的 InPlaceIfPossible 策略在只改镜像或资源限额时原地更新 Pod,不重建实例——这对 PD 分离很关键,因为重建一个 Decode 实例意味着丢掉它持有的 KV cache。

      rolloutStrategy:
        type: RollingUpdate
        rollingUpdate:
          type: InPlaceIfPossible
          maxUnavailable: 1
          inPlaceUpdateStrategy:
            gracePeriodSeconds: 30

扩缩容通过 scalingAdapter 自动创建 RoleBasedGroupScalingAdapter CR,直接对接 HPA。CoordinatedPolicy 则用于多角色协同:控制 Prefill 和 Decode 扩缩容之间的 maxSkew,避免一侧先扩导致 KV 传输积压。

社区还有 rbg-planner,基于 ARIMA 负载预测做 SLA 驱动的自动扩缩容,按 TTFT/ITL 目标分别调整 Prefill/Decode 副本数,适合不想手写 HPA 指标规则的团队。

NVIDIA Dynamo 运行时

如果团队已经用 NVIDIA Dynamo 做推理编排,RBG 也提供了 dynamo/pd-disagg.yaml 示例:Processor(Dynamo frontend)做请求路由,Prefill/Decode 用 nvcr.io/nvidia/ai-dynamo/sglang-runtime 镜像。Dynamo 依赖外部 etcd 和 NATS 做服务发现,这比纯 SGLang Router 多两个运维组件,换来的是 Dynamo 生态的调度和加速能力(如 Model Express P2P 权重分发)。

与 LWS DisaggregatedSet 对比

如果已经读过本站基于 LeaderWorkerSet 的 PD 分离实践,你会关心两者怎么选。两者都解决"成组 Pod 管理",但抽象层级不同:

维度 RBG(RoleBasedGroup) LWS(DisaggregatedSet)
API 成熟度 v1alpha2(v0.7.0),API 仍在 alpha 阶段 LWS v0.9.0,LeaderWorkerSet API 更早进入生态
角色建模 一个 CR 内声明多 Role,原生多角色拓扑 DisaggregatedSet 组合多个 LWS,每个角色底层仍是一个独立 LeaderWorkerSet
启动依赖 dependencies 字段原生支持启动顺序 无内置依赖声明,靠 Pod 就绪顺序和外部编排
服务发现 确定性域名 <group>-<role>-<index>.s-<group>-<role> 每个角色有独立 Service,revision-aware 路由需要按 label 成对发现
协同升级/扩缩容 CoordinatedPolicy 控制 maxSkew 与推进节奏 无跨角色协调原语,滚动更新混版本风险需自行处理
原地更新 InPlaceIfPossible 原生支持 Pod 重建为主,KV cache 随 Pod 丢失
生态集成 Mooncake、Dynamo、rbg-planner 官方示例 vLLM/SGLang 官方文档路径成熟,NIXL 传输示例完善
社区规模 较新,贡献者集中在 sgl-project 组织 kubernetes-sigs 项目,社区和审计面更广

选 RBG 的场景:需要跨角色协调(配比扩缩容、协同灰度)、想用声明式方式表达启动依赖、或者计划用 rbg-planner 做 SLA 驱动自动扩缩。RBG 把这些 PD 分离特有的运维问题做进了 API。

选 LWS 的场景:追求稳定性和社区背书、已经在用 vLLM/SGLang 官方 LWS 部署路径、或者 KV 传输走 NIXL 且不需要跨角色协调原语。LWS 的 LeaderWorkerSet API 更成熟,出问题时可参考的实践更多。

一个务实的判断标准:如果你的痛点是"角色之间怎么协调"(启动顺序、配比、灰度),RBG 的抽象更贴合;如果痛点只是"多机 TP 怎么成组管理",LWS 更稳。两者不互斥——RBG 本身就借鉴并复用了 LWS 代码。

上线前检查清单

  • GPU 节点已安装 CUDA 驱动,nvidia.com/gpu 资源可用
  • /dev/shm Memory emptyDir 已配置,大小按模型 context 和并发估算
  • Router 的 Prefill/Decode 地址与 RBG 名称一致
  • 探针初始延迟按模型加载时间调整
  • 多机 TP 时确认节点间网络满足 KV 传输带宽(RDMA 优先)
  • 使用 Mooncake 时,Mooncake 服务先于 RBG 部署并纳入监控
  • 滚动更新策略确认 InPlaceIfPossible,避免不必要的 KV cache 丢失
  • HPA 指标对接 RoleBasedGroupScalingAdapter,或评估 rbg-planner

PD 分离不是"把一个 Deployment 拆成两个"这么简单,它引入了角色间协调这个新问题域。RBG 的价值在于把这个问题域变成声明式 API:启动顺序、服务发现、协同升级、配比扩缩容都在一个 CR 里表达,而不是散落在脚本和运维约定里。如果你的 PD 分离部署还在靠 bash 脚本粘合 StatefulSet,值得试一次 RBG。