大模型实战|实测:8 卡平头哥 ZW610E,在 GPUStack 上跑通大模型

0 阅读10分钟

一台 8 卡平头哥 PPU 一体机,从环境探测到 1966 tok/s 压测的完整部署实录——按「环境 / 流程 / 实测 / 踩坑」四段展开,照着就能复刻。

项目配置
算力8×PPU-ZW610E,每卡 48GB
软件栈GPUStack v2.3.0 + vLLM 0.23.0(hggc 后端)
镜像交付离线搬运约 16GB 镜像
最终状态3 个模型全部上线

“国产 GPU 到底能不能跑大模型?”这是被问得最多、也最容易被口号带偏的问题。这篇不谈情怀,只把一台平头哥 PPU 一体机的真实部署过程、十三个踩坑和一组压测数字摊开给你看。

01 部署环境

要在这台 8 卡平头哥一体机上跑大模型,先得把环境探清楚。机器是 8 卡平头哥 PPU-ZW610E(每卡 48GB),软件栈是 GPUStack v2.3.0 + vLLM 0.23.0(hggc 后端)。下面三步实测,决定了后面所有的下载与部署路线。

最终状态(已跑通):

模型GPUTP副本状态
qwen3-14bGPU011/1 运行
qwen3-embedding-8bGPU111/1 运行
qwen3-32bGPU4–741/1 运行

统一入口:http://192.168.31.46:8080/v1。32B 加载 62GB 权重加 graph capture 约 12 分钟,三实例修复后 23 分钟零重启。

部署前最该戒掉的习惯,是“照纸面参数办事”。这台机器出厂就枚举了 8 张卡、驱动已预装,原本最担心的“alinux3 无 PPU 驱动”风险直接解除;但另两件事不实测就会埋雷:系统盘装不下 40GB 级镜像,必须把 Docker data-root 迁到 NVMe;整机 80 端口已被自带的 AIStack nginx 占用,GPUStack 只能起在 8080。

还有一项决定后面所有下载路线的关键实测——服务器出口带宽。结论很扎心:服务器出口只有约 1.3 MB/s,大镜像绝不在服务器直下,一律走 Mac 下载 → U 盘 → docker load。

ls /dev/alixpu*   # 8 卡出厂已枚举
ppu-smi           # 驱动预装,每卡 48GiB
curl -sS -m 10 https://quay.io/v2/  # 401=通

02 部署流程

真正的上线只有三步:把 GPUStack 拉起来、把 8 张 PPU 纳管进集群、再把三个模型部署上去。但前置还有两件“搬运”活儿——镜像和权重必须先就位。下面按实操顺序一步一步来。

1. 先把 Docker data-root 迁到 NVMe

系统盘只剩约 48GB 且已用大半,40GB 级镜像直接 load 必爆根盘。改完重启 Docker 再生效:

{
  "data-root": "/bmcp_lvm_fs/docker"
}
systemctl restart docker

2. 镜像与权重离线就位

推理镜像 gpustack/runner:hggc13.0-vllm0.23.0 线上传输约 16.43GB,服务器出口只有约 1.3 MB/s,所以走“Mac 下载 → U 盘 → docker load”。实测三条路线,只有 quay.io 直连能并行(Mac 上 8–16 路 Range 聚合约 12–16 MB/s)。权重由 Mac 从 ModelScope 手动下载,U 盘拷入 /bmcp_lvm_fs/models/hf/models/:

模型权重大小
qwen3-14b约 28GB
qwen3-embedding-8b约 15GB
qwen3-32b约 62GB

别用代理测速:Mac 默认全流量走本机 Clash 代理,测速前必须加 --noproxy '*' 绕开,否则会把代理节点的锅扣在源站头上。

同 digest 可跨源核对:同一镜像在 quay.io 与 Docker Hub 的 digest 完全一致。需要 Hub 镜像时先查 quay.io 有没有同名货,可省一次 16GB 下载。

交付铁律:三次 SHA-256 同值。下载后、拷贝到 U 盘后、docker load 前各算一次 SHA-256,三次同值才算闭环;tar 内置的是裸名 tag,load 后必须补成 quay.io 前缀并删除裸名,否则每次重建部署都会重新拉取 16GB。

3. 启动 GPUStack(Server + Worker 同容器)

整机 80 端口被自带的 AIStack nginx 占用,这里起在 8080;--network=host 让推理容器直接使用宿主网卡,后面多卡通信用得上:

