部署Dynamo PD分离推理服务

本文以Qwen3-32B模型为例,演示如何在ACK中部署Dynamo PD分离架构的模型推理服务。

背景知识

  • Qwen3-32B

    Qwen3-32B 是通义千问系列最新一代的大型语言模型,基于328亿参数的密集模型架构,兼具卓越的推理能力与高效的对话性能。其最大特色在于支持思考模式与非思考模式的无缝切换。在复杂逻辑推理、数学计算和代码生成任务中表现出众,而在日常对话场景下也可高效响应。模型具备出色的指令遵循、多轮对话、角色扮演和创意写作能力,并在Agent任务中实现领先的工具调用表现。原生支持32K上下文,结合YaRN技术可扩展至131K。同时,支持100多种语言,具备强大的多语言理解与翻译能力,适用于全球化应用场景。有关更多详细信息,请参阅博客GitHub文档

  • Dynamo

    DynamoNVIDIA推出的一款高吞吐、低延迟的推理框架,专门用于在多节点分布式环境中为LLM模型提供推理服务。image.png

    其核心特性包括如下所示,更多关于Dynamo框架的信息,请参见Dynamo GitHubDynamo文档

    • 引擎无关性:它不绑定于特定的推理引擎,可以灵活支持TensorRT-LLM、vLLM、SGLang等多种后端。

    • LLM专属优化能力

      • PrefillDecode分离推理,通过将PrefillDecode分开部署,降低推理延迟,提升系统吞吐。

      • 动态GPU调度,根据实时变化的负载需求来优化性能。

      • LLM感知的请求路由,可以根据节点KVCache进行路由,避免不必要的KVCache重计算。

      • 加速数据传输,利用NIXL技术来加速中间计算结果及KVCache传输。

      • KVCache卸载,可以将KVCache卸载到内存、磁盘甚至云盘上,从而提升系统总吞吐量。

      • 高性能与高扩展性,核心由 Rust 语言构建以追求极致性能,同时提供Python接口以方便用户进行扩展。

      • 完全开源,Dynamo完全开源,并遵循透明的、开源软件优先的开发理念。

  • PD分离

    Prefill/Decode分离架构,是当前主流的LLM推理优化技术,旨在解决推理过程中两个核心阶段的资源需求冲突问题。LLM的推理过程可分为两个阶段:

    • Prefill (提示词处理) 阶段:此阶段一次性处理用户输入的全部提示词(Prompt),并行计算所有输入Token的注意力,并生成初始的KV缓存。这个过程是计算密集型(Compute-Bound)的,需要强大的并行计算能力,但只在请求开始时执行一次。

    • Decode (解码生成) 阶段:此阶段是自回归过程,模型根据已有的KV缓存,逐个生成新的Token。每一步的计算量很小,但需要反复、快速地从显存中加载巨大的模型权重和KV缓存,因此是内存带宽密集型(Memory-Bound)的。image.png

    核心矛盾在于将这两种特性迥异的任务混合在同一GPU上调度,效率极低。推理引擎在处理多个用户请求时往往会采用连续批处理(Continuous Batching)的方式,将不同请求的Prefill阶段和Decode阶段放在一个批次里调度。由于Prefill阶段需处理完整提示词(计算复杂度高),而Decode阶段仅需生成单token(计算复杂度低),若在同一批次中调度,Decode阶段会因序列长度差异与资源竞争导致时延增加,进而增加系统整体延迟并降低吞吐量。

    image.png

    PD分离架构的解决方案就是将这两个阶段解耦,将PrefillDecode阶段分开部署在不同GPU上。通过这种分离,系统可以针对PrefillDecode不同特征进行优化,避免资源争抢,从而显著降低生成每个输出 token 的平均时间(TPOT),提升系统吞吐。

  • RoleBasedGroup

    RoleBasedGroup(RBG)是阿里云容器服务团队设计的一种新的工作负载,为了解决PD分离架构在Kubernetes集群中大规模部署及运维的难题。该项目已开源,更多信息请查看RBG Github

    RBG API设计如下图所示,它由一组Role构成一个Group整体,每个Role可以基于StatefulSet/Deployment/LWS构建。其核心特性如下:

    • 灵活的多角色定义:RBG支持定义任意数量任意名称的Role;支持定义Role间的依赖关系,可以按指定顺序启动Role;可以按照Role维度弹性扩缩容。

    • Runtime:具备Group内部的自动服务发现能力;支持多种重启策略;支持滚动更新;支持Gang调度。

      image.png

