更新时间:2025-07-29 GMT+08:00
分享

PD分离部署使用说明

什么是PD分离部署

大模型推理是自回归的过程,有以下两阶段:

  • Prefill阶段(全量推理)

    将用户请求的prompt传入大模型,进行计算,中间结果写入KVCache并推出第1个token,属于计算密集型。

  • Decode阶段(增量推理)

    将请求的前1个token传入大模型,从显存读取前文产生的KVCache再进行计算,属于访存密集型。

PD分离部署场景下,大模型推理的Prefill阶段(全量推理)和Decode阶段(增量推理)分别实例化部署在不同的推理卡资源上同时进行推理,用于提高资源利用效率。

PD分离结合Prefill阶段的计算密集型特性,以及Decode阶段的访存密集型特性,通过调节PD节点数量配比来提升Decode节点的batch size来充分发挥NPU卡的算力,进而提升集群整体吞吐。

此外,在Decode平均低时延约束场景,PD分离相比PD混合部署,更加能够发挥性能优势。

分离部署的实例类型启动分为以下三个阶段:
  1. 启动全量推理实例:必须为NPU实例,用于启动全量推理服务,负责输入的全量推理。全量推理占用至少1个容器。
  2. 启动增量推理实例:必须为NPU实例,用于启动增量推理服务,负责输入的增量推理。增量推理占用至少1个容器。
  3. 启动scheduler实例:可为CPU实例,用于启动api-server服务,负责接收推理请求,向全量或增量推理实例分发请求,收集推理结果并向客户端返回推理结果。服务调度实例不占用显卡资源,建议增加1个容器,也可以在全量推理或增量推理的容器上启动。

约束限制

  • 全量和增量节点的local rank table必须一一对应。
  • 全量和增量节点不能使用同一个端口。
  • scheduler实例中NODE_PORTS=8088,8089;端口设置顺序必须与global rank table文件中各全量和增量节点顺序一致,否则会报错。
  • 确保scheduler实例和P、D实例之间网络通畅,检查代理设置例如no_proxy环境变量,避免scheduler访问P、D实例时走不必要的网关。

前提条件

已完成推理环境镜像制作,具体参见准备推理环境

步骤一:生成ranktable

