# Ascend NPU 实战（一）：原生 Docker 与 Ascend Docker Runtime VNPU


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

<!--more-->

## vNPU 原理介绍

### 什么是 Ascend vNPU

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

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

1. 选择虚拟化模式和 vNPU 模板；
2. 创建或指定要使用的 vNPU；
3. 把设备和 Ascend 驱动环境挂载到容器。

![昇腾 NPU 虚拟化架构](https://img.lixueduan.com/kubernetes/vnpu/hami-vnpu-arch.png)

### 和 NVIDIA MIG 的类比

Ascend vNPU 可以类比 NVIDIA 的 MIG（Multi-Instance GPU），两者都是把一块物理加速卡切分成多个相互隔离的硬件实例，再分别分配给容器或任务使用。不过这只是概念上的类比，不代表两者的命令、资源模型和实现方式完全相同：

| 对比维度 | Ascend vNPU | NVIDIA MIG |
|---|---|---|
| 虚拟实例 | AVI / vNPU | MIG Instance |
| 划分方式 | 根据 `vir01`、`vir02` 等模板划分 AICORE、内存、AICPU 和媒体处理资源 | 根据 GPU 支持的 profile 划分计算核心、显存和相关硬件资源 |
| 创建方式 | 使用 `npu-smi` 或 Ascend Docker Runtime | 使用 NVIDIA 工具或 NVIDIA Container Toolkit 相关能力 |
| 容器视角 | 容器内看到一个 `310Pvir01` 等 vNPU 设备 | 容器内看到分配到的 MIG GPU 设备 |
| 共同点 | 都可以把硬件资源切成更小的实例，供多个容器隔离使用 | 都可以把硬件资源切成更小的实例，供多个容器隔离使用 |

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

### 不同场景下的使用方式

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

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

- **Ascend Docker Runtime**：支持静态和动态两种方式。静态方式仍然需要提前创建 vNPU，但设备和驱动挂载由 Runtime 完成；动态方式则在启动容器时通过 `ASCEND_VISIBLE_DEVICES`、`ASCEND_VNPU_SPECS` 等参数指定设备和模板，不需要提前创建 vNPU。

- **集群调度组件**：可以使用 Ascend Device Plugin 配合 Kubernetes Scheduler、Volcano 或其他调度组件。Device Plugin 负责设备发现、资源上报和分配，调度器负责选择节点和设备；动态方式下，组件还可以根据任务请求自动配置 vNPU。

简单说，原生 Docker 是“用户自己选、自己挂”，Ascend Docker Runtime 是“Runtime 帮你挂”，集群调度则是在此基础上再由调度器统一管理资源。三种方式的角色关系先在这里建立起来，具体命令见下面的实测过程。

## 测试环境

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

- 操作系统：CTYunOS 2.0.1
- 主机名：`lixd-npu-test1`
- NPU：Ascend 310P3 / Atlas 300I Pro
- Ascend Driver： 24.1.0.1
- 架构： ARM64

### 查询 NPU 和 vNPU 模板

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

```bash
[root@lixd-npu-test1 ~]# npu-smi info -l
	Total Count                    : 1

	NPU ID                         : 1536
	Product Name                   : IT21PDDA011
	Serial Number                  : 2106030728ZEP500xxxx
	Chip Count                     : 1

[root@lixd-npu-test1 ~]# npu-smi info -m
	NPU ID                         Chip ID                        Chip Logic ID                  Chip Name
	1536                           0                              0                              Ascend 310P3
	1536                           1                              -                              Mcu
```


本机的关键结果是：

```text
NPU ID: 1536
Chip ID: 0
Chip Name: Ascend 310P3
Product Name: Atlas 300I Pro
```

再确认产品类型：

```bash
npu-smi info -t product -i 1536
```

```text
NPU ID                         : 1536
Chip Count                     : 1

Product Type                   : Atlas 300I Pro
Chip ID                        : 0
```

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

```bash
npu-smi info -t template-info -i 1536
```

```text
+------------------------------------------------------------------------------------------+
|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：

```bash
dnf install -y docker
systemctl enable --now docker
systemctl is-active docker
```

确认 Docker 服务和服务端架构：

```bash
docker version
docker info
```

### 创建 vNPU

本机使用 NPU ID `1536`、Chip ID `0` 和 `vir01` 模板：

```bash
npu-smi set -t create-vnpu -i 1536 -c 0 -f vir01
npu-smi info -t info-vnpu -i 1536 -c 0
```

输出如下：

```text
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 ID**：宿主机 `npu-smi` 使用的设备编号，例如本文中的 `1536`，用于 `-i` 参数；
- **vNPU ID**：创建 vNPU 后返回的实例编号，例如本文中的 `100`；
- **容器内设备编号**：宿主机的 `/dev/vdavinci100` 映射到容器内的 `/dev/davinci100` 后，容器内 `npu-smi` 使用的是容器视角下的可见设备编号。

这三个编号不一定相同，不能把宿主机物理 NPU ID、vNPU ID 和容器内显示的设备编号混用。

### 启动容器挂载 vNPU

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

```bash
ls -l /dev/vdavinci100 \
  /dev/davinci_manager \
  /dev/devmm_svm \
  /dev/hisi_hdc
```

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

```text
crw-rw---- 1 HwHiAiUser HwHiAiUser 238,   0 Sep  4 15:58 /dev/davinci_manager
crw-rw---- 1 HwHiAiUser HwHiAiUser 235,   0 Sep  4 15:58 /dev/devmm_svm
crw-rw---- 1 HwHiAiUser HwHiAiUser 511,   0 Sep  4 15:58 /dev/hisi_hdc
crw------- 1 HwHiAiUser HwHiAiUser 236, 100 Sep  4 16:24 /dev/vdavinci100
```

然后我们启动容易，使用 --device 参数把 vNPU 挂载到容器：

```bash
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 \
  python:3.7-slim-buster \
  npu-smi info
```

容器内输出如下：

```text
+--------------------------------------------------------------------------------------------------------+
| npu-smi 24.1.0.1                                 Version: 24.1.0.1                                     |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 13      310Pvir01             | OK              | NA           51                0     / 0             |
| 0       0                     | 0000:06:00.0    | 0            229  / 2690                             |
+-------------------------------+-----------------+------------------------------------------------------+
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Chip                  | Process id      | Process name             | Process memory(MB)        |
+-------------------------------+-----------------+------------------------------------------------------+
| No running processes found in NPU 13                                                                   |
+--------------------------------------------------------------------------------------------------------+
```

输出中的 `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` 驱动/工具库以只读方式提供给精简镜像，并让动态链接器能找到这些库。
  - 这里的挂载是为了让精简镜像能够执行 `npu-smi`。

## Ascend Docker Runtime Demo

### 安装 Ascend Docker Runtime

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

本次使用 MindCluster 6.0.0 提供的 ARM64 安装包。官方安装包地址见 [MindCluster v6.0.0 Release](https://gitee.com/ascend/mind-cluster/releases/tag/v6.0.0)。

```bash
mkdir -p /tmp/ascend-docker-runtime
cd /tmp/ascend-docker-runtime

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

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

命令输出如下：

```text
1faddb734d347a2ac85065c02e162f1e0d1e051954d805543df9ce5ca0a3ee92  Ascend-docker-runtime_6.0.0_linux-aarch64.run
Uncompressing ascend-docker-runtime     0%
Uncompressing ascend-docker-runtime     62%
Uncompressing ascend-docker-runtime     100%
[INFO] installing ascend docker runtime
[INFO] platform(aarch64) 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：

```bash
systemctl daemon-reload
systemctl restart docker
docker info --format 'server={{.ServerVersion}} arch={{.Architecture}} default-runtime={{.DefaultRuntime}}'
```

```text
server=25.0.3 arch=aarch64 default-runtime=ascend
```

### 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：

```bash
docker run --rm --runtime=ascend \
  -e ASCEND_VISIBLE_DEVICES=100 \
  -e ASCEND_RUNTIME_OPTIONS=VIRTUAL \
  -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 \
  python:3.7-slim-buster \
  npu-smi info
```

容器内输出如下：

```text
+--------------------------------------------------------------------------------------------------------+
| npu-smi 24.1.0.1                                 Version: 24.1.0.1                                     |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 13      310Pvir01             | OK              | NA           51                0     / 0             |
| 0       0                     | 0000:06:00.0    | 0            229  / 2690                             |
+-------------------------------+-----------------+------------------------------------------------------+
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Chip                  | Process id      | Process name             | Process memory(MB)        |
+-------------------------------+-----------------+------------------------------------------------------+
| No running processes found in NPU 13                                                                   |
+--------------------------------------------------------------------------------------------------------+
```

这里由 Runtime 负责把静态 vNPU 注入容器，容器内 `npu-smi` 显示 `310Pvir01`，命令中不再出现原生 Docker 的 `--device=/dev/vdavinci100:/dev/davinci100`。

### Ascend Docker Runtime：动态 vNPU

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

#### 关闭 vNPU 配置恢复

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

```bash
npu-smi set -t vnpu-cfg-recover -d 0
```

输出如下：

```text
Status                         : OK
Message                        : The VNPU config recover mode Disable is set successfully.
```

#### 启动动态 vNPU 容器

通过 `ASCEND_VISIBLE_DEVICES` 选择物理设备，通过 `ASCEND_VNPU_SPECS` 指定 vNPU 模板：

```bash
docker run --rm --runtime=ascend \
  -e ASCEND_VISIBLE_DEVICES=0 \
  -e ASCEND_VNPU_SPECS=vir01 \
  -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 \
  python:3.7-slim-buster \
  npu-smi info
```

容器内输出如下：

```text
+--------------------------------------------------------------------------------------------------------+
| npu-smi 24.1.0.1                                 Version: 24.1.0.1                                     |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 13      310Pvir01             | OK              | NA           51                0     / 0             |
| 0       0                     | 0000:06:00.0    | 0            229  / 2690                             |
+-------------------------------+-----------------+------------------------------------------------------+
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Chip                  | Process id      | Process name             | Process memory(MB)        |
+-------------------------------+-----------------+------------------------------------------------------+
| No running processes found in NPU 13                                                                   |
+--------------------------------------------------------------------------------------------------------+
```

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

容器退出后再次查询：

```bash
npu-smi info -t info-vnpu -i 1536 -c 0
```

输出如下：

```text
+-------------------------------------------------------------------------------+
| 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`，说明动态实例随容器生命周期自动释放。

## 小结：两种 Docker 运行方式

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

| 方式 | 设备和模板 | 容器内注入 |
|---|---|---|
| 原生 Docker 静态 vNPU | 用户执行 `npu-smi` 创建 | 用户手工 `--device` 和挂载驱动，容器内执行 `npu-smi` |
| Ascend Docker Runtime 静态 vNPU | 用户执行 `npu-smi` 创建，Runtime 接收 vNPU ID | Runtime 根据 `ASCEND_VISIBLE_DEVICES` 和 `ASCEND_RUNTIME_OPTIONS=VIRTUAL` 注入，容器内执行 `npu-smi` |
| Ascend Docker Runtime 动态 vNPU | 启动容器时设置 `ASCEND_VISIBLE_DEVICES`、`ASCEND_VNPU_SPECS` | Runtime 自动创建实例并注入，容器内执行 `npu-smi` |

这两种 Docker 方式使用的是同一套底层 vNPU 能力，区别主要在于 vNPU 创建和设备注入是否由 Runtime 自动完成。本文只负责把原生 Docker 和 Ascend Docker Runtime 的基础能力跑通。


---

> 作者: [意琦行](https://github.com/lixd)  
> URL: https://www.lixueduan.com/posts/kubernetes/66-ascend-npu-docker-vnpu/  

