目 录CONTENT

文章目录

【AI】鲲鹏950+昇腾950DT:vLLM-DeepSeek部署实战

天行1st
2026-11-10 / 0 评论 / 0 点赞 / 2 阅读 / 0 字

Ascend 950 单机 8 卡,用 vLLM 跑通 DeepSeek-V4-Flash

目标只有一个:在 RKE2 集群的一个节点上,用 vLLM-Ascend 镜像起一个 Pod,把 DeepSeek 跑起来,OpenAI 兼容接口能正常对话,然后收工。

不做跨节点推理,不做 P/D 分离,不接 KVcache,不做性能调优。

一、环境

项目配置
KubernetesRKE2,容器运行时 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 88 卡各起一份 DP 副本并行处理请求
enable-expert-parallelMoE 专家分散到 8 卡,不是 8 份完整模型副本
kv-cache-dtype fp8主 KV Cache 显式 FP8
attention_config.indexer_kv_dtype fp8Indexer Cache 也显式 FP8,不能留给默认值
block-size 32与跑通的 Docker 参数对齐
enforce-eager先关图模式,缩小启动排查范围
gpu-memory-utilization 0.9Ascend 后端沿用 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、路由组件那套。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区