Tim Wang Tech Blog

如何解决 `Failed to initialize NVML: Unknown Error` 问题

本文是NOTICE: Containers losing access to GPUs with error: “Failed to initialize NVML: Unknown Error”的中文翻译版本,内容有删减,亲测该方法有效。

如何解决 Failed to initialize NVML: Unknown Error 问题

概述

在某些特定的情况下,我们发现k8s容器可能会突然从最初连接到的GPU上分离。我们已经确定了这个问题的根本原因,并确定了可能发生这种情况的受影响环境。在本文档的末尾提供了受影响环境的解决方法,直到发布适当的修复为止。

问题总结

我们发现当使用container来管理GPU工作负载时,用户container可能会突然失去对GPU的访问权限。这种情况发生在使用systemd来管理容器的cgroups时,当触发重新加载任何包含对NVIDIA GPU的引用的Unit文件时(例如,通过执行systemctl daemon-reload)。

当你的container失去对GPU的访问权限时,你可能会看到类似于以下错误消息:

Failed to initialize NVML: Unknown Error

一旦发生上述 ⬆️,就需要手动删除受影响的container,然后重新启动它们。

当container重新启动(手动或自动,取决于是否使用容器编排平台),它将重新获得对GPU的访问权限。

此问题的根源在于,最近版本的runc要求在/dev/char下面为注入到容器中的任何设备节点提供符号链接。不幸的是,NVIDIA设备并没有这些符号链接,NVIDIA GPU驱动也没有(当前)提供自动创建这些链接的方法

受影响的环境

如果你使用runc并在高级容器运行时(CRI)启用systemd cgroup管理的环境,那么你可能会受到这个问题的影响

如果你没有使用systemd来管理cgroup,那么它就不会受到这个问题的影响。 下面是可能会受影响的的环境的详尽列表:

使用containerd/runc的Docker环境

