# Qwen3.5-122B-A10B-GPTQ-Int4 部署实录
## 一、下载模型
### 1. 创建模型存储目录
```bash
mkdir -p /models
```
### 2. 安装 ModelScope 下载工具
```bash
pip install modelscope
```
### 3. 后台下载模型
```bash
nohup modelscope download --model Qwen/Qwen3.5-122B-A10B-GPTQ-Int4 \
--local_dir /models/Qwen3.5-122B-A10B-GPTQ-Int4 > /tmp/dl.log 2>&1 &
```
### 4. 确认下载进程
```bash
sleep 10 && tail -5 /tmp/dl.log
```
---
## 二、部署 vLLM 推理服务
> 每次重启主机后需清理旧容器再重建:`docker rm -f llm-server`
### 1. 系统优化
```bash
# 关闭 NUMA 平衡(避免跨节点内存迁移导致性能下降)
sudo sh -c 'echo 0 > /proc/sys/kernel/numa_balancing'
```
### 2. 启动 Docker 容器
```bash
docker run -dit \
--shm-size 200g \
--network=host \
--name llm-server \
--privileged \
--device=/dev/kfd \
--device=/dev/dri \
--device=/dev/mkfd \
--group-add video \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
-u root \
-v /opt/hyhal/:/opt/hyhal/:ro \
-v /models:/models \
harbor.sourcefind.cn:5443/dcu/admin/base/custom:vllm018-ubuntu22.04-dtk26.04-gemma4-0413 \
bash
```
> **说明:** 挂载 `/dev/kfd`、`/dev/dri`、`/dev/mkfd` 用于 DCU 设备透传;`/opt/hyhal` 为海光 HAL 层只读挂载。
### 3. 启动双实例 vLLM 服务
> **为什么不使用 8 卡 TP?** K100-AI 采用 PCIe 互联(32 GB/s),8 卡 TP 时 all-reduce 通信占总耗时 80%+,输出速度约 24 t/s。双实例 4 卡 TP 方案下每组内部通信量减半,组间零通信,实测输出速度提升至 25\~27 t/s,且并发吞吐翻倍。
**GPU-NUMA 拓扑映射:**
| DCU 卡 | NUMA 节点 | CPU 核心 |
|--------|----------|---------|
| 卡 0\~3 | NUMA 节点 3 | 24\~31, 88\~95 |
| 卡 4\~7 | NUMA 节点 7 | 56\~63, 120\~127 |
**实例 1(卡 0\~3,端口 8002):**
```bash
docker exec -d llm-server bash -c '
export HIP_VISIBLE_DEVICES=0,1,2,3
export ALLREDUCE_STREAM_WITH_COMPUTE=1
nohup vllm serve /models/Qwen3.5-122B-A10B-GPTQ-Int4 \
--port 8002 \
--trust-remote-code \
--dtype float16 \
--quantization gptq \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--served-model-name Qwen3.5-122B \
--default-chat-template-kwargs '\''{"enable_thinking": false}'\'' \
--reasoning-parser qwen3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder > /tmp/llm1.log 2>&1 &
'
```
**实例 2(卡 4\~7,端口 8003):**
```bash
docker exec -d llm-server bash -c '
export HIP_VISIBLE_DEVICES=4,5,6,7
export ALLREDUCE_STREAM_WITH_COMPUTE=1
nohup vllm serve /models/Qwen3.5-122B-A10B-GPTQ-Int4 \
--port 8003 \
--trust-remote-code \
--dtype float16 \
--quantization gptq \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--served-model-name Qwen3.5-122B \
--default-chat-template-kwargs '\''{"enable_thinking": false}'\'' \
--reasoning-parser qwen3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder > /tmp/llm2.log 2>&1 &
'
```
**关键参数说明:**
| 参数 | 含义 |
|------|------|
| `HIP_VISIBLE_DEVICES` | 指定使用的 DCU 卡,实例1 用 0\~3,实例2 用 4\~7 |
| `ALLREDUCE_STREAM_WITH_COMPUTE=1` | all-reduce 与计算重叠,减少通信等待 |
| `--quantization gptq` | GPTQ-Int4 量化推理 |
| `--tensor-parallel-size 4` | 每实例 4 卡张量并行 |
| `--gpu-memory-utilization 0.90` | GPU 显存占用上限 90% |
| `--max-model-len 8192` | 最大上下文长度 8K |
| `--default-chat-template-kwargs {"enable_thinking": false}` | 关闭思维链,减少输出 token 数 |
| `--enable-auto-tool-choice` | 启用工具调用 |
### 4. 等待模型加载
```bash
sleep 300 && docker exec llm-server bash -c 'tail -3 /tmp/llm1.log'
docker exec llm-server bash -c 'tail -3 /tmp/llm2.log'
```
> 模型加载通常需要 5\~8 分钟,日志中出现 `Application startup complete` 即表示服务就绪。
### 5. 预热(触发 CUDA Graph 编译)
```bash
for i in 1 2 3; do
curl -s http://localhost:8002/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3.5-122B","messages":[{"role":"user","content":"你好"}],"max_tokens":32}' > /dev/null
curl -s http://localhost:8003/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3.5-122B","messages":[{"role":"user","content":"你好"}],"max_tokens":32}' > /dev/null
done
```
> 首次请求会触发 CUDA Graph 编译,耗时约 30 秒,预热后速度正常。
---
## 三、Nginx 负载均衡
### 1. 安装 Nginx
```bash
apt install -y nginx
```
### 2. 配置负载均衡
```bash
cat > /etc/nginx/conf.d/llm-lb.conf << 'EOF'
upstream llm_backend {
least_conn;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
server {
listen 8001;
client_max_body_size 200m;
location / {
proxy_pass http://llm_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 300s;
proxy_buffering off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}
}
EOF
```
### 3. 启动 Nginx
```bash
# 首次启动
nginx
# 后续修改配置后重载
nginx -s reload
```
### 4. 验证负载均衡
```bash
# 分别测试两个实例
curl -s http://localhost:8002/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3.5-122B","messages":[{"role":"user","content":"你好"}],"max_tokens":32}'
curl -s http://localhost:8003/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3.5-122B","messages":[{"role":"user","content":"你好"}],"max_tokens":32}'
# 测试 Nginx 负载均衡
curl -s http://localhost:8001/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3.5-122B","messages":[{"role":"user","content":"你好"}],"max_tokens":32}'
```
> 三个请求均返回正常 JSON 响应即表示负载均衡配置成功。
---
## 四、Open WebUI 部署
### 1. 拉取镜像
```bash
# 配置 Docker 镜像加速源
sudo tee /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://docker.1panel.live",
"https://hub.rat.dev"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
# 拉取 Open WebUI 镜像
docker pull dyrnq/open-webui:main
```
### 2. 启动 Open WebUI
```bash
docker run -dit \
--name open-webui \
--network=host \
-v /data/open-webui:/app/backend/data \
-e OPENAI_API_BASE_URLS="http://localhost:8001/v1" \
-e OPENAI_API_KEYS="EMPTY" \
-e RAG_EMBEDDING_ENGINE="openai" \
-e RAG_EMBEDDING_BASE_URL="http://localhost:8001/v1" \
-e RAG_EMBEDDING_MODEL="Qwen3.5-122B" \
-e ENABLE_RAG_WEB_SEARCH="false" \
-e HF_HUB_OFFLINE="1" \
-e TRANSFORMERS_OFFLINE="1" \
--restart unless-stopped \
dyrnq/open-webui:main
```
**关键环境变量说明:**
| 变量 | 值 | 说明 |
|------|---|------|
| `OPENAI_API_BASE_URLS` | `http://localhost:8001/v1` | 对接 Nginx 负载均衡地址 |
| `OPENAI_API_KEYS` | `EMPTY` | vLLM 不校验 Key,随便填 |
| `HF_HUB_OFFLINE` | `1` | 禁止从 HuggingFace 下载(无外网) |
| `TRANSFORMERS_OFFLINE` | `1` | 禁止在线下载 Transformers 模型 |
| `RAG_EMBEDDING_ENGINE` | `openai` | 嵌入引擎使用 OpenAI 兼容接口 |
### 3. 访问 Open WebUI
浏览器打开:
```
http://192.168.39.232:8080
```
首次访问需注册管理员账号,登录后确认:
- **API 地址**:`http://localhost:8001/v1`
- **模型名**:`Qwen3.5-122B`
- **API Key**:`EMPTY`
### 4. 如果 Open WebUI 无法启动
```bash
# 查看容器状态
docker ps | grep open-webui
# 查看启动日志
docker logs open-webui --tail 30
# 如果显示 HuggingFace 下载失败,确认环境变量 HF_HUB_OFFLINE=1
docker rm -f open-webui
# 重新按步骤 2 启动
```
---
## 五、性能基准测试
### 1. 测试脚本
```bash
cat > /tmp/llm-bench.sh << 'SCRIPT'
#!/bin/bash
BASE_URL="http://localhost:8001/v1/chat/completions"
MODEL="Qwen3.5-122B"
PROMPT="你好,请用一句话介绍通义千问3.5模型"
MAX_TOKENS=128
ROUNDS=5
echo "=========================================="
echo " LLM Speed 基准测试"
echo "=========================================="
echo "目标: $BASE_URL"
echo "模型: $MODEL"
echo "轮次: $ROUNDS | max_tokens: $MAX_TOKENS"
echo "------------------------------------------"
total_ttft=0
total_speed=0
total_time=0
success=0
fail=0
for i in $(seq 1 $ROUNDS); do
result=$(curl -s -w "\n%{time_starttransfer} %{time_total}" \
"$BASE_URL" \
-H "Content-Type: application/json" \
-d "{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"$PROMPT\"}],\"temperature\":0.7,\"max_tokens\":$MAX_TOKENS}")
timing=$(echo "$result" | tail -1)
ttft=$(echo "$timing" | awk '{print $1}')
total=$(echo "$timing" | awk '{print $2}')
json=$(echo "$result" | sed '$d')
completion_tokens=$(echo "$json" | python3 -c "
import sys,json
try:
r=json.load(sys.stdin)
print(r.get('usage',{}).get('completion_tokens',0))
except:
print(0)
" 2>/dev/null)
if [ "$completion_tokens" -gt 0 ] 2>/dev/null; then
speed=$(echo "$completion_tokens $total" | awk '{printf "%.2f", $1/$2}')
ttft_ms=$(echo "$ttft" | awk '{printf "%.0f", $1*1000}')
echo " 第${i}轮 | 首字:${ttft_ms}ms | 总时:${total}s | tokens:${completion_tokens} | 速度:${speed} t/s"
total_ttft=$(echo "$total_ttft $ttft" | awk '{printf "%.4f", $1+$2}')
total_speed=$(echo "$total_speed $speed" | awk '{printf "%.2f", $1+$2}')
total_time=$(echo "$total_time $total" | awk '{printf "%.2f", $1+$2}')
success=$((success+1))
else
echo " 第${i}轮 | 失败"
fail=$((fail+1))
fi
done
echo "=========================================="
if [ $success -gt 0 ]; then
avg_ttft=$(echo "$total_ttft $success" | awk '{printf "%.0f", $1/$2*1000}')
avg_speed=$(echo "$total_speed $success" | awk '{printf "%.2f", $1/$2}')
avg_time=$(echo "$total_time $success" | awk '{printf "%.2f", $1/$2}')
qps=$(echo "$success $total_time" | awk '{printf "%.2f", $1/$2}')
echo " 成功: $success | 失败: $fail"
echo " 平均首字延迟: ${avg_ttft}ms"
echo " 平均输出速度: ${avg_speed} t/s"
echo " 平均总时间: ${avg_time}s"
echo " QPS: $qps"
else
echo " 全部失败,请检查服务状态"
fi
echo "=========================================="
SCRIPT
chmod +x /tmp/llm-bench.sh
```
### 2. 运行测试
```bash
bash /tmp/llm-bench.sh
```
### 3. 实测性能数据
| 方案 | 平均输出速度 | 平均首字延迟 | 并发 QPS |
|------|------------|------------|---------|
| 8 卡 TP=8 单实例 | ~24 t/s | ~3.3s | 0.76 |
| **4 卡 TP=4 双实例** | **~25 t/s** | **~2.0s** | **~0.5/实例** |
| 双实例合计吞吐 | ~25 t/s × 2 | ~2.0s | **~1.0** |
**性能瓶颈分析:**
Qwen3.5-122B-A10B 是 MoE 模型(122B 总参数,10B 激活),GPU 计算只需 ~8ms/token,但 4 卡 all-reduce 通信走 PCIe 需 ~30ms/token。通信占总耗时 80%,是当前硬件下的性能天花板。量化方式、CPU、内存、硬盘均不是瓶颈。
---
## 六、服务架构总览
```
┌──────────────────────┐
│ Open WebUI :8080 │
│ (浏览器聊天界面) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Nginx :8001 │
│ (least_conn 负载均衡) │
└────┬────────────┬─────┘
│ │
┌────────▼───┐ ┌────▼───────┐
│ 实例1 :8002 │ │ 实例2 :8003 │
│ 卡 0~3 │ │ 卡 4~7 │
│ NUMA 节点3 │ │ NUMA 节点7 │
│ TP=4 │ │ TP=4 │
└────────────┘ └────────────┘
```
---