部署SGLang PD分离推理服务
本文介绍如何在容器引擎CCE中部署SGLang PD分离推理引擎,实现Qwen3-32B大模型的高性能推理服务。通过使用RDMA高速网络的GPU机型,充分发挥PD分离架构的性能优势。
背景知识
SGLang推理引擎
SGLang是一个高性能的大型语言模型与多模态模型服务推理引擎,通过前后端协同设计,提升模型交互速度与控制能力。其核心特性包括:
- 后端优化:支持RadixAttention(前缀缓存)、零开销CPU调度、PD分离、Speculative decoding、连续批处理、PagedAttention、TP/DP/PP/EP并行、结构化输出、chunked prefill及多种量化技术(FP8/INT4/AWQ/GPTQ)
- 前端灵活性:提供灵活编程接口,支持链式生成、高级提示、控制流、多模态输入、并行处理和外部交互
- 广泛模型支持:支持Qwen、DeepSeek、Llama等生成模型,易于扩展新模型
更多信息请参见:SGLang GitHub
Qwen3-32B
Qwen3-32B是通义千问系列最新一代的大型语言模型,基于328亿参数的密集模型架构,兼具卓越的推理能力与高效的对话性能。其最大特色在于支持思考模式与非思考模式的无缝切换。在复杂逻辑推理、数学计算和代码生成任务中表现出众,原生支持32K上下文,结合YaRN技术可扩展至131K。
PD分离架构
Prefill/Decode分离架构是当前主流的LLM推理优化技术,旨在解决推理过程中两个核心阶段的资源需求冲突问题。
LLM推理的两个阶段
-
Prefill(提示词处理)阶段
- 一次性处理用户输入的全部提示词(Prompt)
- 并行计算所有输入Token的注意力
- 生成初始的KV缓存
- 特点:计算密集型(Compute-Bound),需要强大的并行计算能力
-
Decode(解码生成)阶段
- 自回归过程,根据已有的KV缓存逐个生成新的Token
- 每一步计算量小,但需要反复从显存中加载模型权重和KV缓存
- 特点:内存带宽密集型(Memory-Bound)
PD分离的优势
- 资源优化:将Prefill和Decode阶段分开部署在不同GPU上,针对不同特征进行优化
- 避免资源争抢:消除混合调度导致的效率低下问题
- 降低延迟:显著降低生成每个输出token的平均时间(TPOT)
- 提升吞吐:整体系统吞吐量大幅提升
RoleBasedGroup(RBG)
RoleBasedGroup是一种Kubernetes工作负载扩展,专为PD分离架构在Kubernetes集群中的大规模部署及运维而设计。
前提条件
1. CCE集群要求
- 已创建CCE集群,Kubernetes版本为1.31及以上
- 集群已配置GPU节点池
- 具体操作,请参见创建CCE托管集群。
2. 节点规格要求
本方案使用百度云ehc.lgn5.c128m1024.8a800.8re.4d机型(请联系客户经理申请GPU规格邀测),节点数量:2台。
3.组件要求
需要在CCE集群中安装以下组件:
- CCE CSI PFS L2 Plugin:用于挂载PFS。安装方法:集群详情-运维与管理-组件管理-存储,选择组件并进行安装。
- CCE GPU Manager:用于GPU资源调度。安装方法:集群详情-运维与管理-组件管理-云原生AI,选择组件并进行安装。
- CCE RDMA Device Plugin:用于RDMA资源调度。安装方法:集群详情-运维与管理-组件管理-云原生AI,选择组件并进行安装。
- RBG Controller:用于管理RoleBasedGroup工作负载。安装方法:在CCE控制台,进入Helm模版,选择社区模版-rbgs,点击安装。选择已有集群进行安装。
部署步骤
步骤一:准备模型文件
下载Qwen3-32B模型
在PFS中创建目录,将模型下载至PFS。
- 登录PFS控制台,将集群中的节点挂载到PFS挂载服务中,参考控制台操作文档和命令行操作文档。如何创建PFS文件系统,创建挂载服务并绑定存储实例,请参考创建文件系统,创建挂载服务,绑定存储实例。
- 在PFS中创建目录,并执行以下命令从ModelScope下载Qwen3-32B模型。
1mkdir models-test/DeepSeek-V3.1
2pip install modelscope
3modelscope download --model Qwen/Qwen3-32B --local_dir <pfs目录>/models-test/Qwen3-32B
步骤二:配置存储卷
创建PV和PVC
为目标集群配置存储卷PV和存储声明PVC。请参考使用并行文件存储PFS L2。
创建Qwen3-32B模型存储卷
通过Yaml新建PV、PVC示例:
1apiVersion: v1
2kind: PersistentVolume
3metadata:
4 name: <your-pv-name> #本示例中为test-pv-02
5spec:
6 accessModes:
7 - ReadOnlyMany
8 capacity:
9 storage: 500Gi
10 local:
11 path: <your-pfs-path> #本示例中为/pfs/pfs-qnL8Jh/models-test/Qwen-models
12 nodeAffinity:
13 required:
14 nodeSelectorTerms:
15 - matchExpressions:
16 - key: ready-for-pfsl2
17 operator: In
18 values:
19 - "true"
20 persistentVolumeReclaimPolicy: Retain
21 storageClassName: local-volume
22 volumeMode: Filesystem
1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4 finalizers:
5 - kubernetes.io/pvc-protection
6 name: <your-pvc-name> #本示例中为test-pvc-02
7 namespace: default
8spec:
9 accessModes:
10 - ReadOnlyMany
11 resources:
12 requests:
13 storage: 500Gi
14 storageClassName: local-volume
15 volumeMode: Filesystem
16 volumeName: <your-pv-name> #本示例中为test-pv-02
步骤三:部署Qwen3-32B推理服务
创建qwen3-32b-sglang-pd.yaml文件:
1apiVersion: workloads.x-k8s.io/v1alpha1
2kind: RoleBasedGroup
3metadata:
4 name: sglang-pd-qwen3-32b
5spec:
6 roles:
7 - name: scheduler
8 replicas: 1
9 dependencies: [ "decode", "prefill" ]
10 template:
11 spec:
12 serviceAccountName: sglang-router-sa
13 volumes:
14 - name: model
15 persistentVolumeClaim:
16 claimName: <your-pvc-name>
17 containers:
18 - name: scheduler
19 image: registry.baidubce.com/ai-native-dev/infer-manager/dev-image:0.4.ubuntu2204-py313-sglang0.5.2-router0.1.9-mooncake-0.3.6-nixl-0.6.0-cuda12.4
20 command:
21 - sh
22 - -c
23 - python -m sglang_router.launch_router --pd-disaggregation --service-discovery --prefill-selector "baidu-cce/inference-workload=sglang-pd-qwen3-32b-prefill" --decode-selector "baidu-cce/inference-workload=sglang-pd-qwen3-32b-decode" --service-discovery-namespace default --prefill-policy round_robin --decode-policy round_robin --service-discovery-port 8000 --host 0.0.0.0 --port 8000
24 volumeMounts:
25 - mountPath: /models
26 name: model
27 - name: prefill
28 replicas: 1
29 template:
30 metadata:
31 labels:
32 baidu-cce/inference-workload: sglang-pd-qwen3-32b-prefill
33 baidu-cce/inference_backend: sglang
34 spec:
35 serviceAccountName: sglang-router-sa
36 nodeSelector:
37 gputype: A800
38 volumes:
39 - name: model
40 persistentVolumeClaim:
41 claimName: <your-pvc-name>
42 - name: dshm
43 emptyDir:
44 medium: Memory
45 sizeLimit: 15Gi
46 containers:
47 - name: sglang-prefill
48 image: registry.baidubce.com/ai-native-dev/infer-manager/dev-image:0.4.ubuntu2204-py313-sglang0.5.1.post3-mooncake-0.3.5-cuda12.4
49 imagePullPolicy: Always
50 env:
51 - name: POD_IP
52 valueFrom:
53 fieldRef:
54 fieldPath: status.podIP
55 command:
56 - sh
57 - -c
58 - |
59 ldconfig
60 python -m sglang.launch_server --tp 2 --model-path /models/Qwen/Qwen3-32B/ --disaggregation-mode prefill --port 8000 --disaggregation-bootstrap-port 34000 --host $(POD_IP) --enable-metrics
61 ports:
62 - containerPort: 8000
63 name: http
64 - containerPort: 34000
65 name: bootstrap
66 readinessProbe:
67 initialDelaySeconds: 200
68 periodSeconds: 20
69 tcpSocket:
70 port: 8000
71 resources:
72 limits:
73 nvidia.com/gpu: "2"
74 rdma/hca: "1"
75 memory: "256Gi"
76 cpu: "32"
77 requests:
78 nvidia.com/gpu: "2"
79 rdma/hca: "1"
80 memory: "256Gi"
81 cpu: "32"
82 volumeMounts:
83 - mountPath: /models
84 name: model
85 - mountPath: /dev/shm
86 name: dshm
87 - name: decode
88 replicas: 1
89 template:
90 metadata:
91 labels:
92 baidu-cce/inference-workload: sglang-pd-qwen3-32b-decode
93 baidu-cce/inference_backend: sglang
94 spec:
95 serviceAccountName: sglang-router-sa
96 nodeSelector:
97 gputype: A800
98 volumes:
99 - name: model
100 persistentVolumeClaim:
101 claimName: <your-pvc-name>
102 - name: dshm
103 emptyDir:
104 medium: Memory
105 sizeLimit: 15Gi
106 containers:
107 - name: sglang-decode
108 image: registry.baidubce.com/ai-native-dev/infer-manager/dev-image:0.4.ubuntu2204-py313-sglang0.5.1.post3-mooncake-0.3.5-cuda12.4
109 imagePullPolicy: Always
110 env:
111 - name: POD_IP
112 valueFrom:
113 fieldRef:
114 fieldPath: status.podIP
115 command:
116 - sh
117 - -c
118 - |
119 ldconfig
120 python -m sglang.launch_server --tp 2 --model-path /models/Qwen/Qwen3-32B/ --disaggregation-mode decode --port 8000 --disaggregation-bootstrap-port 34000 --host $(POD_IP) --enable-metrics
121 ports:
122 - containerPort: 8000
123 name: http
124 readinessProbe:
125 initialDelaySeconds: 30
126 periodSeconds: 10
127 tcpSocket:
128 port: 8000
129 resources:
130 limits:
131 nvidia.com/gpu: "2"
132 rdma/hca: "1"
133 memory: "256Gi"
134 cpu: "32"
135 requests:
136 nvidia.com/gpu: "2"
137 rdma/hca: "1"
138 memory: "256Gi"
139 cpu: "32"
140 volumeMounts:
141 - mountPath: /models
142 name: model
143 - mountPath: /dev/shm
144 name: dshm
部署Qwen3-32B服务:
1kubectl create -f qwen3-32b-sglang-pd.yaml
步骤四:测试推理服务
测试Qwen3-32B服务
获取scheduler pod ip,执行以下命令,向模型推理服务发送一条示例的模型推理请求。
1curl http://<your-scheduler-pod-ip>:8000/v1/chat/completions -H "Content-Type: application/json" -d '{"model": "/models/Qwen/Qwen3-32B/", "messages": [{"role": "user", "content": "帮我用python写一个hello world"}], "max_tokens": 2000, "temperature": 0.7, "top_p": 0.9, "seed": 10}'
预期输出:
1{"id":"b60dab5ac1b142068dfd030a3b1938d7","object":"chat.completion","created":1761552772,"model":"/models/Qwen/Qwen3-32B/","choices":[{"index":0,"message":{"role":"assistant","content":"<think>\n嗯,用户让我用Python写一个Hello World。这个看起来挺简单的,但我要仔细想想怎么回答比较好。首先,用户可能是个刚开始学编程的新手,所以需要详细一点的解释。不过也有可能他们只是需要快速得到一个例子,不需要太多细节。\n\n首先,我应该确认用户的需求。他们可能只是想要一个基本的打印语句,还是需要更复杂的交互?比如,是否需要用户输入,或者图形界面?不过根据问题,用户可能只需要最基础的。\n\n接下来,我得考虑Python版本的问题。Python 2和Python 3在print语句上有区别,Python 2用的是print \"Hello World\",而Python 3用的是print(\"Hello World\")。现在大多数用户可能用的是Python 3,但也不能完全排除Python 2的可能性。不过现在Python 2已经停止支持了,所以应该推荐使用Python 3的语法。\n\n然后,代码的结构。最简单的就是print(\"Hello, World!\")。但或许可以加上注释,解释每一行的作用。比如,说明print函数是用来输出内容的。或者展示不同的方式,比如使用变量,或者多行字符串?\n\n用户可能有更深层的需求。比如,他们可能想了解如何运行这个程序,或者如何在不同环境中执行。比如,是否需要解释保存文件为.py格式,然后用命令行运行?或者是否需要提到IDE的使用?\n\n还有可能用户对Python的安装有问题,但这个问题可能超出当前范围。不过可以简要提到确保Python已安装,并检查版本。\n\n另外,是否需要考虑大小写和标点?比如,Hello World是否要带逗号,或者不同的语言?比如用户可能需要其他语言的示例,但问题明确是Hello World,所以保持简单。\n\n可能还需要考虑排版,让代码更清晰。比如用代码块包裹起来,使用正确的语法高亮。在回答中,用反引号或者三个反引号来格式化代码。\n\n有没有可能用户需要更复杂的例子?比如带输入的,比如让用户输入名字,然后输出Hello + 名字?不过问题没有提到这些,所以可能不需要,但可以作为扩展建议。\n\n总结一下,用户需要的是一个基本的Python Hello World程序,使用Python 3的语法,并且可能需要简单的解释。我应该提供代码示例,说明如何运行,并解释关键点。同时,考虑用户可能的后续问题,比如运行环境,但暂时不需要深入,除非用户进一步询问。\n\n最后,确保回答友好,鼓励用户继续提问。可能用户在学习过程中会有更多问题,所以保持支持性的语气很重要。\n</think>\n\n以下是一个简单的 Python \"Hello World\" 示例代码:\n\n```python\n# 这是一个最基础的 Python 程序\nprint(\"Hello, World!\")\n```\n\n### 代码说明:\n1. `print()` 是 Python 的内置函数,用于将括号内的内容输出到控制台\n2. `\"Hello, World!\"` 是一个字符串,会被原样输出\n3. `#` 开头的行是注释,用于解释代码(不会被执行)\n\n### 运行方法:\n1. 将代码保存为 `hello.py` 文件(扩展名 `.py` 表示 Python 脚本)\n2. 在终端/命令行运行:`python hello.py`\n3. 如果安装正确,会看到输出:`Hello, World!`\n\n### 扩展建议:\n如果需要更互动的版本,可以试试这个:\n```python\nname = input(\"请输入你的名字:\")\nprint(f\"Hello, {name}!\")\n```\n\n需要任何解释或遇到问题都可以随时告诉我","reasoning_content":null,"tool_calls":null},"logprobs":null,"finish_reason":"stop","matched_stop":151645}],"usage":{"prompt_tokens":15,"total_tokens":774,"completion_tokens":759,"prompt_tokens_details":null,"reasoning_tokens":0},"metadata":{"weight_version":"default"}}
输出结果表明模型可以根据给定的输入(在这个例子中是一条测试消息,用python代码写出hello world)生成相应的回复。