特定条件

  • 启用了systemdcgroup驱动程序(例如,在/etc/docker/daemon.json中设置了参数"exec-opts": ["native.cgroupdriver=systemd"]
  • 使用了更新的Docker版本,其中systemd cgroup管理被默认设置的(即,在Ubuntu 22.04上)。

Note如果你要检查Docker是否使用systemd cgroup管理,运行以下命令(下面的输出表示启用了systemd cgroup驱动程序)

 $ docker info  
 ...  
 Cgroup Driver: systemd  
 Cgroup Version: 1

使用containerd/runc的Kubernetes环境

特定条件

  • containerd配置文件(通常位于:/etc/containerd/config.toml)中设置SystemdCgroup = true,如下所示:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
BinaryName = "/usr/local/nvidia/toolkit/nvidia-container-runtime"
...
SystemdCgroup = true

Note:如果你想要检查containerd是否使用systemd cgroup管理,运行以下命令(下面的输出表示启用了systemd cgroup驱动程序)

$ sudo crictl info  
...  
"runtimes": {
    "nvidia": {
        "runtimeType": "io.containerd.runc.v2",
        ...
        "options": {
          "BinaryName": "/usr/local/nvidia/toolkit/nvidia-container-runtime",
          ...
          "ShimCgroup": "",
          "SystemdCgroup": true

使用cri-o/runc的Kubernetes环境

特定条件

cri-o配置文件中启用了systemdcgroup_manager(通常位于:/etc/crio/crio.conf或/etc/crio/crio.conf.d/00-default),如下所示(使用OpenShift的示例):

[crio.runtime]
...
cgroup_manager = "systemd"

hooks_dir = [
"/etc/containers/oci/hooks.d",
"/run/containers/oci/hooks.d",
"/usr/share/containers/oci/hooks.d",
]

Note: Podman环境默认使用crun,并且除非刻意将runc配置为要使用的底层容器运行CRI时,否则不会受到这个问题的影响。

如何检查你的环境是否受到影响

您可以按照以下步骤确认您的系统是否受到影响。您可以这些步骤来确认错误是否可以复现。

Docker环境

运行一个测试的container

$ docker run -d --rm --runtime=nvidia --gpus all \
    --device=/dev/nvidia-uvm \
    --device=/dev/nvidia-uvm-tools \
    --device=/dev/nvidia-modeset \
    --device=/dev/nvidiactl \
    --device=/dev/nvidia0 \
    nvcr.io/nvidia/cuda:12.0.0-base-ubuntu20.04 bash -c "while [ true ]; do nvidia-smi -L; sleep 5; done"  

bc045274b44bdf6ec2e4cc10d2968d1d2a046c47cad0a1d2088dc0a430add24b

Note 请确保你测试的container挂载不同的设备(如上所示)。这是确定此特定问题的必要条件。

如果您的系统有超过1个GPU,请在上述命令后附加额外的 –device 挂载。拥有2个GPU的系统的例子:

$ docker run -d --rm --runtime=nvidia --gpus all \
    ...
    --device=/dev/nvidia0 \
    --device=/dev/nvidia1 \
    ...

此时可以查看container的log

$ docker logs bc045274b44bdf6ec2e4cc10d2968d1d2a046c47cad0a1d2088dc0a430add24b

GPU 0: Tesla K80 (UUID: GPU-05ea3312-64dd-a4e7-bc72-46d2f6050147)
GPU 0: Tesla K80 (UUID: GPU-05ea3312-64dd-a4e7-bc72-46d2f6050147)

然后使用下面的命令来触发 daemon-reload

$ sudo systemctl daemon-reload

此时再次查看container的log, 我们可以复现这个问题

$ docker logs bc045274b44bdf6ec2e4cc10d2968d1d2a046c47cad0a1d2088dc0a430add24b

GPU 0: Tesla K80 (UUID: GPU-05ea3312-64dd-a4e7-bc72-46d2f6050147)
GPU 0: Tesla K80 (UUID: GPU-05ea3312-64dd-a4e7-bc72-46d2f6050147)
GPU 0: Tesla K80 (UUID: GPU-05ea3312-64dd-a4e7-bc72-46d2f6050147)
GPU 0: Tesla K80 (UUID: GPU-05ea3312-64dd-a4e7-bc72-46d2f6050147)
Failed to initialize NVML: Unknown Error
Failed to initialize NVML: Unknown Error

K8s 环境

运行一个测试的Pod

$ cat nvidia-smi-loop.yaml

apiVersion: v1
kind: Pod
metadata:
  name: cuda-nvidia-smi-loop
spec:
  restartPolicy: OnFailure
  containers:
  - name: cuda
    image: "nvcr.io/nvidia/cuda:12.0.0-base-ubuntu20.04"
    command: ["/bin/sh", "-c"]
    args: ["while true; do nvidia-smi -L; sleep 5; done"]
    resources:
      limits:
        nvidia.com/gpu: 1


$ kubectl apply -f nvidia-smi-loop.yaml  
 
pod/cuda-nvidia-smi-loop created

此时可以查看Pod的log

$ kubectl logs cuda-nvidia-smi-loop

GPU 0: NVIDIA A100-PCIE-40GB (UUID: GPU-551720f0-caf0-22b7-f525-2a51a6ab478d)
GPU 0: NVIDIA A100-PCIE-40GB (UUID: GPU-551720f0-caf0-22b7-f525-2a51a6ab478d)

然后使用下面的命令来触发 daemon-reload

$ sudo systemctl daemon-reload

此时再次查看Pod的log, 我们可以复现这个问题

$ kubectl logs cuda-nvidia-smi-loop

GPU 0: NVIDIA A100-PCIE-40GB (UUID: GPU-551720f0-caf0-22b7-f525-2a51a6ab478d)
GPU 0: NVIDIA A100-PCIE-40GB (UUID: GPU-551720f0-caf0-22b7-f525-2a51a6ab478d)
Failed to initialize NVML: Unknown Error
Failed to initialize NVML: Unknown Error

Workarounds

下面的解决方法适用于独立的docker环境和k8s环境(按照推荐顺序👍提供多个选项;最推荐👍的选项在最上面):

Docker环境

使用nvidia-ctk工具:

NVIDIA Container Toolkit v1.12.0包含了一个在/dev/char中为所有可能的NVIDIA设备节点创建符号链接的工具,这些节点对于在容器中使用GPU是必要的。该工具可以如下运行:

sudo nvidia-ctk system create-dev-char-symlinks \
    --create-all

这个命令应该在每个节点上的启动时运行,这样容器中使用GPU时就不会出现问题。在运行这个命令之前,需要确保NVIDIA驱动的内核模块已经加载。

一个简单的udev规则可以强制执行这一点,如下所示:

# This will create /dev/char symlinks to all device nodes
ACTION=="add", DEVPATH=="/bus/pci/drivers/nvidia", RUN+="/usr/bin/nvidia-ctk system 	create-dev-char-symlinks --create-all"

安装此规则的推荐的地方是:/lib/udev/rules.d/71-nvidia-dev-char.rules

在使用NVIDIA GPU Driver Container的情况下,必须指定驱动程序安装的路径。在这种情况下,命令应该修改为:

sudo nvidia-ctk system create-dev-symlinks \
        --create-all \
        --driver-root={{NVIDIA_DRIVER_ROOT}}

{{NVIDIA_DRIVER_ROOT}}是NVIDIA GPU Driver container安装NVIDIA GPU驱动程序并创建NVIDIA设备节点的路径。

隐式地禁用Docker中的systemd cgroup管理

设置/etc/docker/daemon.json文件中的参数"exec-opts": ["native.cgroupdriver=cgroupfs"]并重启docker。

降级docker.io

降级到docker.io包,其中systemd不是默认的cgroup管理器(当然不要覆盖)。

K8s 环境

GPU Operator

你可以使用GPU Operator22.9.2以及以上版本,它会自动修复集群中所有K8s节点上的问题(此修复已经集成到gpu-operator-validator中,当新Node部署或每次Node重启时都会运行)。

k8s-device-plugin

对于直接使用k8s-device-plugin的场景来说(即不通过GPU Operator使用),你可以安装如上一节所述的udev规则来解决此问题。。如果你使用了container来安装driver的话,请请确保传递正确的{{NVIDIA_DRIVER_ROOT}}。详情请参考configuration-option-details in k8s-device-plugin

containerdcri-o隐式地禁用systemd cgroup

从cri-o配置文件中删除参数cgroup_manager = "systemd"(通常位于:/etc/crio/crio.conf/etc/crio/crio.conf.d/00-default),然后重启cri-o。

降级containerd

降级到containerd.io包的一个版本,其中systemd不是默认的cgroup管理器(当然不要覆盖)。

相关问题链接🔗