docker run -d --name gpustack \
  --restart=unless-stopped \
  --network=host --ipc=host \
  --device=/dev/alixpu \
  --device=/dev/alixpu_ctl \
  --device=/dev/alixpu_sep \
  --device=/dev/alixpu_ppu0 \
  --device=/dev/alixpu_ppu1 \
  --device=/dev/alixpu_ppu2 \
  --device=/dev/alixpu_ppu3 \
  --device=/dev/alixpu_ppu4 \
  --device=/dev/alixpu_ppu5 \
  --device=/dev/alixpu_ppu6 \
  --device=/dev/alixpu_ppu7 \
  -v /bmcp_lvm_fs/docker/gpustack:/var/lib/gpustack \
  -v /bmcp_lvm_fs/models:/bmcp_lvm_fs/models \
  -v /var/run/docker.sock:/var/run/docker.sock \
  quay.io/gpustack/gpustack:latest \
  --port 8080 --enable-worker \
  --cache-dir /bmcp_lvm_fs/models/gpustack-cache

挂载要点:docker.sock 必须挂,否则 GPUStack 无法下发推理容器;models 目录按宿主路径原样同挂,漏挂会落到空目录。ppu0–ppu7 共 8 张卡,上面的命令完整列出全部设备。

4. 纳管与验收

控制台 http://192.168.31.46:8080 → 资源 → GPU,应看到 8×PPU-ZW610E 全在线、vendor=thead。GPUStack 探测 PPU 靠自带捆绑库,不依赖宿主机 ppu-smi——容器内手跑 ppu-smi 报缺库是正常现象,别被带偏。

GPUStack 控制台概览

GPUStack 控制台概览:集群 1 / 节点 1 / GPU 8 / 部署 3,三模型已全部上线。

GPUStack GPU 列表

GPU 列表:8 张 PPU-ZW610E 全部在线,vendor=thead,显存利用率最高 95%。

5. 部署三个模型

走控制台“模型库 / 部署”或 API POST /v2/models(source=local_path):

模型GPUTP部署要求
qwen3-14bGPU01单卡部署
qwen3-embedding-8bGPU11部署类别选 embedding
qwen3-32bGPU4–74必须配置 NCCL env

必带项:多卡要改网卡名。32B 四卡 TP=4 起不来,报 NCCL ERROR Bootstrap: no socket interface found——根因是推理容器走 host 网络,却默认注入 NCCL_SOCKET_IFNAME=eth0,而本机网卡是 bond0。模型 env 覆盖 NCCL_SOCKET_IFNAME=bond0(模型级优先)。今后任何多卡部署都必须带这一条。

  • gpu_ids 写法:worker名:类型:序号,本机写作 aistack:hggc:4,不是直觉里的 1/4
  • 必须开启 enable_model_route=true,否则网关显示 0 模型
  • embedding 部署类别要选 embedding,路由跟着类别走

6. 验收三连

网关 models / chat / embeddings 都通,且 docker ps 无重启循环,才算真的上线:

curl http://192.168.31.46:8080/v1/models \
  -H "Authorization: Bearer $KEY"

curl http://192.168.31.46:8080/v1/chat/completions \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen3-32b","messages":[{"role":"user","content":"你好"}],"max_tokens":64}'

curl http://192.168.31.46:8080/v1/embeddings \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen3-embedding-8b","input":"测试文本"}'

一页速查(照此走)

环节关键操作验收标准
探测检查 /dev/alixpu*、ppu-smi、系统盘余量和出口带宽以实测结果确定路线
Dockerdata-root 先迁 NVMe,GPUStack 使用 8080 端口系统盘不被镜像占满
镜像Mac 走 quay.io 并行分段下载,使用 --noproxy '*'SHA-256 三次同值,load 后只留一个 quay.io tag
权重ModelScope 手动下载落到 /bmcp_lvm_fs/models/hf/models/
部署TP∈{1,2,4,8},锁同一 NUMA 组TP>1 带 NCCL_SOCKET_IFNAME=bond0,开启 enable_model_route=true
验收调用 models / chat / embeddings三连全通,docker ps 无重启循环

03 性能实测

压测走服务端本机直打网关(含 LB / 鉴权,等同客户端真实链路),SSE 流式取真实 TTFT,54 档全部 0 错误。先放一张 14B 与 32B 的峰值对照:

模型卡数TP短文本峰值
qwen3-32B441966 tok/s
qwen3-14B111223 tok/s

下面是完整并发扫描的原始数据(54 档全部 0 错误)。先看短 / 中文本的吞吐与 TPOT 拐点——14B 与 32B 的拐点都在 c=64,再往上只是排队变长,不再多产 token。

表 1:qwen3-14b 并发扫描(单卡 · 短 / 中文本)

并发短·吞吐短·TPOT中·吞吐中·TPOT
137.526.536.827.1
8251.631.1248.532.0
16485.031.7475.633.3
32884.134.6670.736.1
641219.149.3921.551.4
961223.349.3923.651.3
1281222.249.4921.651.3

表 2:qwen3-32b 并发扫描(4 卡 TP=4 · 短 / 中文本)

