Ascend VNPU 实战:从原生 Docker 到 Ascend Docker Runtime

Ascend vNPU 实战:从原生 Docker 到 Ascend Docker Runtime

本文以 Ascend 310P3(Atlas 300I Pro)为例,介绍昇腾 NPU vNPU 的基本使用方法。 内容包括从原生 Docker 开始,演示手动创建 vNPU 并挂载到容器,以及 Ascend Docker Runtime 下的静态挂载和动态虚拟化。

后续我们会继续把 vNPU 接入 Kubernetes,并进一步看 HAMi 如何对 vNPU 做统一的资源管理和调度。本文先绕开 Kubernetes 和 HAMi,从 Ascend 自身提供的 vNPU 能力开始,跑通 vNPU。

vNPU 介绍

什么是 Ascend vNPU

昇腾的虚拟化实例叫 AVI(Ascend Virtual Instance)。它可以按照固定的虚拟化模板,把一个物理 NPU 划分成多个 vNPU,再把 vNPU 提供给容器使用。

从使用者的角度,原生 Docker 下主要需要完成三件事:

  1. 选择虚拟化模式和 vNPU 模板;
  2. 创建或指定要使用的 vNPU;
  3. 把设备(vNPU)挂载到容器使用。

昇腾 NPU 虚拟化架构

和 NVIDIA MIG 的类比

Ascend vNPU 在使用方式上可以类比 NVIDIA MIG(Multi-Instance GPU):两者都可以把一块物理加速卡的资源划分成更小的实例,再分别提供给容器或任务使用。

不过这只是使用方式上的类比。Ascend vNPU 与 NVIDIA MIG 在资源模型、虚拟化机制和具体实现上并不相同,因此不能简单把两者视为同一种底层技术。

对比维度Ascend vNPUNVIDIA MIG
虚拟实例AVI / vNPUMIG Instance
划分方式根据 vir01、vir02 等模板划分 AICORE、内存、AICPU 和媒体处理资源根据 GPU 支持的 profile 划分计算核心、显存和相关硬件资源
创建/注入方式使用 npu-smi 创建静态 vNPU;Ascend Docker Runtime 可按 ASCEND_VNPU_SPECS 动态创建并注入,或挂载已有 vNPU使用 nvidia-smi mig / NVML 创建 MIG 实例;NVIDIA Container Toolkit 负责将已有 MIG 设备注入容器
容器视角容器内看到一个 310Pvir01 等 vNPU 设备容器内看到分配到的 MIG GPU
共同点都可以把物理加速器资源以更细粒度的实例提供给不同容器或任务使用都可以把物理加速器资源以更细粒度的实例提供给不同容器或任务使用

本文后面的命令只针对 Ascend vNPU,NVIDIA MIG 仅作为帮助理解的参照。

不同场景下的使用方式

vNPU 的底层能力相同,但不同运行环境负责的事情不一样:

  • 原生 Docker:通过 npu-smi 手动创建 vNPU,再手动把 vNPU 设备和 Ascend 驱动环境挂载到容器中,下面会完整展开。

  • Ascend Docker Runtime:支持静态和动态两种方式。静态方式仍然需要提前创建 vNPU,但 vNPU 设备注入由 Runtime 完成;动态方式则在启动容器时通过 ASCEND_VISIBLE_DEVICES、ASCEND_VNPU_SPECS 等参数指定物理设备和 vNPU 模板,由 Runtime 动态创建并注入 vNPU,不需要提前创建虚拟实例。

  • 集群调度组件:可以使用 Ascend Device Plugin 配合 Kubernetes Scheduler、Volcano 或其他调度组件。Device Plugin 负责设备发现、资源上报和节点侧设备分配,Scheduler 根据资源请求选择合适的节点;HAMi、Volcano 等增强方案还可以进一步参与资源调度和管理。

简单说,原生 Docker 是“用户自己创建、自己挂载设备”,Ascend Docker Runtime 是“Runtime 帮你注入设备”,集群调度则是在此基础上进一步统一管理资源。

Ascend vNPU 三种 Docker 使用方式

测试环境

本次使用环境关键信息如下:

