Ascend vNPU 切片如何工作

与 NVIDIA vGPU 不同——后者可以请求任意内存大小——Huawei NPU 虚拟化是基于模板的。每种芯片型号都有一组固定的切片大小(称为 templates),并且固件只接受这些大小;插件和调度器始终会将内存请求向上取整到最接近的模板。

有关设置 Ascend device plugin 以及下面引用的 hami-scheduler-device ConfigMap 的安装步骤,请参见 Install for Huawei Ascend NPU

每种芯片型号对应的三个数值

hami-scheduler-device ConfigMap 中的 vnpus 部分为每种芯片型号定义了插件和调度器所需的三个值:

Field含义
memoryAllocatable一张物理卡用于切片的内存(单位:MiB)。
memoryCapacity硬件上限。请求如果大于 memoryAllocatable≤ memoryCapacity,则会占用整张卡。
templates[]允许的切片大小(memoryaiCore,以及可选的 aiCPU)。插件在启动时会按 memory 升序对其排序。

例如,Ascend910B4 的默认条目如下:

- chipName: 910B4
  commonWord: Ascend910B4
  resourceName: huawei.com/Ascend910B4
  resourceMemoryName: huawei.com/Ascend910B4-memory
  memoryAllocatable: 32768    # 32 GiB per card
  memoryCapacity: 32768
  aiCore: 20
  aiCPU: 7
  templates:
    - name: vir05_1c_8g        # 8 GiB slice, 5 aiCore, 1 aiCPU
      memory: 8192
      aiCore: 5
      aiCPU: 1
    - name: vir10_3c_16g       # 16 GiB slice, 10 aiCore, 3 aiCPU
      memory: 16384
      aiCore: 10
      aiCPU: 3

节点上显示的内容

对于每张物理卡,插件都会向 kubelet 广告 memoryAllocatable / smallestTemplateMemory 个“槽位”。每个槽位代表该卡上的一个潜在切片;切片的实际大小会在调度时根据请求的内存再决定。

示例:一台使用上面默认配置的节点,包含 8 × Ascend 910B4(每张 32 GiB)

  • 最小模板是 vir05_1c_8g(8 GiB),因此每张卡暴露 32768 / 8192 = 4 个槽位。

  • 节点的 status.allocatable 只包含槽位数量:

    status.allocatable:
      huawei.com/Ascend910B4: 32   # 8 cards × 4 slots

对应的 huawei.com/Ascend910B4-memory 不是 kubelet 扩展资源,因此不会出现在节点上。插件会改为在 hami.io/node-register-Ascend910B4 注解中发布每张卡的 UUID、总内存和 aiCore;hami-scheduler 读取该注解,以维护按卡划分的内存预算。一个 Pod 会同时消耗 status.allocatable 中的一个槽位以及调度器跟踪的内存预算中的一部分。

你可以使用以下命令查看调度器侧视图:

kubectl get node {ascend-node} -o jsonpath='{.metadata.annotations.hami\.io/node-register-Ascend910B4}'

内存请求如何取整 — trimMemory

当 Pod 被准入时,HAMi webhook 会从最小模板到最大模板依次遍历,并选择第一个 memory 请求内存的模板。该模板定义了切片实际使用的内存和 aiCore 数量。

templates (sorted): 8192, 16384

requested 2 GiB   -> 8 GiB slice (vir05_1c_8g),  5 aiCore
requested 6 GiB   -> 8 GiB slice (vir05_1c_8g),  5 aiCore
requested 10 GiB  -> 16 GiB slice (vir10_3c_16g), 10 aiCore
requested 20 GiB  -> whole card (32 GiB), all aiCore   # no template fits, but ≤ memoryCapacity
requested 40 GiB  -> rejected                           # > memoryCapacity

随后,webhook 会将 Pod 的 huawei.com/Ascend910B4-memory 请求重写为取整后的值,因此你在准入后的 Pod spec 中看到的是切片大小,而不是你最初请求的数值。

详细过程:8 × 910B4,同一节点上的两个 Pod

初始状态——每张卡都是空闲的:剩余 32 GiB,剩余 20 aiCore。

步骤Pod 请求选择的模板发生了什么
1Ascend910B4: 1, Ascend910B4-memory: 2048(2 GiB)vir05_1c_8g — 8 GiB / 5 aiCore调度器以 binpack 方式选中 #0 卡;Pod 的请求被重写为 8192。此时 #0 卡剩余:24 GiB,15 aiCore。节点计数降为 31
2Ascend910B4: 1, Ascend910B4-memory: 10240(10 GiB)vir10_3c_16g — 16 GiB / 10 aiCore#0 卡仍有空间(24 GiB 剩余,15 aiCore 剩余)→ 调度器复用 #0 卡。此时 #0 卡剩余:8 GiB,5 aiCore。节点计数降为 30

第三个请求 1 GiB 的 Pod(会取整为 8 GiB / 5 aiCore)仍然可以刚好放入 #0 卡并将其填满;第四个则会迫使调度器切换到 #1 卡。若使用 gpuSchedulerPolicy=spread,则每个新的切片都会从一张新的卡开始。

当与切片绑定的容器启动后,Ascend Docker Runtime 会读取插件注入的环境变量:

ASCEND_VISIBLE_DEVICES=0          # physical card index
ASCEND_VNPU_SPECS=vir05_1c_8g     # template name

然后请求固件创建实际的 vNPU。当 Pod 终止且 vNPU 处于空闲状态时,插件的周期性清理(CleanupIdleVNPUs)会在下一次 tick 时将其销毁,从而使该槽位可以再次被复用。

需要注意的事项

  • 没有小数模板。 即使在最小模板为 8 GiB 的芯片上请求 2 GiB,仍然会占用 8 GiB 和 5 aiCore。请选择与你的典型工作负载相匹配的芯片 / 模板集合。
  • 切片仅限单卡。 huawei.com/Ascend910B4 > 1 会被解释为“我要 N 张整卡”——如果请求同时包含大于 1 的数量和小于 memoryAllocatable 的内存请求,webhook 会拒绝。
  • ConfigMap 是唯一事实来源。 如果你的卡使用的内存大小不是默认值,或者你需要不同的模板形状,请编辑 hami-scheduler-device 中该芯片对应的 vnpus 条目,并重启 hami-ascend-device-plugin Pods。下一次 chart 升级时,这些修改会丢失。