前提条件

  • 已创建ACK集群且集群版本为1.22及以上,并且已经为集群添加GPU节点。具体操作,请参见创建ACK托管集群为集群添加GPU节点

    本文要求集群中GPU卡>=6, 单个GPU卡显存>=32GB。推荐使用ecs.ebmgn8is.32xlarge规格,更多规格信息可参考弹性裸金属服务器规格
  • 已安装ack-rbgs组件。组件安装步骤如下。

    登录容器服务管理控制台,在左侧导航栏选择集群列表单击目标集群名称,进入集群详情页面,使用Helm为目标集群安装ack-rbgs组件。您无需为组件配置应用名命名空间,单击下一步后会出现一个请确认的弹框,单击是,即可使用默认的应用名(ack-rbgs)和命名空间(rbgs-system)。然后选择Chart 版本为最新版本,单击确定即可完成ack-rbgs组件的安装。

    image

模型部署

部署Dynamo PD分离架构推理服务。Dynamo PD分离时序图如下所示。

  • 用户请求首先发送给processor组件,经由router选出合适的Decode Worker,将请求转发给Decode Worker。

  • Decode Worker判断是否prefill计算是在本地完成还是远程完成。如果需要远程计算,则向PrefillQueue中发送请求。

  • PrefillWorkerQueue获取请求后进行Prefill计算。等计算完成后,将Prefill KVCache传输给Decode Worker。

image.png

步骤一:准备Qwen3-32B模型文件

  1. 执行以下命令从ModelScope下载Qwen-32B模型。

    请确认是否已安装git-lfs插件,如未安装可执行yum install git-lfs或者apt-get install git-lfs安装。更多的安装方式,请参见安装git-lfs
    git lfs install
    GIT_LFS_SKIP_SMUDGE=1 git clone https://www.modelscope.cn/Qwen/Qwen3-32B.git
    cd Qwen3-32B/
    git lfs pull
  2. 登录OSS控制台,查看并记录已创建的Bucket名称。如何创建Bucket,请参见创建存储空间。在OSS中创建目录,将模型上传至OSS。

    关于ossutil工具的安装和使用方法,请参见安装ossutil
    ossutil mkdir oss://<your-bucket-name>/Qwen3-32B
    ossutil cp -r ./Qwen3-32B oss://<your-bucket-name>/Qwen3-32B
  3. 创建PVPVC。为目标集群配置名为llm-model的存储卷PV和存储声明PVC。具体操作,请参见创建PVPVC

    控制台操作示例

    1. 创建PV。

      • 登录容器服务管理控制台,在左侧导航栏选择集群列表

      • 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择存储 > 存储卷

      • 存储卷页面,单击右上角的创建

      • 创建存储卷对话框中配置参数。

        以下为示例PV的基本配置信息:

        配置项

        说明

        存储卷类型

        OSS

        名称

        llm-model

        访问证书

        配置用于访问OSSAccessKey IDAccessKey Secret。

        Bucket ID

        选择上一步所创建的OSS Bucket。

        OSS Path

        选择模型所在的路径,如/Qwen3-32B

    2. 创建PVC。

      • 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择存储 > 存储声明

      • 存储声明页面,单击右上角的创建

      • 创建存储声明页面中,填写界面参数。

        以下为示例PVC的基本配置信息:

        配置项

        说明

        存储声明类型

        OSS

        名称

        llm-model

        分配模式

        选择已有存储卷。

        已有存储卷

        单击选择已有存储卷链接,选择已创建的存储卷PV。

    kubectl操作示例

    1. 创建llm-model.yaml文件,该YAML文件包含Secret静态卷PV静态卷PVC等配置,示例YAML文件如下所示。

      apiVersion: v1
      kind: Secret
      metadata:
        name: oss-secret
      stringData:
        akId: <your-oss-ak> # 配置用于访问OSSAccessKey ID
        akSecret: <your-oss-sk> # 配置用于访问OSSAccessKey Secret
      ---
      apiVersion: v1
      kind: PersistentVolume
      metadata:
        name: llm-model
        labels:
          alicloud-pvname: llm-model
      spec:
        capacity:
          storage: 30Gi 
        accessModes:
          - ReadOnlyMany
        persistentVolumeReclaimPolicy: Retain
        csi:
          driver: ossplugin.csi.alibabacloud.com
          volumeHandle: llm-model
          nodePublishSecretRef:
            name: oss-secret
            namespace: default
          volumeAttributes:
            bucket: <your-bucket-name> # bucket名称
            url: <your-bucket-endpoint> # Endpoint信息,如oss-cn-hangzhou-internal.aliyuncs.com
            otherOpts: "-o umask=022 -o max_stat_cache_size=0 -o allow_other"
            path: <your-model-path> # 本示例中为/Qwen3-32B/
      ---
      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: llm-model
      spec:
        accessModes:
          - ReadOnlyMany
        resources:
          requests:
            storage: 30Gi
        selector:
          matchLabels:
            alicloud-pvname: llm-model
    2. 创建Secret创建静态卷PV创建静态卷PVC

      kubectl create -f llm-model.yaml