项目实测值
操作系统Huawei Cloud EulerOS 2.0(x86_64)
主机名lixd-test-npu
NPUAscend 310P3 / Atlas 300I Pro,1 张
架构x86_64
Ascend Driver22.0.4
CANN6.0.0
Dockerdocker-engine 18.09.0
Ascend Docker Runtime6.0.0

查询 NPU 和 vNPU 模板

先确认 NPU 设备和芯片信息:

1
2
npu-smi info -l
npu-smi info -m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	Total Count                    : 1

	NPU ID                         : 13
	Product Name                   : IT21PDDA01
	Serial Number                  : 2106030635ZENC0xxx94
	Chip Count                     : 1

	NPU ID                         Chip ID                        Chip Logic ID                  Chip Name
	13                             0                              0                              Ascend 310P3
	13                             1                              -                              Mcu

本机的关键结果是:

1
2
3
4
NPU ID: 13
Chip ID: 0
Chip Name: Ascend 310P3
Product Name: Atlas 300I Pro

再确认产品类型:

1
npu-smi info -t product -i 13
1
	Product Type                   : Atlas 300I Pro

再查询当前产品支持的 vNPU 模板:

1
npu-smi info -t template-info -i 13
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
+------------------------------------------------------------------------------------------+
|NPU instance template info is:                                                            |
|Name                AICORE    Memory    AICPU     VPC            VENC           JPEGD     |
|                               GB                 PNGD           VDEC           JPEGE     |
|==========================================================================================|
|vir01               1         3         1         1              0              2         |
|                                                  0              1              1         |
+------------------------------------------------------------------------------------------+
|vir02               2         6         2         3              1              4         |
|                                                  0              3              2         |
+------------------------------------------------------------------------------------------+
|vir02_1c            2         6         1         3              0              4         |
|                                                  0              3              2         |
+------------------------------------------------------------------------------------------+
|vir04               4         12        4         6              2              8         |
|                                                  0              6              4         |
+------------------------------------------------------------------------------------------+
|vir04_3c            4         12        3         6              1              8         |
|                                                  0              6              4         |
+------------------------------------------------------------------------------------------+
|vir04_3c_ndvpp      4         12        3         0              0              0         |
|                                                  0              0              0         |
+------------------------------------------------------------------------------------------+
|vir04_4c_dvpp       4         12        4         12             3              16        |
|                                                  0              12             8         |
+------------------------------------------------------------------------------------------+

本机可用的模板包括 vir01、vir02、vir02_1c、vir04、vir04_3c、vir04_3c_ndvpp 和 vir04_4c_dvpp。本文选择 vir01 做最小验证。

原生 Docker 下的 vNPU Demo

原生 Docker 下使用 vNPU 分成两步:先用 npu-smi 创建 vNPU,再把生成的 vNPU 设备映射到容器。

安装 Docker

使用 dnf 安装 Docker。Huawei Cloud EulerOS 仓库里的包名是 docker-engine:

1
2
dnf install -y docker-engine
systemctl enable --now docker

确认 Docker 服务和服务端信息:

1
2
docker version
docker info --format 'server={{.ServerVersion}} arch={{.Architecture}} default-runtime={{.DefaultRuntime}}'
1
server=18.09.0 arch=x86_64 default-runtime=runc

创建 vNPU

本机使用 NPU ID 13、Chip ID 0 和 vir01 模板:

1
2
npu-smi set -t create-vnpu -i 13 -c 0 -f vir01
npu-smi info -t info-vnpu -i 13 -c 0

输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
	Status                         : OK
	Message                        : Create vnpu success

+-------------------------------------------------------------------------------+
| NPU resource static info as follow:                                           |
| Format:Free/Total                   NA: Currently, query is not supported.    |
| AICORE    Memory    AICPU    VPC    VENC    VDEC    JPEGD    JPEGE    PNGD    |
|            GB                                                                 |
|===============================================================================|
| 7/8       18/21     6/7      11/12  3/3     11/12   14/16    7/8      NA/NA   |
+-------------------------------------------------------------------------------+
| Total number of vnpu: 1                                                       |
+-------------------------------------------------------------------------------+
|  Vnpu ID  |  Vgroup ID     |  Container ID  |  Status  |  Template Name       |
+-------------------------------------------------------------------------------+
|  100      |  0             |  000000000000  |  0       |  vir01               |
+-------------------------------------------------------------------------------+

