Featured image of post Nvidia显卡(四):容器化配置Nvidia显卡的CUDA编程和深度学习环境

Nvidia显卡(四):容器化配置Nvidia显卡的CUDA编程和深度学习环境

容器化配置Nvidia显卡的CUDA编程和深度学习环境

缘起

这是“Nvidia显卡”系列文章的第四篇,本系列文章主要记录如何使用Nvidia显卡,尤其是在Linux系统上使用Nvidia显卡。在之前的文章中,我记录了在Ubuntu系统上使用Nvidia显卡的相关设置,包括游戏、CUDA编程、深度学习、Docker容器等。后来我转向了Fedora系统,本以为在Fedora系统上配置Nvidia显卡的CUDA编程和深度学习环境会和在Ubuntu系统上类似,而且由于有RPM Fusion的支持,安装和配置会更加方便。然而实际上却遇到了意想不到的困难,因此不得不再开一篇文章来记录这些问题和解决方案。

简单来讲,上一篇文章中遇到的问题是,Fedora 42系统使用的GCC版本与Nvidia的CUDA版本不兼容,导致无法使用CUDA编译器,详见上一篇文章Nvidia显卡(三):Fedora下的游戏、CUDA、深度学习、Docker等的最后。为了解决这个问题,我决定在Fedora上使用Docker容器来配置Nvidia显卡的CUDA编程和深度学习环境。

本系列的其他文章参见:

容器方案

为了绕过Fedora 42系统与CUDA编译器不兼容的问题,我决定使用容器技术用Nvidia官方提供的镜像来创建一个基于Ubuntu的环境。

但我在创建的过程中仍然走了一些弯路,主要是因为我之前一直想使用Podman来替代Docker,这次就尝试了使用Podman来创建CUDA编程环境。然而由于权限问题、Nvidia的支持问题等等,遇到了一些未能解决的问题。最终我还是放弃了使用Podman,继续使用Docker。之后有机会我会写一篇文章来介绍Podman,未来我也会慢慢将容器服务转移到Podman,在这里就不再赘述了。这里主要记录一下用Docker来搭建CUDA编程和深度学习环境的过程。

基本框架

我们已经在Fedora系统上安装了Nvidia显卡和驱动,为了让Docker容器内的应用能够使用Nvidia显卡,我们还需要安装Nvidia的Docker支持。因此整个环境的基本框架如下:

Nvidia docker framework

  • 最底层是支持CUDA的Nvidia显卡。
  • 其次是安装了Nvidia驱动的宿主机(host machine)。
  • 然后是让容器应用调用宿主机显卡的Nvidia容器工具箱(Nvidia Container Toolkit)。
  • 最上层是容器,里面有CUDA工具箱,用以对GPU进行编程和调用。

CUDA编程和深度学习环境容器的搭建

宿主机的准备