步骤二:安装ETCDNATS服务

Dynamo框架中跨节点通信使用的是NIXL。NIXL启动时会向ETCD注册,从而实现相互发现。NATS服务主要用于PrefillDecode Worker间消息传递。因此部署推理服务前,需要先部署ETCDNATS服务。

  1. 执行以下命令创建etcd.yaml并部署ETCD服务。

    展开查看相关YAML示例。

    apiVersion: v1
    kind: Service
    metadata:
      name: etcd
      labels:
        app: etcd
    spec:
      ports:
        - port: 2379
          name: client
        - port: 2380
          name: peer
      clusterIP: None # Enables headless service mode
      selector:
        app: etcd
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: etcd
      labels:
        app: etcd
    spec:
      selector:
        matchLabels:
          app: etcd
      serviceName: "etcd"
      replicas: 1
      template:
        metadata:
          labels:
            app: etcd
        spec:
          containers:
            - name: etcd
              # image: bitnami/etcd:3.5.19
              image: ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/etcd:3.6.1
              volumeMounts:
                - name: data
                  mountPath: /var/lib/etcd
              env:
                - name: ETCDCTL_API
                  value: "3"
                - name: ALLOW_NONE_AUTHENTICATION
                  value: "yes"
          volumes:
            - name: data
              emptyDir: {}
    kubectl apply -f etcd.yaml
  2. 执行以下命令创建nats.yaml并部署NATS服务。

    展开查看相关YAML示例。

    apiVersion: v1
    kind: Service
    metadata:
      name: nats
      labels:
        app: nats
    spec:
      ports:
        - port: 4222
          name: client
        - port: 8222
          name: management
        - port: 6222
          name: cluster
      selector:
        app: nats
      type: ClusterIP
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nats
      labels:
        app: nats
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nats
      template:
        metadata:
          labels:
            app: nats
        spec:
          containers:
            - name: nats
              # image: nats:latest
              image: ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/nats:2.11.5
              args:
                - -js
                - --trace
                - -m
                - "8222"
              ports:
                - containerPort: 4222
                - containerPort: 8222
                - containerPort: 6222
    kubectl apply -f nats.yaml

步骤三:部署Dynamo PD分离架构的推理服务

本文使用RBG部署2P1D Dynamo推理服务,部署架构图如下所示。 PrefillDecode均采用了TP=2的方式部署。