创建成功后,本机得到 vNPU ID 100,对应设备节点为 /dev/vdavinci100。

这里有几个容易混淆的设备编号:

  • npu-smi NPU ID:宿主机通过 npu-smi 查询和管理设备时使用的编号,例如本文中的 13,创建 vNPU 时通过 -i 13 指定;
  • Runtime 物理设备 ID:Ascend Docker Runtime 使用 /dev/davinci* 对应的设备序号选择物理设备。本文对应 /dev/davinci0,因此动态虚拟化时使用 ASCEND_VISIBLE_DEVICES=0;
  • vNPU ID:创建 vNPU 后返回的虚拟实例编号,例如本文中的 100,对应 /dev/vdavinci100。使用 Runtime 挂载已有 vNPU 时,通过 ASCEND_VISIBLE_DEVICES=100 指定;
  • 容器内显示编号:设备注入容器后,npu-smi 还会显示容器视角下的设备编号,例如本文实测中的 NPU 13。

这些编号来自不同的设备管理视角,不应该直接混用。

启动容器挂载 vNPU

启动容器前,先确认 vNPU 设备节点和基础设备节点已经存在:

1
2
3
4
ls -l /dev/vdavinci100 \
  /dev/davinci_manager \
  /dev/devmm_svm \
  /dev/hisi_hdc

本次实测可以看到以下设备节点:

1
2
3
4
crw-rw---- 1 HwHiAiUser HwHiAiUser 239,   0 Sep 17 17:42 /dev/davinci_manager
crw-rw---- 1 HwHiAiUser HwHiAiUser 234,   0 Sep 17 17:42 /dev/devmm_svm
crw-rw---- 1 HwHiAiUser HwHiAiUser 511,   0 Sep 17 17:42 /dev/hisi_hdc
crw------- 1 HwHiAiUser HwHiAiUser 236, 100 Sep 17 20:49 /dev/vdavinci100

然后我们启动容器,使用 --device 参数把 vNPU 挂载到容器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
IMG=ascendai/cann:7.0.1-310p-openeuler20.03-py3.8

docker run --rm \
  --device=/dev/vdavinci100:/dev/davinci100 \
  --device=/dev/davinci_manager \
  --device=/dev/devmm_svm \
  --device=/dev/hisi_hdc \
  -v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi:ro \
  -v /usr/local/Ascend:/usr/local/Ascend:ro \
  -e LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:/usr/local/Ascend/driver/lib64/driver:/usr/local/Ascend/driver/lib64 \
  $IMG \
  npu-smi info

容器内输出如下:

1
2
3
4
5
6
7
8
9
+--------------------------------------------------------------------------------------------------------+
| npu-smi 22.0.4                                   Version: 22.0.4                                       |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 13      310Pvir01             | OK              | NA           56                0    / 0              |
| 0       0                     | 0000:00:0D.0    | 0            312  / 2690                             |
+===============================+=================+======================================================+

输出中的 310Pvir01 表明容器内已经看到 vir01 类型的 vNPU。

这里的参数可以简单分为两类。它们不是用来创建 vNPU 的,vNPU 已经在前一步创建完成:

  • 设备映射(--device):把 /dev/vdavinci100 以及 /dev/davinci_manager、/dev/devmm_svm、/dev/hisi_hdc 等 Ascend 设备节点交给容器, 这样容器才能访问已经创建的 vNPU。
  • 工具和运行库(-v、LD_LIBRARY_PATH):把宿主机的 npu-smi 和 /usr/local/Ascend 驱动/工具库以只读方式提供给容器,并让动态链接器能找到这些库。

Ascend Docker Runtime Demo

安装 Ascend Docker Runtime

原生 Docker 路径验证完成后,再安装 Ascend Docker Runtime,继续验证 Runtime 的静态挂载和动态虚拟化。