在开始创建和部署容器前,我们要先保证宿主机上已经安装了Nvidia驱动、Docker和Nvidia容器工具箱(Nvidia Container Toolkit)。

  • Nvidia驱动:请参考上一篇文章Nvidia显卡(三):Fedora下的游戏、CUDA、深度学习、Docker等中关于在Fedora系统上通过RPM Fusion安装Nvidia驱动的部分。 可以通过以下命令来确认Nvidia驱动是否安装成功:

    1
    
    nvidia-smi
    

    如果运行上面的命令可以正确输出类似下面所示的GPU当前的状态,就说明驱动已安装成功:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    +-----------------------------------------------------------------------------------------+
    | NVIDIA-SMI 575.64.05              Driver Version: 575.64.05      CUDA Version: 12.9     |
    |-----------------------------------------+------------------------+----------------------+
    | GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
    | Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
    |                                         |                        |               MIG M. |
    |=========================================+========================+======================|
    |   0  NVIDIA GeForce RTX 5070 Ti     Off |   00000000:02:00.0  On |                  N/A |
    |  0%   42C    P8             18W /  300W |    2837MiB /  16303MiB |      1%      Default |
    |                                         |                        |                  N/A |
    +-----------------------------------------+------------------------+----------------------+
    
    +-----------------------------------------------------------------------------------------+
    | Processes:                                                                              |
    |  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
    |        ID   ID                                                               Usage      |
    |=========================================================================================|
    |    0   N/A  N/A            2540      G   /usr/bin/ksecretd                         3MiB |
    

    然后记录下与当前驱动相对应的CUDA版本号,例如上面显示支持CUDA 12.9。

  • Docker:请参考Docker官方文档,主要步骤如下:

    1
    2
    3
    4
    
    sudo dnf -y install dnf-plugins-core
    sudo dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
    sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    sudo systemctl enable --now docker
    

    可以运行下面的命令来验证是否安装成功:

    1
    
    sudo docker run hello-world
    

    如果不想使用sudo来运行Docker,需要将当前用户添加到docker组中:

    1
    2
    
    sudo usermod -aG docker $USER
    newgrp docker
    
  • Nvidia容器工具箱(Nvidia Container Toolkit):请参考Nvidia官方文档进行安装,主要步骤如下:

    1
    2
    3
    4
    5
    6
    7
    8
    
    curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | \
    sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo
    export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.17.8-1
    sudo dnf install -y \
        nvidia-container-toolkit-${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
        nvidia-container-toolkit-base-${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
        libnvidia-container-tools-${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
        libnvidia-container1-${NVIDIA_CONTAINER_TOOLKIT_VERSION}
    

    安装完成后,可以运行以下命令来验证Nvidia容器工具箱是否安装成功(注意这里的CUDA版本号要和你自己的版本号一致):

    1
    
    docker run --rm --gpus all nvidia/cuda:12.9.1-base-ubuntu24.04 nvidia-smi
    

    如果能够像在宿主机里运行nvidia-smi命令一样正确输出Nvidia显卡的状态信息,就说明Nvidia容器工具箱安装成功。

创建和部署容器

  1. 选择镜像

    Nvidia官方提供了多种CUDA编程和深度学习的容器镜像,我们可以在他们的Docker镜像站点根据需要选择对应的容器来创建和部署。里面的容器种类很多,大致可以分为以下两类:

    • 通用镜像:适合定义一些比较通用的CUDA编程和深度学习环境。里面又有不同的种类:

      • 基础镜像:如nvidia/cuda:12.9.1-base-ubuntu24.04,这是一个基础的CUDA镜像,包含Ubuntu 24.04和CUDA 12.9.1的基础环境,适合需要自定义开发环境的用户。
      • 深度学习框架镜像:如nvidia/cuda:12.9.1-devel-ubuntu24.04,这是一个包含了CUDA开发环境的镜像,用户不需要再安装CUDA开发工具包,适合需要进行CUDA编程的用户。
      • 运行时镜像:如nvidia/cuda:12.9.1-runtime-ubuntu24.04,这是一个只包含运行时环境的镜像,适合只需要运行CUDA应用的用户。
    • 专用镜像:适合一些特定的深度学习框架和应用。里面也有不同的种类:

      • TensorFlow镜像:如nvidia/13.0.0-tensorrt-devel-ubuntu24.04,这是一个包含了TensorFlow和CUDA的镜像,适合需要使用TensorFlow进行深度学习的用户。
      • cudnn镜像:如nvidia/13.0.0-cudnn-devel-ubuntu24.04,这是一个包含了cuDNN和CUDA的镜像,适合需要使用cuDNN进行深度学习的用户。

    对我而言,我需要更加通用一些的场景,而且各种工具和库可能都需要用到,因此我选择了nvidia/cuda:12.9.1-devel-ubuntu24.04这个镜像作为基础镜像。这里的12.9.1是我之前在宿主机上安装的Nvidia驱动对应的CUDA版本号。

  2. 设计容器方案

    考虑到我需要使用这个容器开发相当长的时间,因此在扩展了一些工具或者进行了一些开发之后,需要把这些更改都给保存下来。这里有两种方案来保存这些更改:

    • 提交容器:在容器内安装和配置好所有需要的工具和库后,可以使用docker commit命令将当前容器的状态保存为一个新的镜像。这样可以方便地在以后重新创建相同环境的容器。
    • 保存到磁盘:将一个物理机上的磁盘(或者一个文件夹)挂载到容器内,开发时就在这个挂载点上进行所有的文件操作,这样所有的更改都会直接反映到宿主机上。

    这里我决定使用第二种方案,因为万一容器有啥问题,我能方便地换容器镜像,而不用担心开发的数据丢失。所以这里我们使用一个比较基础的镜像,里面安装一些基本的工具,偏定制化的工具我们在容器部署后在容器内进行安装和配置,并把安装位置和配置文件都放在挂载的目录中。

  3. 创建Dockerfile

    在创建容器之前,我们需要先创建一个Dockerfile文件来定义容器的环境。这个文件可以放在任意目录下,我建议放在一个专门的目录中,例如~/docker/cuda-dev。下面是一个简单的Dockerfile示例:

     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
    27
    28
    29
    30
    31
    32
    
    # Base: CUDA 12.9 + Ubuntu 22.04
    FROM nvidia/cuda:12.9.1-devel-ubuntu24.04
    
    ENV DEBIAN_FRONTEND=noninteractive
    
    # Install system dependencies and sudo
    USER root
    RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential \
        gcc-14 g++-14 \
        cmake \
        git \
        wget \
        curl \
        sudo \
        bzip2 \
        ca-certificates \
        zsh \
        && rm -rf /var/lib/apt/lists/*
    
    # Update alternatives to use GCC 14
    RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 \
        && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
    
    # Give ubuntu user passwordless sudo
    RUN echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
    
    # Switch to ubuntu user
    USER ubuntu
    WORKDIR /workspace
    
    CMD ["/bin/bash"]
    

    这个Dockerfile会创建一个基于Nvidia CUDA开发镜像的容器,除了基础的工作,我还做了以下配置:

    • 将容器默认的用户ubuntu加入到sudo组中,这样可以在容器内使用sudo命令。
    • 安装了一些常用的工具和库,如build-essentialgitwgetcurlvim等。
    • 安装了zsh,毕竟我已经习惯了使用oh-my-zsh作为shell工具。

    除此之外,我还比较习惯用oh-my-zshpowerlevel10k定制终端,以及使用mamba作为开发环境管理工具。但是这些都是比较个性化的配置,按之前说的,我们在容器内进行安装和配置即可。

  4. 创建docker-compose.yml

    为了方便管理和部署容器,我们可以使用docker-compose来创建一个docker-compose.yml文件。这个文件可以放在同一个目录下,例如~/docker/cuda-dev/docker-compose.yml。下面是一个简单的docker-compose.yml示例:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    version: "3.9"
    
    services:
    cuda-dev:
        build: .
        container_name: cuda-dev
        runtime: nvidia
        deploy:
        resources:
            reservations:
            devices:
                - driver: nvidia
                count: all
                capabilities: [gpu]
        environment:
        - NVIDIA_VISIBLE_DEVICES=all
        - NVIDIA_DRIVER_CAPABILITIES=compute,utility
        volumes:
        - /home/${USER}/develop:/home/ubuntu
        - /run/media/${USER}/Disk1/devdata:/data
        tty: true
    

    这个配置文件定义了一个名为cuda-dev的服务,使用我们之前创建的Dockerfile来构建镜像。我在宿主机的家目录下创建了一个workspace目录来存放开发文件,并将其挂载到容器的/home/ubuntu目录下。同时,我还将宿主机的一整个磁盘挂载到容器的/data目录下,这样可以方便地在容器内访问宿主机的文件。

    选择这种挂载方式是为了与上面的设计相适应:

    • develop文件夹挂载到容器的/home/ubuntu目录下,这样我们如果在容器内直接安装到/home/ubuntu,或者把开发文件放在这个目录下,所有的更改都会直接保存到宿主机里的develop文件夹中。
    • 将宿主机的/run/media/${USER}/Disk1/devdata目录挂载到容器的/data目录下,这样我们把开发时需要的数据放在硬盘Disk1devdata目录下,容器内就可以直接访问这些数据。
  5. 构建和运行容器

    在终端中进入到docker-compose.yml所在的目录,例如~/docker/cuda-dev,然后运行以下命令来构建容器:

    1
    
    docker compose build
    

    构建完成后用以下命令来运行容器:

    1
    
    docker compose up -d
    

    运行完成后,你可以通过以下命令来查看容器的状态:

    1
    
    docker ps | grep cuda-dev-container
    
  6. 访问和测试容器

    容器运行后,你可以通过以下命令进入容器的终端:

    1
    
    docker exec -it cuda-dev-container bash
    
    1. 查看CUDA编译器

      进入容器后,你可以测试CUDA编程环境是否正常工作,例如运行以下命令来检查CUDA版本:

      1
      
      nvcc --version
      

      如果输出了正确的CUDA版本信息,就说明CUDA编译器工作正常。

    2. 查看容器能否调用Nvidia显卡

      运行以下命令来检查Nvidia显卡的状态:

      1
      
      nvidia-smi
      

      如果输出了正确的Nvidia显卡的状态信息,就说明容器可以调用Nvidia显卡。

    3. 测试编译CUDA程序

      接下来我们来尝试编译在宿主机中编译失败的CUDA-Sample程序。去CUDA-Sample GitHub仓库Release页面,选择跟你的CUDA版本号对应的版本下载,例如CUDA Samples v12.9。将下载的文件放到宿主机的~/docker/cuda-dev/workspace目录下,也可以在容器内使用wget命令直接下载:

      1
      
      wget https://github.com/NVIDIA/cuda-samples/archive/refs/tags/v12.9.tar.gz
      

      然后在容器内解压缩:

      1
      2
      
      tar -xzvf v12.9.tar.gz
      cd cuda-samples-12.9
      

      之后就是正常的编译和运行流程了:

      1
      2
      3
      
      mkdir build && cd build
      cmake ..
      make -j4
      

      编译完成后,你可以运行一些示例程序来测试CUDA编程环境是否正常工作,例如:

      1
      2
      
      cd 1_Utilities/deviceQuery
      ./deviceQuery
      

      如果输出了Nvidia显卡的相关信息,就说明大功告成!

    4. 测试Python能否调用GPU

      进入容器后,你可以测试Python能否调用GPU,例如运行以下Python代码:

      1
      2
      3
      
      import torch
      print(torch.cuda.is_available())
      print(torch.cuda.get_device_name(0))
      

      如果输出True和你的Nvidia显卡名称,就说明Python可以调用GPU。

  7. 使用VS Code远程开发

    如果你想在VS Code中使用这个容器进行远程开发,可以安装Remote - Containers扩展。

    在宿主机打开VS Code后,点击左下角的远程连接图标,选择“Attach to Running Container”,然后选择cuda-dev-container容器,就可以连接到容器了。

    接下来你可以通过VS Code的终端直接进入容器命令行,也可以直接用VS Code打开容器中的工作目录进行开发。

问题

在CUDA开发容器里使用YOLO训练模型时,遇到了一个报错:

1
ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm).

这个问题的原因是Docker容器的共享内存(shm)默认大小只有64MB,比较小,不足以满足YOLO训练模型时的需求。解决这个问题的方法是增加Docker容器的共享内存大小,可以通过在docker-compose.yml文件中添加shm_size参数来实现,例如:

1
2
3
services:
  cuda-dev:
    shm_size: '8gb'

这里的shm_size可以根据需要调整大小,例如8gb16gb等。添加后重新构建和运行容器即可。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计