并发短·吞吐短·TPOT中·吞吐中·TPOT
150.919.449.420.2
8351.322.1321.823.1
16672.922.8590.623.7
321196.325.2892.625.9
641965.129.91350.331.4
961756.830.01342.431.4
1281966.330.21353.231.4

表 3:长文本(入约 6000 / 出 1500)SLO 上限

SLO:TTFT p95 ≤1s 且 TPOT ≤60ms。

模型并发吞吐TTFT p95TPOTSLO 达标
14B16409.70.246s36.7100%
14B32694.60.404s43.7100%
14B48642.60.572s54.829.7%
14B64891.40.635s67.70%
32B32835.70.353s29.1100%
32B48806.40.471s33.4100%
32B641089.01.22s36.717.2%
32B64(复测)1103.50.627s36.8100%

表 4:qwen3-embedding-8b 向量并发(每请求 8 条、约 512 字)

并发req/s输入 tok/s均时延p95 时延
15.695,2310.18s0.18s
410.329,4980.35s0.43s
814.9813,7810.45s0.69s
1616.1214,8340.87s1.15s

拐点小结:短 / 中文本 14B、32B 均在 c=64 触顶(再涨仅排队);长文本 14B 上限 32 路(48 路起吞吐反降,KV 抢占重算),32B 上限 48 路(64 路两次复测差异大,判为偶发抖动,对外按 48 路承诺)。SLO 判据:TTFT p95 ≤1s 且 TPOT ≤60ms。

选型结论:短对话场景 32B 不划算——4 倍显存换 1.6 倍吞吐,每卡效率只有 14B 的 40%。但长上下文场景 32B 优势明显放大——SLO 内并发 1.5 倍,且 14B 长文本会因 KV 不足触发抢占重算(吞吐反降 7.5%),32B 四卡 KV 空间大得多。高校 RAG、知识库、长文档优先 32B;短问答与高并发轻量场景用 14B。

对外可承诺运营参数

均按 SLO 内数据、留余量取,上限值已进抖动 / 违约区,不对外承诺:

模型建议并发可承诺吞吐TTFTTPOT / 时延
qwen3-14B短 / 中 32 路;长文 32 路短 / 中约 880 tok/s;长文约 695 tok/s≤0.40sTPOT ≤44ms
qwen3-32B短 / 中 32–48 路;长文 48 路短 / 中约 1200–1600 tok/s;长文约 806 tok/s≤0.47sTPOT ≤33ms
qwen3-embedding-8B向量并发 8 路约 1.38 万输入 tok/s—时延 ≤0.45s

GPUStack 模型广场

模型广场:qwen3-32b / qwen3-embedding-8b / qwen3-14b 三模型均显示绿色可用状态。

GPUStack 部署列表

部署列表:三个模型副本数均为 1/1,运行状态正常。

04 踩坑分享

完整踩坑清单有十三条,这里挑四个最隐蔽、也最容易被反复踩的。

P7:NCCL 找不到网卡

32B 四卡 TP=4 起不来,报:

NCCL ERROR Bootstrap: no socket interface found

根因:GPUStack 推理容器走 host 网络,却默认注入 NCCL_SOCKET_IFNAME=eth0,而本机网卡是 bond0,根本没有 eth0。TP=1 不碰 NCCL,所以 14B 和 Embedding 一直活得好好的,把这个雷完美掩盖。

修复:模型 env 覆盖 NCCL_SOCKET_IFNAME=bond0(PUT /v2/models/{id} 带 env,模型级优先)。今后任何 TP>1 部署都必须带这一条。

P6:Embedding 永远 pending

类别误选 LLM 又开了跨卡分布式,调度要求 TP=7,但 32 个注意力头不能被 7 整除。TP 只能取 1/2/4/8;embedding 单卡足够,且部署类别要选对,路由跟着类别走。

P10:自定义 Runner Source 清空后端

该 source 是整表替换内置后端目录,只放几行 runner 会把后端定义清空,新实例报 Inference backend vllm not found。本地已有镜像时根本不需要 Runner Source,回滚 custom=null 再 reload 即可。

P5:gpu_ids 格式

API 建部署报 Invalid GPU ID,因为 gpu_ids 格式是 worker名:类型:序号(本机写作 aistack:hggc:4),不是直觉里的 1/4。

这条路,已经跑通

国产算力 + GPUStack 这条路,在这台 8 卡平头哥一体机上已经不是“能不能”的问题,而是“怎么稳”的问题。从驱动探测、16GB 镜像离线搬运,到十三个坑的逐一排除,再到 1966 tok/s 的压测背书,整套流程可复刻、可承诺。

也别神话它:思考模式、超 8K 长文、单副本无冗余、32B 长文 64 路偶发抖动,这几项在承诺对外能力前还要补测。先把可承诺的运营参数守住,再谈扩展。

国产 GPU 跑大模型,已经可以从口号变成交付。