Ascend 950 单机 8 卡,用 vLLM 跑通 DeepSeek-V4-Flash
目标只有一个:在 RKE2 集群的一个节点上,用 vLLM-Ascend 镜像起一个 Pod,把 DeepSeek 跑起来,OpenAI 兼容接口能正常对话,然后收工。
不做跨节点推理,不做 P/D 分离,不接 KVcache,不做性能调优。
一、环境
| 项目 | 配置 |
|---|---|
| Kubernetes | RKE2,容器运行时 containerd |
| NPU | 每节点 8 张 Ascend950DT,单卡 96 GiB |
| 驱动 | npu-smi 25.6.rc1,宿主机 CANN 9.1.0 |
| 模型 | 宿主机 /data/models/DeepSeek-V4-Flash,约 149G |
| 端口 | 8000 |
二、确认模型和驱动
在部署节点 mgmt013 上执行:
ls -lh /data/models/DeepSeek-V4-Flash/config.json
find /usr/local/Ascend/driver -name 'libascend_hal.so*'
ls -l /usr/local/bin/npu-smi /usr/local/dcmi
npu-smi info
本环境 libascend_hal.so 在 /usr/local/Ascend/driver/lib64/driver/libascend_hal.so,YAML 里的 LD_LIBRARY_PATH 就是照这个路径写的。
npu-smi info 确认 8 张卡都空闲,没有别的推理任务占着;再确认 8000 端口没人监听。
三、YAML
存成 vllm-deepseek-v4.yaml。下面只列关键字段,驱动和设备挂载按 volumes 里的写法照着补:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-deepseek-v4
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels: { app: vllm-deepseek-v4 }
template:
metadata:
labels: { app: vllm-deepseek-v4 }
spec:
nodeName: mgmt013 # 钉在有模型、有镜像的那台
hostNetwork: true # 直接用宿主网络,不需要 Service
dnsPolicy: ClusterFirstWithHostNet
terminationGracePeriodSeconds: 60
containers:
- name: vllm
image: quay.io/ascend/vllm-ascend:nightly-main-a5
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
command: ["/bin/bash", "-ec"]
args:
- |
ulimit -n 1048576
ulimit -l unlimited
export LD_LIBRARY_PATH="/usr/local/Ascend/driver/lib64/driver:/usr/local/Ascend/driver/lib64/common:/usr/local/Ascend/driver/lib64:/usr/local/dcmi:${LD_LIBRARY_PATH:-}"
exec vllm serve /models/DeepSeek-V4-Flash \
--host 0.0.0.0 --port 8000 \
--served-model-name dsv4 \
--tensor-parallel-size 1 \
--data-parallel-size 8 \
--enable-expert-parallel \
--enable-chunked-prefill \
--max-model-len 32768 \
--max-num-batched-tokens 4096 \
--max-num-seqs 8 \
--gpu-memory-utilization 0.9 \
--block-size 32 \
--tokenizer-mode deepseek_v4 \
--tool-call-parser deepseek_v4 \
--enable-auto-tool-choice \
--reasoning-parser deepseek_v4 \
--kv-cache-dtype fp8 \
--attention_config.indexer_kv_dtype fp8 \
--enforce-eager
env:
- { name: ASCEND_RT_VISIBLE_DEVICES, value: "0,1,2,3,4,5,6,7" }
- { name: OMP_PROC_BIND, value: "false" }
- { name: OMP_NUM_THREADS, value: "10" }
- { name: PYTORCH_NPU_ALLOC_CONF, value: "expandable_segments:True" }
- { name: TASK_QUEUE_ENABLE, value: "1" }
- { name: HCCL_BUFFSIZE, value: "1024" }
- { name: HF_HUB_OFFLINE, value: "1" }
- { name: TRANSFORMERS_OFFLINE, value: "1" }
- { name: PYTHONUNBUFFERED, value: "1" }
volumeMounts:
- { name: model, mountPath: /models/DeepSeek-V4-Flash, readOnly: true }
- { name: driver, mountPath: /usr/local/Ascend/driver, readOnly: true }
- { name: dcmi, mountPath: /usr/local/dcmi, readOnly: true }
- { name: npu-smi, mountPath: /usr/local/bin/npu-smi, readOnly: true }
- { name: hccn, mountPath: /etc/hccn.conf, readOnly: true }
- { name: shm, mountPath: /dev/shm }
# 另需照同样写法挂载:/etc/ascend_install.info(File)、/usr/bin/urma_admin(File)、/dev/ummu、/dev/uburma(Directory)
volumes:
- name: model
hostPath: { path: /data/models/DeepSeek-V4-Flash, type: Directory }
- name: driver
hostPath: { path: /usr/local/Ascend/driver, type: Directory }
- name: dcmi
hostPath: { path: /usr/local/dcmi, type: Directory }
- name: npu-smi
hostPath: { path: /usr/local/bin/npu-smi, type: File }
- name: hccn
hostPath: { path: /etc/hccn.conf, type: File }
- name: shm
emptyDir: { medium: Memory, sizeLimit: 512Gi }
四、启动
先确认没有旧任务占卡占端口:
kubectl get pods -o wide
npu-smi info
ss -lntp | grep ':8000'
docker ps | grep vllm # 如果之前用 Docker 跑过
确认干净了再上:
kubectl apply -f vllm-deepseek-v4.yaml
kubectl get pods -l app=vllm-deepseek-v4 -o wide
kubectl logs -f deployment/vllm-deepseek-v4
模型加载要等几分钟。判断标准是日志里出现引擎初始化完成、API 服务开始监听,不是 Pod 变成 Running。这个 YAML 故意没配探针,1/1 不代表模型已就绪。
五、验证
1. 模型列表
curl -sS --connect-timeout 5 --max-time 30 \
http://172.16.0.13:8000/v1/models
返回的 model id 应该是 dsv4,max_model_len 是 32768。
2. 一次对话
curl -sS --connect-timeout 5 --max-time 300 \
http://172.16.0.13:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "dsv4",
"messages": [{"role": "user", "content": "请用一句话介绍你自己。"}],
"max_tokens": 512,
"temperature": 0.1,
"stream": false
}'
实测返回(摘录关键字段):
{
"model": "dsv4",
"finish_reason": "stop",
"system_fingerprint": "vllm-0.30.0-dp8-ep-b653302e",
"usage": {
"prompt_tokens": 91,
"completion_tokens": 461,
"total_tokens": 552,
"completion_tokens_details": {"reasoning_tokens": 425}
}
}
finish_reason: stop 且 content 是通顺中文,就算通了。
注意 completion_tokens_details:461 个输出 token 里 425 个是思考过程,占 92%。DeepSeek 这类推理模型返回里除了 message.content 还有独立的 message.reasoning,短回答不代表算得少。max_tokens 给小了,可能出现思考没结束就被截断、content 为空。
3. 流式 + 计时
curl -N -sS --max-time 300 \
-w '\n总耗时:%{time_total} 秒\n' \
http://172.16.0.13:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "dsv4",
"messages": [{"role": "user", "content": "你好,请用一句话介绍你自己。"}],
"max_tokens": 512,
"temperature": 0.1,
"stream": true,
"stream_options": {"include_usage": true}
}'
time_total 是整条链路的总时间,含排队、Prefill、Decode 和传输,别当成纯生成速度。要看首 token 时延,得单独记录第一个有效 token 到达的时间。
六、参数说明
| 参数 | 说明 |
|---|---|
nodeName | 钉在唯一同时有镜像和模型的节点上 |
hostNetwork | 直接用宿主机网络,省掉 Service/NodePort |
privileged | 图省事的设备访问方式,不占位,调度器不会帮你预留卡 |
ASCEND_RT_VISIBLE_DEVICES | 容器内可见的卡,运行时设置,不是 K8s 资源声明 |
tensor-parallel-size 1 | 单卡装完整专家,不切张量 |
data-parallel-size 8 | 8 卡各起一份 DP 副本并行处理请求 |
enable-expert-parallel | MoE 专家分散到 8 卡,不是 8 份完整模型副本 |
kv-cache-dtype fp8 | 主 KV Cache 显式 FP8 |
attention_config.indexer_kv_dtype fp8 | Indexer Cache 也显式 FP8,不能留给默认值 |
block-size 32 | 与跑通的 Docker 参数对齐 |
enforce-eager | 先关图模式,缩小启动排查范围 |
gpu-memory-utilization 0.9 | Ascend 后端沿用 vLLM 这个参数名,指设备内存预算比例 |
核心是 TP=1 + DP=8 + EP:DeepSeek 这类 MoE 模型,单卡放不下全部专家时靠专家并行拆分,比纯粹切张量更省通信。这套组合是当初在 Docker 里试通的参数,直接搬进了 K8s。
两个 KV Cache 的 dtype 一定要显式写死。这版 Ascend 实现里,indexer_kv_dtype 不指定时会走通用 dtype 转换,把 auto 解析成模型计算精度,缓存布局对不上,表现是 /v1/models 正常但一请求就报 view size is not compatible 或 block_stride must be greater than min block size。写上 fp8 就绕开了这个歧义。
/dev/shm 给 512Gi 是为了对齐 Docker 的共享内存上限,sizeLimit 不是启动就分配 512G,是按需消耗主机内存。
七、收尾
临时释放卡:
kubectl scale deployment vllm-deepseek-v4 --replicas=0
彻底删掉:
kubectl delete -f vllm-deepseek-v4.yaml
这两个操作都不会删 hostPath 里的模型权重,也不会删 containerd 里的镜像。
结语
到这一步,单节点 DeepSeek 的基础生成链路就算通了:模型加载完成、/v1/models 正确、对话请求返回 finish_reason: stop。
没验证的东西也说明白:首 token 时延、生成吞吐、32K 满上下文、并发、长期稳定性,这些一个都没测。功能验证和性能验收是两件事,别混。
真要往生产走,下一步才是 P/D 分离、KVcache、路由组件那套。
评论区