本次使用 MindCluster 6.0.0 提供的 x86_64 安装包。官方安装包地址见 MindCluster v6.0.0 Release。

1
2
3
4
5
6
7
8
9
mkdir -p /tmp/ascend-docker-runtime
cd /tmp/ascend-docker-runtime

wget -O Ascend-docker-runtime_6.0.0_linux-x86_64.run \
  https://gitee.com/ascend/mind-cluster/releases/download/v6.0.0/Ascend-docker-runtime_6.0.0_linux-x86_64.run

sha256sum Ascend-docker-runtime_6.0.0_linux-x86_64.run
chmod u+x Ascend-docker-runtime_6.0.0_linux-x86_64.run
./Ascend-docker-runtime_6.0.0_linux-x86_64.run --install

命令输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
2e3800050d4203daa87937748a436ba0c32fabbc8693ac597ee40ba5b6f7c6af  Ascend-docker-runtime_6.0.0_linux-x86_64.run
Uncompressing ascend-docker-runtime     0%    57%  100%
[INFO] installing ascend docker runtime
[INFO] platform(x86_64) matched!
[INFO] install executable files success
[INFO] install scene is 'docker'.
[INFO] /etc/docker/daemon.json modify success
[INFO] Ascend Docker Runtime has been installed in: /usr/local/Ascend/Ascend-Docker-Runtime
[INFO] The version of Ascend Docker Runtime is: 6.0.0
[INFO] please reboot daemon and container engine to take effect
[INFO] Ascend Docker Runtime install success

安装完成后重载并重启 Docker:

1
2
3
systemctl daemon-reload
systemctl restart docker
docker info --format 'server={{.ServerVersion}} arch={{.Architecture}} default-runtime={{.DefaultRuntime}}'
1
server=18.09.0 arch=x86_64 default-runtime=ascend

Runtime 安装时会把 ascend 注册到 /etc/docker/daemon.json 并设为默认 runtime:

1
2
3
4
5
6
7
8
9
{
        "default-runtime": "ascend",
        "runtimes": {
                "ascend": {
                        "path": "/usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime",
                        "runtimeArgs": []
                }
        }
}

Ascend Docker Runtime:静态 vNPU

安装 Runtime 后,可以复用前面通过 npu-smi 创建的静态 vNPU。与原生 Docker 的区别是:不再手工写 --device,而是把 vNPU ID 交给 Runtime,由 Runtime 完成设备注入。

本机前面创建的 vNPU ID 是 100。设置 ASCEND_RUNTIME_OPTIONS=VIRTUAL,告诉 Runtime ASCEND_VISIBLE_DEVICES 指向的是已经存在的 vNPU:

1
2
3
4
5
6
7
IMG=ascendai/cann:7.0.1-310p-openeuler20.03-py3.8

docker run --rm --runtime=ascend \
  -e ASCEND_VISIBLE_DEVICES=100 \
  -e ASCEND_RUNTIME_OPTIONS=VIRTUAL \
  $IMG \
  npu-smi info

容器内输出如下:

1
2
3
4
5
6
7
8
9
+--------------------------------------------------------------------------------------------------------+
| npu-smi 22.0.4                                   Version: 22.0.4                                       |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 13      310Pvir01             | OK              | NA           56                0    / 0              |
| 0       0                     | 0000:00:0D.0    | 0            312  / 2690                             |
+===============================+=================+======================================================+

相比原生 Docker,不再需要手工声明 –device、挂载宿主机驱动目录或配置 LD_LIBRARY_PATH。在使用 Ascend Runtime 后,只需要通过 ASCEND_VISIBLE_DEVICES 和 ASCEND_RUNTIME_OPTIONS 描述需要注入的 vNPU。Runtime 在 prestart hook 阶段自动完成三件事:

  • 把 ASCEND_VISIBLE_DEVICES 对应的 NPU 设备挂载到容器;
  • 在宿主机上配置该容器的 device cgroup,保证设备隔离;
  • 将宿主机相关的 Ascend 驱动运行库和工具按 Runtime 配置注入容器。

也就是说,Runtime 已经解决了原生 Docker 里最麻烦的那部分。