image.png

  1. 使用dynamo-configs.yaml配置qwen3模型及Dynamo框架相关的配置文件。

    展开查看相关YAML示例。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: dynamo-configs
    data:
      pd_disagg.py: |
        from components.frontend import Frontend
        from components.kv_router import Router
        from components.processor import Processor
    
        Frontend.link(Processor).link(Router)
    
      qwen3.yaml: |
        Common:
          model: /models/Qwen3-32B/
          kv-transfer-config: '{"kv_connector":"DynamoNixlConnector"}'
          router: round-robin
          # Number of tokens in a batch for more efficient chunked transfers to GPUs.
          block-size: 128
          max-model-len: 2048
          max-num-batched-tokens: 2048
          disable-log-requests: true
    
        Frontend:
          served_model_name: qwen
          endpoint: dynamo.Processor.chat/completions
          port: 8000
    
        Processor:
          common-configs: [ model, router ]
    
        VllmWorker:
          common-configs: [ model, kv-transfer-config, router, block-size, max-model-len, disable-log-requests ]
          # Enable prefill at different workers.
          remote-prefill: true
          # Disable local prefill so only disaggregated prefill is used.
          conditional-disagg: false
          gpu-memory-utilization: 0.95
          tensor-parallel-size: 1
          ServiceArgs:
            workers: 1
            resources:
              gpu: 1
    
        PrefillWorker:
          common-configs: [ model, kv-transfer-config, block-size, max-model-len, max-num-batched-tokens, gpu-memory-utilization, disable-log-requests ]
          tensor-parallel-size: 1
          gpu-memory-utilization: 0.95
          ServiceArgs:
            workers: 1
            resources:
              gpu: 1
    
    kubectl apply -f dynamo-configs.yaml
  2. 准备Dynamo Runtime镜像。

    具体操作,请参见Dynamo社区文档获取,或制作使用vllm作为推理框架的Dynamo Runtime容器镜像。

  3. 执行以下命令创建dynamo.yaml并部署服务。

    展开查看相关YAML示例。

    apiVersion: workloads.x-k8s.io/v1alpha1
    kind: RoleBasedGroup
    metadata:
      name: dynamo-pd
      namespace: default
    spec:
      roles:
        - name: processor
          replicas: 1
          template:
            spec:
              containers:
                - command:
                    - sh
                    - -c
                    - cd /workspace/examples/llm; dynamo serve graphs.pd_disagg:Frontend -f ./configs/qwen3.yaml
                  env:
                    - name: DYNAMO_NAME
                      value: dynamo
                    - name: DYNAMO_NAMESPACE
                      value: default
                    - name: ETCD_ENDPOINTS
                      value: http://etcd:2379
                    - name: NATS_SERVER
                      value: nats://nats:4222
                    - name: DYNAMO_RP_TIMEOUT
                      value: "60"
                  image: #步骤2中所构建的Dynamo Runtime镜像地址
                  name: processor
                  ports:
                    - containerPort: 8000
                      name: health
                      protocol: TCP
                    - containerPort: 9345
                      name: request
                      protocol: TCP
                    - containerPort: 443
                      name: api
                      protocol: TCP
                    - containerPort: 9347
                      name: metrics
                      protocol: TCP
                  readinessProbe:
                    initialDelaySeconds: 30
                    periodSeconds: 30
                    tcpSocket:
                      port: 8000
                  resources:
                    limits:
                      cpu: "8"
                      memory: 12Gi
                    requests:
                      cpu: "8"
                      memory: 12Gi
                  volumeMounts:
                    - mountPath: /models/Qwen3-32B/
                      name: model
                    - mountPath: /workspace/examples/llm/configs/qwen3.yaml
                      name: dynamo-configs
                      subPath: qwen3.yaml
                    - mountPath: /workspace/examples/llm/graphs/pd_disagg.py
                      name: dynamo-configs
                      subPath: pd_disagg.py
              volumes:
                - name: model
                  persistentVolumeClaim:
                    claimName: llm-model
                - configMap:
                    name: dynamo-configs
                  name: dynamo-configs
        - name: prefill
          replicas: 2
          template:
            spec:
              containers:
                - command:
                    - sh
                    - -c
                    - cd /workspace/examples/llm; dynamo serve components.prefill_worker:PrefillWorker -f ./configs/qwen3.yaml
                  env:
                    - name: DYNAMO_NAME
                      value: dynamo
                    - name: DYNAMO_NAMESPACE
                      value: default
                    - name: ETCD_ENDPOINTS
                      value: http://etcd:2379
                    - name: NATS_SERVER
                      value: nats://nats:4222
                    - name: DYNAMO_RP_TIMEOUT
                      value: "60"
                  image: #步骤2中所构建的Dynamo Runtime镜像地址
                  name: prefill-worker
                  resources:
                    limits:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                    requests:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                  volumeMounts:
                    - mountPath: /models/Qwen3-32B/
                      name: model
                    - mountPath: /workspace/examples/llm/configs/qwen3.yaml
                      name: dynamo-configs
                      subPath: qwen3.yaml
              volumes:
                - name: model
                  persistentVolumeClaim:
                    claimName: llm-model
                - configMap:
                    name: dynamo-configs
                  name: dynamo-configs
        - name: decoder
          replicas: 1
          template:
            spec:
              containers:
                - command:
                    - sh
                    - -c
                    - cd /workspace/examples/llm; dynamo serve components.worker:VllmWorker -f ./configs/qwen3.yaml --service-name VllmWorker
                  env:
                    - name: DYNAMO_NAME
                      value: dynamo
                    - name: DYNAMO_NAMESPACE
                      value: default
                    - name: ETCD_ENDPOINTS
                      value: http://etcd:2379
                    - name: NATS_SERVER
                      value: nats://nats:4222
                    - name: DYNAMO_RP_TIMEOUT
                      value: "60"
                  image: #步骤2中所构建的Dynamo Runtime镜像地址
                  name: vllm-worker
                  resources:
                    limits:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                    requests:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                  volumeMounts:
                    - mountPath: /models/Qwen3-32B/
                      name: model
                    - mountPath: /workspace/examples/llm/configs/qwen3.yaml
                      name: dynamo-configs
                      subPath: qwen3.yaml
              volumes:
                - name: model
                  persistentVolumeClaim:
                    claimName: llm-model
                - configMap:
                    name: dynamo-configs
                  name: dynamo-configs
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: dynamo-service
    spec:
      type: ClusterIP
      ports:
        - port: 8000
          protocol: TCP
          targetPort: 8000
      selector:
        rolebasedgroup.workloads.x-k8s.io/name: dynamo-pd
        rolebasedgroup.workloads.x-k8s.io/role: processor
    kubectl apply -f ./dynamo.yaml

