llm-d
在一台机器、一张昇腾 NPU 上,用三个普通进程搭起 llm-d 的最小推理路径。
llm-d 是架在推理引擎之上的路由层。最小部署是三个进程:
vLLM worker:真正跑模型,本文用 vllm-ascend。
EPP:Endpoint Picker,根据本地
endpoints.yaml选 worker,二进制来自 llm-d-router。Envoy:接收客户端请求,先问 EPP,再转到选中的 worker。
请求路径:客户端 POST :8081 → Envoy → EPP :9002 做路由决策 → vLLM :8000。
本文基于上游 no-kubernetes-deployment 指南,把 model server 换成 vllm-ascend,并把模型换成适合单卡验证的 Qwen/Qwen3-0.6B。工作目录统一用 /root/llm-d。
前置条件
硬件
Atlas 800T / 900 A2 训练系列(Ascend 910B)。本文示例为单卡。
软件
类别 |
要求 |
|---|---|
CANN |
toolkit 已安装,并且可以 |
NNAL |
可以 |
Python |
落在当前 CANN 镜像和 vLLM-Ascend 安装说明 的范围内 |
本文验证环境
下表是编写本文时跑通的组合,不是唯一支持组合。
项目 |
内容 |
|---|---|
机器 |
Atlas 900 A2,Ascend 910B4,单卡 |
镜像 |
|
Python |
3.12,来自上面的镜像 |
CANN |
9.1.0 |
llm-d 配置仓 |
Release tag,看护里替换尖括号里的版本 |
EPP |
llm-d-router v0.10.0 |
Go |
1.26.6 linux-arm64 |
vLLM |
0.23.0 |
vllm-ascend |
0.23.0 |
triton-ascend |
3.2.2 |
Envoy |
1.33.2 linux-aarch64 |
模型 |
阅读本文前,请先按 快速安装昇腾环境 装好 CANN、NNAL 与驱动。
1. 加载 CANN 与 NNAL
加载 CANN 与 NNAL,并把 /usr/local/sbin 和 /usr/local/bin 加入 PATH。
export PATH=/usr/local/sbin:/usr/local/bin:$PATH
source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/nnal/atb/set_env.sh
2. 确认 NPU 在线
npu-smi info
输出类似:
+------------------------------------------------------------------------------------------------+
| npu-smi 25.5.2 Version: 25.5.2 |
+---------------------------+---------------+----------------------------------------------------+
| NPU Name | Health | Power(W) Temp(C) Hugepages-Usage(page)|
| Chip | Bus-Id | AICore(%) Memory-Usage(MB) HBM-Usage(MB) |
+===========================+===============+====================================================+
| 0 910B3 | OK | 91.3 36 0 / 0 |
| 0 | 0000:C1:00.0 | 0 0 / 0 3465 / 65536 |
+===========================+===============+====================================================+
+---------------------------+---------------+----------------------------------------------------+
| NPU Chip | Process id | Process name | Process memory(MB) |
+===========================+===============+====================================================+
| No running processes found in NPU 0 |
+===========================+===============+====================================================+
若提示找不到 npu-smi,回到 快速安装昇腾环境 检查驱动与设备挂载。
启动前确认本机这些端口空闲:8000(vLLM)、8081(Envoy 入口)、9002 / 9003 / 9090(EPP)、19000(Envoy 管理口)。
3. 安装 vLLM Ascend
安装 vllm、vllm-ascend 和 triton-ascend。
python -m pip install --retries 3 vllm==0.23.0
python -m pip install \
--extra-index-url https://mirrors.huaweicloud.com/ascend/repos/pypi/variant \
--extra-index-url https://mirrors.huaweicloud.com/ascend/repos/pypi \
vllm-ascend==0.23.0
python -m pip install --force-reinstall --no-deps \
--extra-index-url https://mirrors.huaweicloud.com/ascend/repos/pypi \
triton-ascend==3.2.2
python -c "from importlib.metadata import version; print('vllm', version('vllm')); print('vllm-ascend', version('vllm-ascend')); print('triton-ascend', version('triton-ascend'))"
完整输出较长,其中应包含:
...vllm 0.23.0
vllm-ascend 0.23.0
triton-ascend 3.2.2
4. 构建 EPP
用 Go 1.26.6 编译 llm-d-router 的 cmd/epp。
mkdir -p /root/llm-d/bin
if ! /root/llm-d/go/bin/go version 2>/dev/null | grep -q 'go1.26.6'; then
if [ ! -f /root/llm-d/go1.26.6.linux-arm64.tar.gz ]; then
curl -fL --retry 3 --retry-delay 5 --connect-timeout 30 --max-time 600 \
-o /root/llm-d/go1.26.6.linux-arm64.tar.gz \
https://go.dev/dl/go1.26.6.linux-arm64.tar.gz
fi
rm -rf /root/llm-d/go
tar -C /root/llm-d -xzf /root/llm-d/go1.26.6.linux-arm64.tar.gz
fi
export GOROOT=/root/llm-d/go
export GOPATH=/root/llm-d/gopath
export PATH="$GOROOT/bin:$PATH"
mkdir -p "$GOPATH"
if [ ! -x /root/llm-d/bin/epp ]; then
rm -rf /root/llm-d/llm-d-router
for _ in 1 2 3; do
GIT_TERMINAL_PROMPT=0 git clone --depth 1 --branch v0.10.0 \
https://github.com/llm-d/llm-d-router /root/llm-d/llm-d-router && break
rm -rf /root/llm-d/llm-d-router
sleep 5
done
cd /root/llm-d/llm-d-router
go build -o /root/llm-d/bin/epp ./cmd/epp
fi
/root/llm-d/go/bin/go version
test -x /root/llm-d/bin/epp
/root/llm-d/bin/epp --help >/dev/null
输出结果如下:
go version go1.26.6 linux/arm64
5. 获取 Envoy
下载 Envoy 1.33.2 的 Linux ARM64 二进制。
mkdir -p /root/llm-d/bin
if [ ! -x /root/llm-d/bin/envoy ]; then
curl -fL --retry 3 --retry-delay 5 --connect-timeout 30 --max-time 1800 \
-o /root/llm-d/bin/envoy.part \
https://github.com/envoyproxy/envoy/releases/download/v1.33.2/envoy-1.33.2-linux-aarch_64
chmod 0755 /root/llm-d/bin/envoy.part
mv /root/llm-d/bin/envoy.part /root/llm-d/bin/envoy
fi
/root/llm-d/bin/envoy --version
输出里应包含:
.../root/llm-d/bin/envoy version: .../1.33.2/Clean/RELEASE/BoringSSL...
6. 准备 llm-d 配置
克隆 llm-d,把 no-kubernetes 指南里的三份 YAML 复制到工作目录,并把模型改成 Qwen/Qwen3-0.6B。复制后的 endpoints.yaml 使用字面 IPv4 地址。
将下面克隆命令里的 <ref> 换成要用的 llm-d Release tag,例如 v0.9.0。
mkdir -p /root/llm-d
if [ ! -f /root/llm-d/src/guides/no-kubernetes-deployment/router/epp/config.yaml ]; then
rm -rf /root/llm-d/src
for _ in 1 2 3; do
GIT_TERMINAL_PROMPT=0 git clone --depth 1 --branch <ref> \
https://github.com/llm-d/llm-d /root/llm-d/src && break
rm -rf /root/llm-d/src
sleep 5
done
fi
cp /root/llm-d/src/guides/no-kubernetes-deployment/router/epp/config.yaml /root/llm-d/config.yaml
cp /root/llm-d/src/guides/no-kubernetes-deployment/router/epp/endpoints.yaml /root/llm-d/endpoints.yaml
cp /root/llm-d/src/guides/no-kubernetes-deployment/router/envoy/envoy.yaml /root/llm-d/envoy.yaml
sed -i 's|/etc/epp/endpoints.yaml|/root/llm-d/endpoints.yaml|' /root/llm-d/config.yaml
sed -i 's|Qwen/Qwen3-32B|Qwen/Qwen3-0.6B|' /root/llm-d/endpoints.yaml
grep '/root/llm-d/endpoints.yaml' /root/llm-d/config.yaml
grep 'Qwen/Qwen3-0.6B' /root/llm-d/endpoints.yaml
输出结果如下:
path: /root/llm-d/endpoints.yaml # EPP must have network access to all worker IPs for metrics scraping
model: Qwen/Qwen3-0.6B
7. 启动 vLLM
在 0 号卡上启动 vLLM,模型与服务名都是 Qwen/Qwen3-0.6B。
mkdir -p /root/llm-d
export ASCEND_RT_VISIBLE_DEVICES=0
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
port_busy() {
(echo >/dev/tcp/127.0.0.1/"$1") >/dev/null 2>&1
}
vllm_alive() {
[ -f /root/llm-d/vllm.pid ] || return 1
pid=$(cat /root/llm-d/vllm.pid)
kill -0 "$pid" 2>/dev/null || return 1
tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q vllm
}
if vllm_alive; then
echo 'vllm already running' >&2
else
if port_busy 8000; then
echo 'port 8000 is already in use by another process' >&2
exit 1
fi
setsid -f sh -c 'echo $$ > /root/llm-d/vllm.pid; exec vllm serve Qwen/Qwen3-0.6B --host 127.0.0.1 --port 8000 --served-model-name Qwen/Qwen3-0.6B --max-model-len 2048 --max-num-seqs 4 --gpu-memory-utilization 0.3 --trust-remote-code --enforce-eager >/root/llm-d/vllm.log 2>&1'
fi
for _ in $(seq 1 50); do
[ -s /root/llm-d/vllm.pid ] && break
sleep 0.2
done
pid=$(cat /root/llm-d/vllm.pid)
ok=0
for _ in $(seq 1 480); do
if ! kill -0 "$pid" 2>/dev/null; then
echo 'vllm process exited' >&2
tail -c 8000 /root/llm-d/vllm.log >&2 || true
exit 1
fi
if curl -sf --max-time 5 http://127.0.0.1:8000/v1/models | grep -q 'Qwen/Qwen3-0.6B'; then
curl -sS --max-time 5 http://127.0.0.1:8000/v1/models
ok=1
break
fi
sleep 5
done
[ "$ok" = 1 ]
完整输出较长,其中应包含:
...Qwen/Qwen3-0.6B...
8. 启动 EPP
启动 EPP,配置文件是工作目录里的 config.yaml。
mkdir -p /root/llm-d
port_busy() {
(echo >/dev/tcp/127.0.0.1/"$1") >/dev/null 2>&1
}
epp_alive() {
[ -f /root/llm-d/epp.pid ] || return 1
pid=$(cat /root/llm-d/epp.pid)
kill -0 "$pid" 2>/dev/null || return 1
tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q /root/llm-d/bin/epp
}
if epp_alive; then
echo 'epp already running' >&2
else
if port_busy 9002 || port_busy 9003 || port_busy 9090; then
echo 'EPP ports 9002/9003/9090 are already in use' >&2
exit 1
fi
setsid -f sh -c 'echo $$ > /root/llm-d/epp.pid; exec /root/llm-d/bin/epp --config-file=/root/llm-d/config.yaml --pool-name=file-discovery --pool-namespace=default --grpc-port=9002 --grpc-health-port=9003 --metrics-port=9090 --secure-serving=false --v=2 >/root/llm-d/epp.log 2>&1'
fi
for _ in $(seq 1 50); do
[ -s /root/llm-d/epp.pid ] && break
sleep 0.2
done
pid=$(cat /root/llm-d/epp.pid)
ok=0
for _ in $(seq 1 60); do
if ! kill -0 "$pid" 2>/dev/null; then
echo 'epp process exited' >&2
tail -c 8000 /root/llm-d/epp.log >&2 || true
exit 1
fi
if curl -sf --max-time 5 http://127.0.0.1:9090/metrics | grep -m1 '^# HELP inference_extension_info '; then
ok=1
break
fi
sleep 2
done
[ "$ok" = 1 ]
输出结果如下:
# HELP inference_extension_info [ALPHA] General information of the current build of Inference Extension.
9. 启动 Envoy
启动 Envoy,入口端口是 8081,管理端口是 19000。
mkdir -p /root/llm-d
port_busy() {
(echo >/dev/tcp/127.0.0.1/"$1") >/dev/null 2>&1
}
envoy_alive() {
[ -f /root/llm-d/envoy.pid ] || return 1
pid=$(cat /root/llm-d/envoy.pid)
kill -0 "$pid" 2>/dev/null || return 1
tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q envoy
}
if envoy_alive; then
echo 'envoy already running' >&2
else
if port_busy 8081 || port_busy 19000; then
echo 'Envoy ports 8081/19000 are already in use' >&2
exit 1
fi
setsid -f sh -c 'echo $$ > /root/llm-d/envoy.pid; exec /root/llm-d/bin/envoy --service-node envoy-proxy --log-level warn --concurrency 2 --drain-strategy immediate --drain-time-s 60 -c /root/llm-d/envoy.yaml >/root/llm-d/envoy.log 2>&1'
fi
for _ in $(seq 1 50); do
[ -s /root/llm-d/envoy.pid ] && break
sleep 0.2
done
pid=$(cat /root/llm-d/envoy.pid)
ok=0
for _ in $(seq 1 60); do
if ! kill -0 "$pid" 2>/dev/null; then
echo 'envoy process exited' >&2
tail -c 8000 /root/llm-d/envoy.log >&2 || true
exit 1
fi
if curl -sf --max-time 5 http://127.0.0.1:19000/ready >/dev/null; then
curl -sS --max-time 5 http://127.0.0.1:19000/ready
ok=1
break
fi
sleep 2
done
[ "$ok" = 1 ]
输出结果如下:
LIVE
10. 发送请求验证
把补全请求发到 Envoy 的 8081 端口。temperature 为 0,seed 为 42,max_tokens 为 8。
curl -sS http://127.0.0.1:8081/v1/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen/Qwen3-0.6B","prompt":"Hello","max_tokens":8,"temperature":0,"seed":42}'
完整输出较长,其中应包含:
...text_completion...Qwen/Qwen3-0.6B...
11. 确认跑在 NPU 上
在 vLLM 日志里查看昇腾通信后端。
grep -m1 'backend=hccl' /root/llm-d/vllm.log
完整输出较长,其中应包含:
...backend=hccl...
Kubernetes 部署、多模型路由和其余模块与上游社区相同。见 llm-d 文档。