Ascend Docker Runtime:动态 vNPU

Ascend Docker Runtime 可以在容器启动时根据环境变量自动创建 vNPU,并在容器退出后释放动态实例。动态验证和上面的静态验证是两条独立路径,动态启动时设备从没有预创建 vNPU 的状态开始。

因此,先确认前面的静态 vNPU 容器已经退出,再销毁 vNPU 100,并确认物理 NPU 上已经没有预创建实例:

1
2
npu-smi set -t destroy-vnpu -i 13 -c 0 -v 100
npu-smi info -t info-vnpu -i 13 -c 0

关闭 vNPU 配置恢复

动态 Runtime 需要关闭 vNPU 配置恢复功能:

1
npu-smi set -t vnpu-cfg-recover -d 0

输出如下:

1
2
	Status                         : OK
	Message                        : The VNPU config recover mode Disable is set successfully.

启动动态 vNPU 容器

通过 ASCEND_VISIBLE_DEVICES 选择物理设备,通过 ASCEND_VNPU_SPECS 指定 vNPU 模板。

这里的 ASCEND_VISIBLE_DEVICES=0 对应宿主机 /dev/davinci0,与前面 npu-smi -i 13 使用的 NPU ID 属于不同的设备编号视角。

1
2
3
4
5
docker run --rm --runtime=ascend \
  -e ASCEND_VISIBLE_DEVICES=0 \
  -e ASCEND_VNPU_SPECS=vir01 \
  $IMG \
  npu-smi info

容器内输出如下:

1
2
3
4
5
6
7
8
9
+--------------------------------------------------------------------------------------------------------+
| npu-smi 22.0.4                                   Version: 22.0.4                                       |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 13      310Pvir01             | OK              | NA           56                0    / 0              |
| 0       0                     | 0000:00:0D.0    | 0            312  / 2690                             |
+===============================+=================+======================================================+

输出中的 310Pvir01 表明 Runtime 已经在容器内注入动态创建的 vir01 vNPU。

容器退出后再次查询:

1
npu-smi info -t info-vnpu -i 13 -c 0

输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
+-------------------------------------------------------------------------------+
| NPU resource static info as follow:                                           |
| Format:Free/Total                   NA: Currently, query is not supported.    |
| AICORE    Memory    AICPU    VPC    VENC    VDEC    JPEGD    JPEGE    PNGD    |
|            GB                                                                 |
+===============================================================================+
| 8/8       21/21     7/7      12/12  3/3     12/12   16/16    8/8      NA/NA   |
+-------------------------------------------------------------------------------+
| Total number of vnpu: 0                                                       |
+-------------------------------------------------------------------------------+
|  Vnpu ID  |  Vgroup ID     |  Container ID  |  Status  |  Template Name       |
+-------------------------------------------------------------------------------+

结果显示当前 vNPU 数量为 0,说明动态实例随容器生命周期自动释放,AICORE 和内存也都恢复到 8/8、21/21。

小结

Docker 方式的职责可以概括为:

方式设备和模板容器内注入
原生 Docker 静态 vNPU用户执行 npu-smi 创建用户手工 --device,并挂载 npu-smi 和驱动库
Ascend Docker Runtime 静态 vNPU用户执行 npu-smi 创建,Runtime 接收 vNPU IDRuntime 根据 ASCEND_VISIBLE_DEVICES 和 ASCEND_RUNTIME_OPTIONS=VIRTUAL 自动注入
Ascend Docker Runtime 动态 vNPU启动容器时设置 ASCEND_VISIBLE_DEVICES、ASCEND_VNPU_SPECSRuntime 自动创建实例并注入,容器退出后释放

两者的核心差别在于谁负责把设备塞进容器:原生 Docker 靠用户手写 --device 和挂载,Ascend Docker Runtime 把这些交给 prestart hook 自动完成。这两种方式使用的是同一套底层 vNPU 能力。

到这里,我们先把 Ascend 自身提供的 vNPU 能力和两种 Docker 使用方式跑通了。下一步再把这套能力放进 Kubernetes,看看 Ascend Device Plugin 和 HAMi 分别解决什么问题。

0%