介绍如何生成ranktable,以1p1d-tp2分离部署模式为例。当前1p1d分离部署模式,全量节点和增量节点分别占用2张卡,一共使用4张卡。

  1. 配置tools工具根目录环境变量

    使用AscendCloud-LLM发布版本进行推理,基于AscendCloud-LLM包的解压路径配置tool工具根目录环境变量:

    export LLM_TOOLS_PATH=${root_path_of_AscendCloud-LLM}/llm_tools

    其中,${root_path_of_AscendCloud-LLM}为AscendCloud-LLM包解压后的根路径。

    当使用昇腾云的官方指导文档制作推理镜像时,可直接基于该固定路径配置环境变量:

    export LLM_TOOLS_PATH=/home/ma-user/AscendCloud/AscendCloud-LLM/llm_tools
  1. 获取每台机器的rank_table

    在每个机器生成global rank_table信息与local rank_table信息。

    python ${LLM_TOOLS_PATH}/PD_separate/pd_ranktable_tools.py --mode gen --prefill-server-list 4,5 --decode-server-list 6,7 --api-server --save-dir ./save_dir

    执行后,会生成一个global_ranktable.json文件和使用实例个数的local_ranktable.json文件;如果指定了--api-server,还会生成一个local_ranktable_host.json文件用于确定服务入口实例。

    ./save_dir生成ranktable文件如下(假设本地主机ip为10.**.**.18)。

    global_ranktable_10.**.**.18.json      # global rank_table
    local_ranktable_10.**.**.18_45.json    # 全量节点local rank_table
    local_ranktable_10.**.**.18_67.json    # 增量节点local rank_table
    local_ranktable_10.**.**.18_host.json  # api-server

    如果要启动多P多D服务,则需要修改--prefill-server-list和--decode-server-list参数,每个实例之间用空格隔开,例如2p2d-tp2:

    python ${LLM_TOOLS_PATH}/PD_separate/pd_ranktable_tools.py --mode gen --prefill-server-list 0,1 2,3 --decode-server-list 4,5 6,7 --api-server --save-dir ./save_dir
  1. 合并不同机器的global rank_table(可选)

    如果分离部署在多台机器,获取每台机器的rank_table后,合并各个机器的global rank_table得到完整的global rank_table。

    python ${LLM_TOOLS_PATH}/PD_separate/pd_ranktable_tools.py --mode merge --global-ranktable-list ./ranktable/global_ranktable_0.0,0,0.json ./ranktable/global_ranktable_1.1.1.1.json --save-dir ./save_dir
  • pd_ranktable_tools.py的入参说明如下。
    • --mode:脚本的处理模式,可选值为gen或者merge。gen模式表示生成rank_table文件,merge模式表示合并global rank_table文件。
    • --save-dir:保存生成的rank_table文件的根目录,默认为当前目录。
    • --api-server:仅在gen模式有效,可选输入,当存在该输入时,表示分离部署的服务入口在该机器。注意,在多台机器启动分离部署时,只能有一台机器存在服务入口。当存在该输入时,会生成local_ranktable_xx_host.json文件,用于在启动推理服务时确定服务入口实例。
    • --prefill-server-list:仅在gen模式有效,可选输入,后续入参表示若干个vllm全量实例,使用空格隔开,每个vllm实例的数字表示使用的昇腾卡device_id,使用多个昇腾卡时,device_id之间使用英文逗号`,`分隔开。当存在该输入时,会生成对应全量实例个数的local_ranktable_xx_yy.json文件,用于在启动推理服务时确定全量实例。
    • --decode-server-list:仅在gen模式有效,可选输入,后续入参表示若干个vllm增量实例,使用空格隔开,每个vllm实例的数字表示使用的昇腾卡device_id,使用多个昇腾卡时,device_id之间使用英文逗号`,`分隔开。当存在该输入时,会生成对应增量实例个数的local_ranktable_xx_yy.json文件,用于在启动推理服务时确定增量实例。
    • --global-ranktable-list:仅在merge模式有效,必选输入,后续入参表示需要合并的global rank_table,使用空格分隔开。

    合并不同机器的global rank_table后,会生成新合并的global_ranktable_merge.json文件。

  • global_rank_table.json格式说明

    server_group_list的长度必须为3,第一个元素(group_id="0")代表Scheduler实例的ip信息,只能有一个实例。

    第二个元素(group_id="1")代表全量实例信息,长度即为全量实例个数。其中需要配置每个全量实例的ip信息以及使用的device信息。rank_id为逻辑卡号,必然从0开始计算,device_id为物理卡号,device_ip则通过上面的hccn_tool获取。

    第三个元素(group_id="2")代表增量实例信息,长度即为增量实例个数。其余信息和全量类似。

    global_rank_table.json具体示例如下:
    {
        "version": "1.0",
        "status": "completed",
        "server_group_list": [
            {
                "group_id": "0",
                "server_count": "1",
                "server_list": [
                    {
                        "server_id": "localhost", 
                        "server_ip": "localhost"
                    }
                ]
            },
            {
                "group_id": "1",
                "server_count": "1",
                "server_list": [
                    {
                        "server_id": "localhost", 
                        "server_ip": "localhost",
                        "device": [
                            {
                                "device_id": "4",
                                "device_ip": "10.**.**.22",
                                "rank_id": "0"
                            },
                            {
                                "device_id": "5",
                                "device_ip": "10.**.**.23",
                                "rank_id": "1"
                            }
                        ]
                    }
                ]
            },
            {
                "group_id": "2",
                "server_count": "1",
                "server_list": [
                    {
                        "server_id": "localhost",
                        "server_ip": "localhost",
                        "device": [
                            {
                                "device_id": "6",
                                "device_ip": "29.**.**.56",
                                "rank_id": "0"
                            },
                            {
                                "device_id": "7",
                                "device_ip": "29.**.**.72",
                                "rank_id": "1"
                            }
                        ]
                    }
                ]
            }
        ]
    }
    ```
  • local_rank_table.json格式说明
    每个全量/增量实例都需要local_rank_table.json。下面以某一个增量实例为例,需要和global_rank_table.json中的增量信息完全对应,group_id为0。
    ```
    {
        "version": "1.0",
        "status": "completed",
        "group_id": "0",
        "server_count": "1",
        "server_list": [
            {
                "server_id": "localhost",
                "server_ip": "localhost",
                "device": [
                    {
                        "device_id": "6",
                        "device_ip": "29.**.**.56",
                        "rank_id": "0"
                    },
                    {
                        "device_id": "7",
                        "device_ip": "29.**.**.72",
                        "rank_id": "1"
                    }
                ]
            }
        ]
    }
    ```

步骤二:启动全量推理实例

以下介绍如何启动全量推理实例。

  1. 启动容器镜像前请先按照参数说明修改${}中的参数。docker启动失败会有对应的error提示,启动成功会有对应的docker id生成,并且不会报错。
    docker run -itd \
    --device=/dev/davinci4 \
    --device=/dev/davinci5 \
    -v /etc/localtime:/etc/localtime  \
    -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
    -v /etc/ascend_install.info:/etc/ascend_install.info \
    --device=/dev/davinci_manager \
    --device=/dev/devmm_svm \
    --device=/dev/hisi_hdc \
    -v /var/log/npu/:/usr/slog \
    -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
    -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
    -v ${dir}:${container_work_dir} \
    --net=host \
    --name ${container_name} \
    ${image_id} \
    /bin/bash

    参数说明:

    • --device=/dev/davinci0,..., --device=/dev/davinci7:挂载NPU设备,示例中挂载了2张卡davinci4、davinci5。
    • -v ${dir}:${container_work_dir} 代表需要在容器中挂载宿主机的目录。宿主机和容器使用不同的大文件系统,dir为宿主机中文件目录,${container_work_dir}为要挂载到的容器中的目录。为方便两个地址可以相同。

      说明:

      • 容器不能挂载到/home/ma-user目录,此目录为ma-user用户家目录。如果容器挂载到/home/ma-user下,拉起容器时会与基础镜像冲突,导致基础镜像不可用。
      • driver及npu-smi需同时挂载至容器。
      • 不要将多个容器绑到同一个NPU上,会导致后续的容器无法正常使用NPU功能。
      • 如果需要多个全量实例,每个全量都需要启动一个容器,只挂载对应的NPU
    • --name ${container_name}:容器名称,进入容器时会用到,此处可以自己定义一个容器名称。
    • {image_id} 为docker镜像的ID,即步骤四:制作推理镜像中生成的新镜像ID,在宿主机上可通过docker images查询得到。
  2. 进入容器。
    docker exec -it -u ma-user ${container_name} /bin/bash
  3. 启动全量推理实例,命令如下。
    export GLOBAL_RANK_TABLE_FILE_PATH=global_ranktable_10.**.**.18.json
    export RANK_TABLE_FILE_PATH=local_rank_table_10.**.**.18_45.json
    export NODE_PORTS=8088,8089
    export USE_OPENAI=1
    
    sh AscendCloud-LLM/llm_tools/PD_separate/start_servers.sh \
        --model=${model} \
        --tensor-parallel-size=2 \
        --max-model-len=4096 \
        --max-num-seqs=256 \
        --max-num-batched-tokens=4096 \
        --host=0.0.0.0 \
        --port=8088 \
        --served-model-name ${served-model-name}

    其中环境变量说明如下:

    • GLOBAL_RANK_TABLE_FILE_PATH:global rank_table的路径,必选。不同实例类型的global rank_table均一致。
    • RANK_TABLE_FILE_PATH:local rank_table的路径,必选。当实例类型为全量推理实例或者增量推理实例,local rank_table配置local_ranktable_xx_yy.json文件,其中xx表示当前实例的IP地址,yy表示当前实例使用的device_id信息;当实例类型为服务入口实例,local rank_table配置local_ranktable_xx_host.json文件,其中xx表示当前实例的IP地址。
    • NODE_PORTS:仅在服务入口实例生效,用于与全量推理实例、增量推理实例的信息交互。该参数入参为形如{port1},{port2},{portn}的字符串,与全量或增量推理实例启动的--port参数相关。--port表示服务部署的端口。每个全量/增量推理实例基于配置的端口号(--port)启动服务,并按照global rank_table中的全量实例、增量实例的顺序,对全量推理实例、增量推理实例启动的端口号进行排序,端口之间用`,`分隔开作为该环境变量的输入。
    • USE_OPENAI:仅在服务入口实例生效,用于配置api-server服务是否使用openai服务,默认为1。当配置为1时,启动服务为openai服务;当配置为0时,启动服务为vllm服务。

    其中常见的参数如下:

    • --host:服务部署的IP
    • --port:服务部署的端口,注意如果不同实例部署在一台机器上,不同实例需要使用不同端口号
    • --model:HuggingFace下载的官方权重
    • --max-num-seqs:同时处理的最大句子数量
    • --max-model-len:模型能处理的请求输入+输出的token长度
    • --max-num-batched-tokens:最多会使用多少token,必须大于或等于--max-model-len,推荐使用4096或8192
    • --tensor-parallel-size:模型并行数量
    • --served-model-name:OpenAI服务的model入参名称,仅在环境变量USE_OPENAI=1时生效。
    • --quantization:如果需要增加模型量化功能,启动推理服务前,先参考量化章节对模型做量化处理。
    • --prefill-batching-policy:针对PD分离场景下的P实例调度策略选择,支持fcfs(先来先服务,vllm默认)及gtsf(Group Time Short First,组内等待时间最短优先)两种策略,若负载比较高且请求长度差异大可以选择gtsf进行尝试。

    参数定义和使用方式与vLLM0.6.3版本一致,此处介绍关键参数。详细参数解释请参见https://github.com/vllm-project/vllm/blob/main/vllm/engine/arg_utils.py

步骤三:启动增量推理实例

  1. 启动增量推理容器

    启动容器镜像前请先按照参数说明修改${}中的参数。docker启动失败会有对应的error提示,启动成功会有对应的docker id生成,并且不会报错。

    docker run -itd \
    --device=/dev/davinci6 \
    --device=/dev/davinci7 \
    -v /etc/localtime:/etc/localtime  \
    -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
    -v /etc/ascend_install.info:/etc/ascend_install.info \
    --device=/dev/davinci_manager \
    --device=/dev/devmm_svm \
    --device=/dev/hisi_hdc \
    -v /var/log/npu/:/usr/slog \
    -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
    -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
    -v ${dir}:${container_work_dir} \
    --net=host \
    --name ${container_name} \
    ${image_id} \
    /bin/bash

    参数说明:

    • --device=/dev/davinci0,..., --device=/dev/davinci7:挂载NPU设备,示例中挂载了2张卡davinci6、davinci7。
    • -v ${dir}:${container_work_dir} 代表需要在容器中挂载宿主机的目录。宿主机和容器使用不同的大文件系统,dir为宿主机中文件目录,${container_work_dir}为要挂载到的容器中的目录。为方便两个地址可以相同。
      • 容器不能挂载到/home/ma-user目录,此目录为ma-user用户家目录。如果容器挂载到/home/ma-user下,拉起容器时会与基础镜像冲突,导致基础镜像不可用。
      • driver及npu-smi需同时挂载至容器。
      • 不要将多个容器绑到同一个NPU上,会导致后续的容器无法正常使用NPU功能。
      • 如果需要多个增量实例,每个增量都需要启动一个容器,只挂载对应的NPU
    • --name ${container_name}:容器名称,进入容器时会用到,此处可以自己定义一个容器名称。
    • {image_id} 为docker镜像的ID,即步骤四:制作推理镜像中生成的新镜像ID,在宿主机上可通过docker images查询得到。
  2. 进入容器
    docker exec -it -u ma-user ${container_name} /bin/bash
  3. 启动增量推理实例,命令如下。
    export GLOBAL_RANK_TABLE_FILE_PATH=global_ranktable_10.**.**.18.json
    export RANK_TABLE_FILE_PATH=local_rank_table_10.**.**.18_67.json
    
    export NODE_PORTS=8088,8089
    export USE_OPENAI=1
    
    sh AscendCloud-LLM/llm_tools/PD_separate/start_servers.sh \
        --model=${model} \
        --tensor-parallel-size=2 \
        --max-model-len=4096 \
        --max-num-seqs=256 \
        --max-num-batched-tokens=4096 \
        --host=0.0.0.0 \
        --port=8089 \
        --served-model-name ${served-model-name}

    其中环境变量说明如下:

    • GLOBAL_RANK_TABLE_FILE_PATH:global rank_table的路径,必选。不同实例类型的global rank_table均一致。
    • RANK_TABLE_FILE_PATH:local rank_table的路径,必选。当实例类型为全量推理实例或者增量推理实例,local rank_table配置local_ranktable_xx_yy.json文件,其中xx表示当前实例的IP地址,yy表示当前实例使用的device_id信息;当实例类型为服务入口实例,local rank_table配置local_ranktable_xx_host.json文件,其中xx表示当前实例的IP地址。
    • NODE_PORTS:仅在服务入口实例生效,用于与全量推理实例、增量推理实例的信息交互。该参数入参为形如{port1},{port2},{portn}的字符串,与全量/增量推理实例启动的--port参数相关,--port表示服务部署的端口。每个全量/增量推理实例基于配置的端口号(--port)启动服务,并按照global rank_table中的全量实例、增量实例的顺序,对全量推理实例、增量推理实例启动的端口号进行排序,端口之间用,(英文逗号)分隔开作为该环境变量的输入。
    • USE_OPENAI:仅在服务入口实例生效,用于配置api-server服务是否使用openai服务,默认为1。当配置为1时,启动服务为openai服务;当配置为0时,启动服务为vllm服务。

    其中常见的参数如下:

    • --host:服务部署的IP地址
    • --port:服务部署的端口,注意如果不同实例部署在一台机器上,不同实例需要使用不同端口号
    • --model:HuggingFace下载的官方权重
    • --max-num-seqs:同时处理的最大句子数量
    • --max-model-len:模型能处理的请求输入+输出的token长度
    • --max-num-batched-tokens:最多会使用多少token,必须大于或等于--max-model-len,推荐使用4096或8192
    • --tensor-parallel-size:模型并行数量
    • --served-model-name:OpenAI服务的model入参名称,仅在环境变量USE_OPENAI=1时生效。
    • --quantization:如果需要增加模型量化功能,启动推理服务前,先参考量化章节对模型做量化处理。

步骤四:启动scheduler实例

建议在PD服务(即全量推理和增量推理服务)启动后,再启动scheduler服务。

  1. 启动scheduler容器。启动容器镜像前请先按照参数说明修改${}中的参数。docker启动失败会有对应的error提示,启动成功会有对应的docker id生成,并且不会报错。
    docker run -itd \
    -v /etc/localtime:/etc/localtime  \
    -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
    -v /etc/ascend_install.info:/etc/ascend_install.info \
    --device=/dev/davinci_manager \
    --device=/dev/devmm_svm \
    --device=/dev/hisi_hdc \
    -v /var/log/npu/:/usr/slog \
    -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
    -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
    -v ${dir}:${container_work_dir} \
    --net=host \
    --name ${container_name} \
    ${image_id} \
    /bin/bash

    参数说明:

    • -v ${dir}:${container_work_dir} 代表需要在容器中挂载宿主机的目录。宿主机和容器使用不同的大文件系统,dir为宿主机中文件目录,${container_work_dir}为要挂载到的容器中的目录。为方便两个地址可以相同。
      • 容器不能挂载到/home/ma-user目录,此目录为ma-user用户家目录。如果容器挂载到/home/ma-user下,拉起容器时会与基础镜像冲突,导致基础镜像不可用。
      • driver及npu-smi需同时挂载至容器。
    • --name ${container_name}:容器名称,进入容器时会用到,此处可以自己定义一个容器名称。
    • {image_id} 为docker镜像的ID,即步骤四:制作推理镜像中生成的新镜像ID,在宿主机上可通过docker images查询得到。
  2. 进入容器。
    docker exec -it -u ma-user ${container_name} /bin/bash
  3. 启动scheduler实例,命令如下。
    export GLOBAL_RANK_TABLE_FILE_PATH=global_ranktable_10.**.**.18.json
    export RANK_TABLE_FILE_PATH=local_rank_table_10.**.**.18_host.json
    export NODE_PORTS=8088,8089
    export USE_OPENAI=1
    export no_proxy=localhost,127.0.0.1,10.**.**.18
    
    sh AscendCloud-LLM/llm_tools/PD_separate/start_servers.sh \
        --model=${model} \
        --tensor-parallel-size=2 \
        --max-model-len=4096 \
        --max-num-seqs=256 \
        --max-num-batched-tokens=4096 \
        --host=0.0.0.0 \
        --port=9000 \
        --served-model-name ${served-model-name}
    
    # 当前schduler端口port对外提供推理服务,故使用该端口进行性能验证和精度对齐

    其中环境变量说明如下:

    • GLOBAL_RANK_TABLE_FILE_PATH:global rank_table的路径,必选。不同实例类型的global rank_table均一致。
    • NODE_PORTS:仅在服务入口实例生效,用于与全量推理实例、增量推理实例的信息交互。该参数入参为形如{port1},{port2},{portn}的字符串,与全量/增量推理实例启动的--port参数相关,--port表示服务部署的端口。每个全量/增量推理实例基于配置的端口号(--port)启动服务,并按照global rank_table中的全量实例、增量实例的顺序,对全量推理实例、增量推理实例启动的端口号进行排序,端口之间用`,`分隔开作为该环境变量的输入。当前端口9000是对外服务端口,而8088、8089则为scheduler调度推理服务端口。
    • USE_OPENAI:仅在服务入口实例生效,用于配置api-server服务是否使用openai服务,默认为1。当配置为1时,启动服务为openai服务;当配置为0时,启动服务为vllm服务。
    • no_proxy:可选,避免scheduler实例和P、D实例之间访问时走不必要的网关。

    其中常见的参数如下,

    • --host:服务部署的IP
    • --port:服务部署的端口,注意如果不同实例部署在一台机器上,不同实例需要使用不同端口号。分离部署对外服务使用的是scheduler实例端口,在后续推理性能测试和精度测试时,服务端口需要和scheduler实例端口保持一致。
    • --model:HuggingFace下载的官方权重
    • --max-num-seqs:同时处理的最大句子数量
    • --max-model-len:模型能处理的请求输入+输出的token长度
    • --max-num-batched-tokens:最多会使用多少token,必须大于或等于--max-model-len,推荐使用4096或8192
    • --tensor-parallel-size:模型并行数量
    • --served-model-name:openai服务的model入参名称,仅在环境变量USE_OPENAI=1时候生效。
    • --quantization:如果需要增加模型量化功能,启动推理服务前,先参考量化章节对模型做量化处理。
    • --prefill-routing-policy:全量节点路由策略,支持RoundRobin(轮询,默认)、FreeKVFirst(优先调度到空闲KV最多的节点)、BLB(优先调度到排队请求数量最少的节点)三种

相关文档