步骤四:验证推理服务

  1. 执行以下命令,在推理服务与本地环境之间建立端口转发。

    重要

    kubectl port-forward建立的端口转发不具备生产级别的可靠性、安全性和扩展性,因此仅适用于开发和调试目的,不适合在生产环境使用。更多关于Kubernetes集群内生产可用的网络方案的信息,请参见Ingress管理

    kubectl port-forward svc/dynamo-service 8000:8000

    预期输出:

    Forwarding from 127.0.0.1:8000 -> 8000
    Forwarding from [::1]:8000 -> 8000
  2. 执行以下命令,向模型推理服务发送了一条示例的模型推理请求。

    curl http://localhost:8000/v1/chat/completions   -H "Content-Type: application/json"   -d '{"model": "qwen","messages": [{"role": "user","content": "测试一下"}],"stream":false,"max_tokens": 30}'

    预期输出:

    {"id":"31ac3203-c5f9-4b06-a4cd-4435a78d3b35","choices":[{"index":0,"message":{"content":"<think>\n好的,用户发来“测试一下”,我需要先确认他们的意图。可能是在测试我的反应速度或者功能,也可能是想","refusal":null,"tool_calls":null,"role":"assistant","function_call":null,"audio":null},"finish_reason":"length","logprobs":null}],"created":1753702438,"model":"qwen","service_tier":null,"system_fingerprint":null,"object":"chat.completion","usage":null}

    输出结果表明模型可以根据给定的输入(在这个例子中是一条测试消息)生成相应的回复。

相关文档

  • 为单机/多机推理配置弹性扩缩容

    针对LLM模型服务的动态负载波动问题,Kubernetes HPA结合ACKack-alibaba-cloud-metrics-adapter组件,可根据CPU/内存/GPU利用率及自定义指标实现Pod的动态弹性伸缩,保障服务稳定性与资源高效利用。

  • 基于Fluid配置分布式缓存实现模型加速

    LLM 模型通常包含超过10GB的权重文件,从存储服务(如 OSS、NAS 等)拉取这些大文件时,容易因长时间延迟和冷启动问题影响性能。Fluid 通过在 Kubernetes 集群节点上构建分布式文件缓存系统,整合多个节点的存储与带宽资源;同时,它从应用程序端优化模型文件的读取机制,从而显著加速模型加载过程。