前几篇,我们已经一路从:
Model
↓
Runtime
↓
Serving Engine
↓
GPU
走到了:
vLLM
↓
Scheduler
↓
Continuous Batching
↓
PagedAttention
↓
GPU
甚至开始测:
TTFT
TPOT
ITL
Throughput
Concurrency
到了这里,很容易产生一种错觉:
只要 GPU 驱动装好了,CUDA 能跑,接下来把程序塞进 Docker 就行。
但真正开始把 AI 应用容器化以后,会马上遇到一个非常经典的问题。
宿主机:
nvidia-smi
一切正常。
RTX 3060 清清楚楚地在那里。
可是启动一个普通 Docker 容器:
docker run --rm ubuntu:24.04 nvidia-smi
却会得到:
nvidia-smi: command not found
或者即使自己装进去了相关工具,也可能发现:
No devices were found
于是问题来了:
GPU 明明插在这台机器上
为什么:
Host
↓
Docker Container
GPU 就消失了?
这一篇先不跑 vLLM。
也不讨论:
PagedAttention
KV Cache
Continuous Batching
我们把整个系统再往下拆一层,专门研究:
Docker
NVIDIA Container Toolkit
GPU Device
Runtime
CDI
然后做两个最基础的实验:
Docker Container
↓
nvidia-smi
以及:
Docker Container
↓
PyTorch
↓
torch.cuda.is_available()
如果这两个实验真正跑通,那么后面才有资格谈:
vLLM Container
TensorRT-LLM Container
Kubernetes GPU Pod
GPU Operator
一、Docker 默认为什么看不到 GPU?

先暂时忘掉 NVIDIA。
我们从 Linux Container 最基本的原理开始。
Docker Container 本质上并不是:
一台小型虚拟机
它更接近:
Host Linux Kernel
│
├── Container A
├── Container B
└── Container C
多个 Container 共享:
Host Kernel
然后通过:
Namespace
cgroup
Mount
Capability
Seccomp
...
把进程隔离开。

所以 Container 默认看到的东西,是:
Docker
按照 OCI Runtime 配置
允许它看到的资源
而不是:
宿主机有什么
Container 就自动有什么
这点非常重要。
比如宿主机上可能存在:
/dev/sda
/dev/nvme0n1
/dev/video0
/dev/dri/*
/dev/nvidia0
但 Docker 不会因为这些 Device Node 存在,就全部暴露给 Container。
否则:
Container
↓
直接操作宿主机硬盘
或者:
Container
↓
任意访问摄像头
显然都很危险。
所以 Docker 的基本原则实际上是:
设备访问需要显式授予。
GPU 也不例外。
二、GPU 在 Linux 里到底是什么?
我们平时看到 GPU,很容易想到:
RTX 3060
12GB VRAM
3584 CUDA Cores
但站在 Linux Kernel 和 Container 的角度,GPU 首先表现成一些:
Device Node
宿主机上可以看:
ls -l /dev/nvidia*
典型会看到:
/dev/nvidia0
/dev/nvidiactl
/dev/nvidia-modeset
/dev/nvidia-uvm
/dev/nvidia-uvm-tools
其中可以粗略理解为:
/dev/nvidia0
↓
具体 GPU Device
/dev/nvidiactl
↓
NVIDIA Driver Control Device
/dev/nvidia-uvm
↓
Unified Virtual Memory
也就是说:
PyTorch
│
▼
CUDA Runtime
│
▼
NVIDIA User-space Driver
│
▼
/dev/nvidia*
│
▼
NVIDIA Kernel Driver
│
▼
GPU
于是问题已经变得非常具体了。
一个普通 Docker Container 默认并没有:
/dev/nvidia0
因此:
Container
自然无法直接访问:
GPU

三、那直接 --device=/dev/nvidia0 不就行了吗?
看到这里,很容易想到:
docker run \
--device=/dev/nvidia0 \
...
Docker 本身确实支持:
--device
把宿主机 Device Node 暴露给 Container。
那是不是 GPU 问题到这里就解决了?
并没有。
因为 CUDA 程序需要的不只是:
/dev/nvidia0
它还可能需要:
/dev/nvidiactl
/dev/nvidia-uvm
以及宿主机 Driver 对应的一系列:
Driver Libraries
例如:
libcuda.so
libnvidia-ml.so
另外还涉及:
环境变量
Device Permission
Library Path
Driver Capability
所以真正需要做的并不是:
把一块 Device Node 塞进去
而更接近:
识别用户请求的 GPU
│
▼
找到对应 Device Nodes
│
▼
把 Device 暴露给 Container
│
▼
挂载需要的 NVIDIA Driver Libraries
│
▼
配置环境变量 / Hook
│
▼
启动 Container
这一整套事情如果全部手工完成,会非常麻烦。
于是:
NVIDIA Container Toolkit
出现了。
四、NVIDIA Container Toolkit 到底解决什么?
可以先画出一个非常粗略的结构:
Application
│
▼
Docker CLI
│
▼
Docker Engine
│
▼
Container Runtime
│
▼
NVIDIA Container Toolkit
│
├── GPU Device
├── Driver Library
├── Environment
└── Runtime Configuration
│
▼
OCI Runtime
│
▼
Container
│
▼
GPU
所以:
NVIDIA Container Toolkit
并不是:
CUDA
也不是:
GPU Driver
更不是:
Docker 的替代品
它解决的是:
怎样把宿主机已有的 NVIDIA GPU 能力正确注入 Container。

NVIDIA 当前官方架构中,nvidia-ctk 可以负责配置 Docker 等 Container Runtime,也可以生成 CDI Device Specification。
于是整个软件栈可以先记成:
┌──────────────────────────────┐
│ Container Application │
│ PyTorch / CUDA / vLLM │
└───────────────┬──────────────┘
│
┌───────────────▼──────────────┐
│ Container CUDA Libraries │
└───────────────┬──────────────┘
│
┌───────────────▼──────────────┐
│ NVIDIA Driver Libraries │
│ exposed from Host │
└───────────────┬──────────────┘
│
┌───────────────▼──────────────┐
│ /dev/nvidia* │
└───────────────┬──────────────┘
│
┌───────────────▼──────────────┐
│ Host NVIDIA Kernel Driver │
└───────────────┬──────────────┘
│
▼
GPU
这张图很重要。
因为它解释了一个以后经常会遇到的问题:
Container 一般并不自己加载一套 NVIDIA Kernel Driver。
真正控制硬件的 Kernel Driver 仍然运行在:
Host
Container 主要使用:
Host Driver Interface
+
Container User-space CUDA Stack
五、Host Driver 和 Container CUDA 为什么可以不完全一样?
这里还有一个非常容易混淆的概念。
很多人会问:
Host 装了 CUDA 12.x
Container 是 CUDA 12.y
为什么还能运行?
原因是我们需要区分:
NVIDIA Driver
和
CUDA Toolkit / CUDA Runtime
它们不是同一个东西。
宿主机真正必须存在的是:
NVIDIA Kernel Driver
例如:
nvidia-smi
能够正常运行。
而 Container Image 里可以携带:
CUDA Runtime
cuBLAS
cuDNN
PyTorch
...
所以大致变成:
Host
────────────────────────
Linux Kernel
│
NVIDIA Driver
│
GPU
Container
────────────────────────
PyTorch
│
CUDA Runtime
│
Host Driver Interface
│
GPU
这也是 Container 对 AI Infra 非常重要的原因之一:
Host
尽量保持稳定 Driver
Container
携带不同版本 AI Software Stack
例如:
Container A
PyTorch + CUDA 12.x
Container B
TensorRT + CUDA 12.y
Container C
vLLM + CUDA 12.z
底下仍然共享:
Host NVIDIA Driver
当然,这并不代表:
任意 CUDA Runtime
+
任意 Driver
都一定兼容。
Container Image 仍然会受到 Driver 能力和 CUDA Compatibility 要求约束。
NVIDIA 官方 Container Toolkit 也支持通过类似:
NVIDIA_REQUIRE_CUDA
这样的约束检查 Driver 是否满足 Container 所需 CUDA 能力。
六、--gpus all 到底做了什么?
完成 NVIDIA Container Toolkit 配置以后,我们最常见的命令是:
docker run --rm \
--gpus all \
IMAGE
这里最关键的是:
--gpus all
Docker 官方当前仍然把 --gpus 作为给 Container 暴露 NVIDIA GPU 的标准方式之一。

可以粗略理解成:
docker run
│
├── Image
│
├── CPU
│
├── Memory
│
└── --gpus all
│
▼
GPU Resource Request
│
▼
NVIDIA Container Stack
│
├── Device Nodes
├── Driver Libraries
└── Runtime Config
│
▼
Container
这里有一个很重要的思想:
GPU 开始变成 Container 的一种显式资源。
它和:
CPU
Memory
Disk
Network
在 Infra 思维里开始变得越来越接近。
以后到了 Kubernetes,我们看到的:
resources:
limits:
nvidia.com/gpu: 1
本质上仍然是在继续解决:
哪个 Workload
可以使用哪块 GPU?
只是系统从:
Docker
升级成了:
Cluster Scheduler
七、Runtime 到底在哪里?
现在再看:
Runtime
这个词。
它特别容易和前面我们讨论过的:
Model Runtime
混在一起。
例如:
Ollama Runtime
llama.cpp Runtime
这里说的是:
Model Runtime
负责:
模型加载
推理
Tensor
Kernel
Memory
而 Docker 这里的 Runtime 是另外一层。
例如可以粗略画成:
Docker
│
▼
Container Runtime
│
▼
OCI Runtime
│
▼
Linux Process
NVIDIA Container Toolkit 则插入:
GPU Device
+
Driver Capability
+
Library
等配置。
所以这一篇里需要特别区分:
Model Runtime
≠
Container Runtime
例如:
vLLM
↓
Model / Serving Runtime
Docker / containerd
↓
Container Runtime
runc
↓
OCI Runtime
它们恰好都叫:
Runtime
但所在层级完全不同。
八、传统 NVIDIA Runtime 模式
安装 NVIDIA Container Toolkit 以后,官方当前仍然提供:
sudo nvidia-ctk runtime configure --runtime=docker
这个命令会更新 Docker 的配置,使 Docker 能使用 NVIDIA Container Runtime,然后需要重启 Docker:
sudo systemctl restart docker
这是 NVIDIA 当前官方安装文档给出的 Docker 配置方式。
可以粗略理解:
Docker Engine
│
▼
NVIDIA Container Runtime
│
▼
OCI Runtime
│
▼
Container
然后:
docker run \
--rm \
--gpus all \
ubuntu:24.04 \
nvidia-smi
GPU 信息就可以被注入 Container。
NVIDIA 官方目前也使用类似下面的命令作为 Sample Workload:
docker run \
--rm \
--runtime=nvidia \
--gpus all \
ubuntu \
nvidia-smi
来验证 GPU Container 环境。
不过现代 Docker 用户平时更常见的仍然是:
--gpus all
九、但为什么现在又出现了 CDI?
做到这里,其实已经可以用 GPU 了。
那为什么 NVIDIA 和 Container Runtime 生态后来还要推动:
CDI
也就是:
Container Device Interface
因为 GPU 并不是唯一需要暴露给 Container 的特殊硬件。
还可能有:
GPU
FPGA
NIC
Accelerator
RDMA Device
AI ASIC
...
如果每一种硬件都要求:
Container Runtime
专门认识这种设备
整个生态会越来越复杂。
于是一个非常自然的想法出现:
能不能把"怎样把某种设备注入 Container"描述成一个标准?
这就是 CDI。

十、CDI 可以先理解成"设备描述文件"
CDI 的思路可以先简化成:
Vendor / Device Plugin
│
▼
生成 CDI Spec
│
▼
Container Runtime
读取 CDI Spec
│
▼
知道应该注入:
Device Node
Mount
Environment
Hook
...
比如 NVIDIA GPU 会出现类似:
nvidia.com/gpu=0
nvidia.com/gpu=all
这样的 Device Name。
Container Runtime 不需要在代码里硬编码:
NVIDIA GPU 到底要挂哪些文件?
而只需要理解:
CDI
这一套统一描述。
NVIDIA Container Toolkit 从较早版本就已经支持生成 CDI Specification;当前文档中,Toolkit 1.18.0 起会通过 nvidia-cdi-refresh systemd 服务自动生成和维护:
/var/run/cdi/nvidia.yaml
并可以使用:
nvidia-ctk cdi list
查看可用设备。
十一、CDI Spec 里面到底描述什么?
我们并不需要一开始就读完整 YAML。
只需要理解它大概解决:
Device
应该怎样出现在 Container 里?
例如概念上类似:
kind: nvidia.com/gpu
devices:
- name: "0"
containerEdits:
deviceNodes:
- /dev/nvidia0
真实 CDI Spec 会比这复杂。
它还可能描述:
Device Nodes
Environment Variables
Library Mounts
Hooks
Docker 对 CDI 的定义也明确包括:
Device
Environment Variable
Host Mount
Executable Hook
等 Container 配置。
因此 CDI 的真正价值并不是:
换了一种 --gpus 写法
而是:
把 Hardware Device Injection 变成标准接口。
这对 Infra 特别重要。
十二、Docker 现在也原生支持 CDI 了
这个地方需要特别注意版本。
现代 Docker Engine 已经能够直接识别 CDI Device。
Docker 官方当前文档显示:
Docker Engine 28.3.0+
CDI 默认启用。
Docker Daemon 默认读取:
/etc/cdi/
/var/run/cdi/
中的 CDI Spec。
所以在支持的环境里,可以出现:
docker run \
--rm \
--device=nvidia.com/gpu=all \
IMAGE
这种非常有意思的写法。
整个结构开始变成:
Docker
│
│ request:
│ nvidia.com/gpu=all
▼
CDI
│
▼
/var/run/cdi/nvidia.yaml
│
├── Device Nodes
├── Libraries
├── Environment
└── Hooks
│
▼
Container
相比以前:
Docker
↓
NVIDIA-specific Runtime Logic
CDI 更像:
Docker
↓
Generic Device Interface
↓
Vendor Device Specification
这就是它真正值得学习的地方。
十三、--gpus all 和 CDI 是谁替代谁?
这里不要简单理解成:
CDI 出现
↓
--gpus all 淘汰
现实并不是这么简单。
今天仍然大量使用:
docker run --gpus all
Docker 官方文档也继续支持这套接口。
与此同时:
CDI
提供了更加通用的 Device Model。
所以现阶段可以先这样理解:
传统用户接口
docker run --gpus all
│
▼
NVIDIA GPU Support
更通用设备模型
docker run --device=nvidia.com/gpu=all
│
▼
CDI
│
▼
Generic Device Injection
对于我们这篇实验:
先把 --gpus all 跑通
这是最重要的。
然后再:
观察 CDI
理解未来 Kubernetes、Podman 和各种 Accelerator Runtime 为什么越来越喜欢这套 Device Model。
十四、实验环境
实验继续使用前面几篇的 AI Infra 实验机:
Ubuntu 24.04 Server
Intel Core i5-10400F
NVIDIA GeForce RTX 3060
12GB VRAM
这和前面 vLLM 实验继续使用 RTX 3060 12GB 的思路一致。
这一篇不需要:
7B Model
vLLM
Ollama
因为我们只验证:
Container
↓
GPU
所以实验链路反而非常简单:
实验 1
Host nvidia-smi
实验 2
普通 Docker Container
默认看不到 GPU
实验 3
安装 NVIDIA Container Toolkit
实验 4
Docker + --gpus all
运行 nvidia-smi
实验 5
Docker + PyTorch
运行 torch.cuda
实验 6
观察 CDI
这一次真正的控制变量只有一个:
GPU 有没有被显式注入 Container
十五、实验一:先确认宿主机 GPU 正常
首先:
nvidia-smi
确认能够看到类似:
NVIDIA GeForce RTX 3060
然后:
ls -l /dev/nvidia*
应该能够看到 NVIDIA Device Node。
也可以:
lsmod | grep nvidia
确认 Driver Module 已经加载。

所以这一层:
Application
↓
NVIDIA Driver
↓
GPU
已经正常。
如果:
nvidia-smi
在 Host 都失败,那么暂时不要碰 Docker。
因为:
Docker GPU Support
建立在:
Host GPU Driver 正常
之上。
NVIDIA 官方安装 Container Toolkit 的前置条件同样要求系统先安装 NVIDIA GPU Driver。
十六、实验二:普通 Docker 默认看不到 GPU
如果没有 docker,按官网提示安装:
https://docs.docker.com/engine/install/ubuntu
先用一个普通 Ubuntu Container:
docker run \
--rm \
ubuntu:24.04 \
bash -c 'ls /dev | grep nvidia'
正常情况下:
没有输出
这就说明:
Host
/dev/nvidia0
并没有自动进入:
Container
再看:
docker run \
--rm \
ubuntu:24.04 \
bash -c 'ls -l /dev'
可以看到 Container 自己拥有很多 Device:
null
zero
random
urandom
tty
...
但是默认没有:
nvidia0
nvidiactl
nvidia-uvm
这个实验虽然极其简单,却非常重要。

因为它证明:
Container 隔离的不只是文件系统和网络,它也控制 Process 能看到哪些 Device。
十七、安装 NVIDIA Container Toolkit
按照 NVIDIA 当前 Ubuntu / Debian 官方方式:
https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
先准备:
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg2

加入 NVIDIA Container Toolkit Repository:
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

更新:
sudo apt-get update
安装:
sudo apt-get install -y \
nvidia-container-toolkit
这些步骤来自 NVIDIA 当前 Ubuntu / Debian 安装流程。


这里我不把 Toolkit 版本硬编码进实验命令。
因为个人实验机器更适合直接跟随:
NVIDIA Stable Repository
安装当前稳定版本。
如果做生产环境:
Production
CI
Cluster
则应该进一步考虑:
Version Pinning
来保证环境可复现。
十八、让 Docker 知道 NVIDIA Runtime
接下来运行:
sudo nvidia-ctk \
runtime configure \
--runtime=docker
然后:
sudo systemctl restart docker
NVIDIA 官方说明这个命令会更新:
/etc/docker/daemon.json
使 Docker 可以使用 NVIDIA Container Runtime。
可以检查:
cat /etc/docker/daemon.json
再看:
docker info

到这里可以画成:
Before
Docker
│
▼
runc
│
▼
Container
After
Docker
│
├── runc
│
└── NVIDIA Container Runtime
│
▼
Container
│
▼
GPU
十九、实验三:Container 终于看到 GPU 了
现在执行:
docker run \
--rm \
--gpus all \
ubuntu:24.04 \
nvidia-smi
如果配置正确,应该能看到与宿主机类似的:
NVIDIA-SMI
Driver Version
CUDA Version
GPU 0
NVIDIA GeForce RTX 3060

注意:
Container 里显示 nvidia-smi
并不意味着:
Container 自己加载了 NVIDIA Kernel Driver
真正发生的仍然是:
Container
│
▼
NVIDIA Driver Interface
│
▼
Host Kernel Driver
│
▼
RTX 3060
这就是整个实验最核心的结果。
二十、观察 /dev/nvidia* 的变化
再做一个非常直观的对比。
普通 Container:
docker run \
--rm \
ubuntu:24.04 \
bash -c 'ls -l /dev/nvidia* 2>/dev/null || true'
大概率:
没有设备
GPU Container:
docker run \
--rm \
--gpus all \
ubuntu:24.04 \
bash -c 'ls -l /dev/nvidia*'
现在则可以看到:
/dev/nvidia0
/dev/nvidiactl
/dev/nvidia-uvm
...

于是:
同一个 Image
ubuntu:24.04
在两种运行方式下:
docker run ubuntu:24.04
和:
docker run --gpus all ubuntu:24.04
看到的硬件世界是不一样的。
这很好地说明:
Image 并不决定 Container 能访问什么 GPU。
真正决定这一点的是:
Container Run-time Configuration
二十一、这也解释了 Image 和 Container 的区别
这一篇顺便可以建立一个很重要的 Container 直觉。
同一个:
Image
完全可以启动成:
Container A
No GPU
Container B
GPU 0
Container C
GPU 1
Container D
GPU 0 + GPU 1
例如多 GPU 机器上可以指定:
docker run \
--rm \
--gpus '"device=0"' \
ubuntu:24.04 \
nvidia-smi
或者:
docker run \
--rm \
--gpus '"device=0,2"' \
ubuntu:24.04 \
nvidia-smi
Docker 官方当前同样支持按 GPU Index 或 GPU UUID 指定设备。
所以:
Image
定义:
Software Environment
而:
docker run
定义:
Runtime Resources
包括:
CPU
Memory
Network
Volume
GPU
这个区别以后进入 Kubernetes 会非常重要。
二十二、实验四:真正让 PyTorch 使用 GPU
nvidia-smi 只能证明:
GPU Device
+
NVML
已经可以在 Container 中访问。
但我们真正关心的是:
AI Framework
↓
CUDA
↓
GPU
所以再跑一个 PyTorch Container。
为了避免自己在 Ubuntu Image 中重新安装一整套 PyTorch,可以直接使用带 CUDA 的 PyTorch Image。
进入 Container:
docker run \
--rm \
--gpus all \
-it \
pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime \
python
具体 Tag 应以实验时 PyTorch 官方提供的版本为准。
然后:
import torch
检查:
print(torch.__version__)
再:
print(torch.cuda.is_available())
期待:
True
然后:
print(torch.cuda.device_count())
单 GPU:
1
再看:
print(torch.cuda.get_device_name(0))
应该出现:
NVIDIA GeForce RTX 3060

于是链路终于变成:
Docker Container
│
▼
PyTorch
│
▼
CUDA Runtime
│
▼
Host NVIDIA Driver
│
▼
RTX 3060
二十三、真正算一个 Tensor
只检查:
torch.cuda.is_available()
还不够有意思。
再真正执行一次 GPU Tensor 运算:
import torch
device = torch.device("cuda")
a = torch.randn(
4096,
4096,
device=device
)
b = torch.randn(
4096,
4096,
device=device
)
c = a @ b
torch.cuda.synchronize()
print(c.device)
print(c.shape)
期待:
cuda:0
torch.Size([4096, 4096])

实验时另开一个终端:
watch -n 0.5 nvidia-smi
就可能看到:
python
进程出现。

这样我们就完成了从:
Container 能看到 GPU
到:
Container 真正在 GPU 上计算
的验证。
二十四、实验结果建议这样记录
这一篇不用像 vLLM Benchmark 那样搞几十个性能指标。
只需要一张很简单的表:
| 实验 | GPU Device | nvidia-smi |
PyTorch CUDA |
|---|---|---|---|
| Host | ✅ | ✅ | --- |
| 普通 Docker | ❌ | ❌ | ❌ |
--gpus all |
✅ | ✅ | ✅ |
| CDI Device | ✅ | ✅ | ✅ |
如果继续记录版本,可以加:
Ubuntu Version
Docker Version
NVIDIA Driver
NVIDIA Container Toolkit
PyTorch Image
GPU
例如:
docker --version
nvidia-smi
nvidia-ctk --version
这样以后环境变化时比较容易复现。
二十五、实验五:看看 CDI 已经生成了什么
如果使用当前较新的 NVIDIA Container Toolkit,可以执行:
nvidia-ctk cdi list
可能看到类似:
nvidia.com/gpu=0
nvidia.com/gpu=GPU-xxxxxxxx
nvidia.com/gpu=all
NVIDIA 当前 Toolkit 会使用:
nvidia-cdi-refresh
维护:
/var/run/cdi/nvidia.yaml
可以看:
sudo cat /var/run/cdi/nvidia.yaml


不用试图一次看懂所有内容。
只关注:
kind
devices
deviceNodes
containerEdits
就够了。
现在前面的架构终于可以落到一个真实文件:
RTX 3060
│
▼
NVIDIA Container Toolkit
│
▼
/var/run/cdi/nvidia.yaml
│
▼
nvidia.com/gpu=0
│
▼
Container Runtime
二十六、实验六:直接用 CDI 启动 GPU Container
如果 Docker Engine 版本支持原生 CDI,可以尝试:
docker run \
--rm \
--device=nvidia.com/gpu=all \
ubuntu:24.04 \
nvidia-smi
Docker Engine 28.3.0 起 CDI 默认启用,并且可以通过:
--device=vendor.com/class=device
这种形式请求 CDI Device。

所以:
--device=nvidia.com/gpu=all
已经非常接近未来更统一的 Accelerator Resource 表达方式。
这时候我们就同时跑通了两条路径:
Path A
docker run
│
--gpus all
│
▼
NVIDIA GPU
Path B
docker run
│
--device=nvidia.com/gpu=all
│
▼
CDI
│
▼
NVIDIA GPU
对于个人实验,两条都值得知道。
二十七、nvidia-smi 能跑,但 PyTorch 仍然说 CUDA 不可用?
这是最常见的坑之一。
比如:
nvidia-smi
正常。
但是:
torch.cuda.is_available()
返回:
False
这时说明:
Device / Driver Access
和:
PyTorch CUDA Software Stack
并不是一个问题。
可能是:
PyTorch CPU-only Build
例如装错:
CPU Version
也可能是:
Container Image
根本没有 CUDA-enabled PyTorch
所以排障顺序最好分层。
二十八、GPU Container 排障,最好按层来
不要一上来就:
重装 CUDA
重装 Docker
重装 PyTorch
可以从底往上排。
第一层:
Host Hardware
检查:
lspci | grep -i nvidia
第二层:
Host Driver
检查:
nvidia-smi
第三层:
Device Node
检查:
ls -l /dev/nvidia*
第四层:
Container Runtime / Toolkit
检查:
nvidia-ctk --version
以及:
docker run \
--rm \
--gpus all \
ubuntu:24.04 \
nvidia-smi
第五层:
CUDA Framework
检查:
import torch
print(torch.cuda.is_available())
第六层:
Application
最后才检查:
vLLM
TensorRT
Application Code
整个排障树其实非常像:
Application
│
PyTorch / Runtime
│
CUDA
│
Container GPU Injection
│
NVIDIA Driver
│
Linux Device
│
Hardware
一层一层往下切。
二十九、坑一:宿主机 nvidia-smi 正常,不代表 Docker GPU 正常
这是这一篇最重要的坑。
Host
nvidia-smi
↓
正常
只能证明:
Host Driver
+
GPU
正常。
它不能证明:
Docker
+
NVIDIA Container Toolkit
+
Runtime Configuration
正常。
所以必须单独测:
docker run \
--rm \
--gpus all \
ubuntu:24.04 \
nvidia-smi
把两条路径严格区分:
Host Test
和:
Container Test
三十、坑二:Container 里没必要重新安装 Kernel Driver
另一个非常常见的误区是:
Container 看不到 GPU
↓
是不是应该 apt install NVIDIA Driver?
一般不是。
Container 不是 VM。
NVIDIA Kernel Driver 在:
Host Kernel
里运行。
Container 主要需要:
User-space CUDA Stack
+
Driver Interface
+
Device Nodes
所以通常不要在普通 CUDA / PyTorch Container 里面再尝试运行:
NVIDIA Kernel Driver Installer
否则很容易把:
Host Driver
和:
Container CUDA Runtime
两个层级搞混。
三十一、坑三:不要把 CUDA Version 那一行理解成"Host 装了这个 CUDA Toolkit"
nvidia-smi 顶部经常会显示:
CUDA Version: 12.x
这个数字特别容易误导。
它主要反映:
当前 NVIDIA Driver
最高支持到什么 CUDA Driver API / Compatibility Level
它并不简单等于:
Host 上安装的 nvcc / CUDA Toolkit 版本
可以分别看:
nvidia-smi
和:
nvcc --version
它们回答的是不同问题。
甚至很多 AI Container Host:
根本不需要安装完整 CUDA Toolkit
只需要:
NVIDIA Driver
+
NVIDIA Container Toolkit
而 CUDA Runtime、cuDNN、PyTorch 等全部存在 Container 中。
这其实是非常干净的一种部署方式。
三十二、坑四:nvidia-smi 需要的是 utility Capability
NVIDIA Container Runtime 不只是控制:
看哪块 GPU
还可以控制:
Driver Capability
例如:
compute
utility
graphics
video
display
其中 NVIDIA 官方定义:
compute
用于 CUDA / OpenCL。
而:
utility
则包含:
nvidia-smi
NVML
需要的能力。
所以理论上可能出现:
Device 在
但某种 Driver Capability 没暴露
从而造成某些工具行为不同。
普通 AI Compute 场景一般关注:
compute
utility
即可。
三十三、坑五:CDI 文件可能过期
CDI 又增加了一个新的排障层。
例如:
GPU / Driver
发生变化
但是:
CDI Spec
没有同步更新。
NVIDIA 当前 Toolkit 通过:
nvidia-cdi-refresh.path
nvidia-cdi-refresh.service
自动维护 CDI Spec。
可以检查:
systemctl status \
nvidia-cdi-refresh.path
以及:
systemctl status \
nvidia-cdi-refresh.service
如果怀疑 CDI Spec 没更新:
sudo systemctl restart \
nvidia-cdi-refresh.service
再看:
nvidia-ctk cdi list
NVIDIA 也提供:
nvidia-ctk --debug cdi list
帮助检查 CDI Spec 加载问题。
三十四、坑六:Docker 重启会影响正在运行的 Container
运行:
sudo nvidia-ctk runtime configure \
--runtime=docker
之后官方步骤需要:
sudo systemctl restart docker
如果这台机器上已经运行:
Database
Service
vLLM
Other Container
就不能把它当作完全无影响的命令。
个人实验机没什么问题。
生产服务器则一定要考虑:
Docker Daemon Restart
对现有 Workload 的影响。
三十五、到这里,终于能解释"Docker 为什么默认看不到 GPU"
现在重新回答开头的问题。
因为 GPU 并不是:
Container 的普通文件
而是:
Host Kernel
管理的一种 Hardware Device
Docker 默认通过隔离机制控制:
Container
能看到哪些 Device
所以:
Host GPU
不会自然变成:
Container GPU
中间必须有人完成:
GPU Resource Request
│
▼
Device Selection
│
▼
Device Node Injection
│
▼
Driver Library Injection
│
▼
Runtime Configuration
│
▼
Container
而 NVIDIA Container Toolkit 正是在做这件事情。
三十六、这一篇真正建立的不是一条 Docker 命令
如果这一篇只记住:
docker run --gpus all
其实还不够。
真正重要的是建立这样一个 Infra 视角:
┌────────────────────────────┐
│ AI Application │
│ vLLM / PyTorch │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ CUDA Runtime │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Container │
│ │
│ GPU is not automatic │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ NVIDIA Container Toolkit │
│ Runtime / CDI │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ /dev/nvidia* │
│ Driver Libraries │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Host NVIDIA Driver │
└─────────────┬──────────────┘
│
▼
GPU
以前我们的思考主要停留在:
Model
↓
Runtime
↓
GPU
从这一篇开始,需要再加一层:
Model
↓
AI Runtime
↓
Container
↓
Container Runtime
↓
Device Management
↓
Driver
↓
GPU
这才越来越接近:
AI Infrastructure
本身。
三十七、为什么 CDI 特别值得记住?
因为 CDI 背后体现的是一个更加普遍的 Infra 问题:
如何让 Container
使用特殊硬件?
今天是:
NVIDIA GPU
明天可能是:
AMD GPU
Intel GPU
FPGA
SmartNIC
DPU
AI Accelerator
如果每一种 Device 都设计:
一套 Runtime Hack
Container 生态会非常混乱。
CDI 尝试把问题变成:
Device Vendor
│
▼
CDI Specification
│
▼
Container Runtime
│
▼
Container
也就是:
Container Runtime 不需要理解每一种硬件,只需要理解统一的 Device Description。
这其实和很多 Infra 设计非常相似:
NIC
↓
标准 Network Interface
Storage
↓
Block / File Interface
Accelerator
↓
CDI
把硬件差异隐藏到统一接口下面。
三十八、这一篇之后,我们离 Kubernetes 已经很近了
现在假设不再只有一个 Docker Container。
而是有:
Node A
GPU 0
GPU 1
Node B
GPU 0
GPU 1
GPU 2
GPU 3
然后集群来了三个任务:
Pod A
需要 1 GPU
Pod B
需要 2 GPU
Pod C
需要 1 GPU
新的问题马上出现:
每台 Node 有多少 GPU?
Kubernetes 怎么知道?
Pod 怎么声明自己需要 GPU?
Scheduler 怎么选择 Node?
Container Runtime 最后又怎样
把那块 GPU 真正放进 Container?
这时:
docker run --gpus all
已经不够了。
因为现在需要的不只是:
Use GPU
而是:
Discover
Advertise
Schedule
Allocate
Inject
Monitor
完整的一套 GPU Resource Lifecycle。
于是很自然就会出现:
Kubernetes Device Plugin
NVIDIA GPU Operator
Container Runtime
CDI
这些新的组件。
三十九、总结
这一篇从一个看起来很简单的问题开始:
为什么:
Host
nvidia-smi 正常
但是:
Docker
默认看不到 GPU?
答案最终拆成了几层。
第一层:
Container
默认不会获得 Host 的所有 Device
GPU 首先是:
Linux Device
第二层:
GPU ≠ 一个 /dev/nvidia0 就结束
真正运行 CUDA 程序还涉及:
Device Nodes
Driver Libraries
Capabilities
Environment
Runtime Configuration
第三层:
NVIDIA Container Toolkit
负责把 NVIDIA GPU 能力正确注入 Container。
于是:
docker run --gpus all ...
才真正能够工作。
第四层:
Host Driver
和:
Container CUDA Runtime
是不同层级。
因此 AI Container 可以携带自己的:
PyTorch
CUDA Runtime
cuDNN
vLLM
同时共享宿主机:
NVIDIA Kernel Driver
第五层:
CDI
开始把:
GPU Injection
从 NVIDIA 专用机制抽象成:
Generic Container Device Interface
于是设备开始能够被描述成:
nvidia.com/gpu=0
nvidia.com/gpu=all
最终整个链路变成:
AI Application
│
▼
PyTorch / vLLM
│
▼
CUDA Runtime
│
▼
Container
│
▼
Container Runtime
│
▼
NVIDIA Container Toolkit / CDI
│
▼
Linux GPU Device
│
▼
NVIDIA Driver
│
▼
GPU
所以:
Docker 默认看不到 GPU,并不是 Docker"不支持 GPU",而是 Container 默认不会自动继承宿主机硬件。GPU 必须作为一种显式资源,被发现、描述、授权并注入 Container。
而这恰好是从:
AI Application
走向:
AI Infrastructure
非常关键的一步。
四十、下一篇为什么出现?
到这里,我们已经能运行:
docker run --gpus all ...
让:
PyTorch Container
使用 RTX 3060。
但是一个新的问题马上出现。
如果现在不是:
一个 Docker Container
而是:
几十台 GPU Server
+
几十块 GPU
+
几百个 Pod
谁来决定:
哪块 GPU 属于哪个 Pod?
Docker 可以告诉我们:
这个 Container 使用 GPU 0
但是 Docker 本身并不会解决:
整个 Cluster
应该怎样调度 GPU
的问题。
于是问题自然从:
How does a Container use a GPU?
继续变成:
How does a Cluster manage GPUs?
这就是下一篇真正要进入的世界:
Kubernetes
│
▼
GPU Resource
│
▼
Device Plugin
│
▼
Container Runtime
│
▼
GPU
以及一个在 AI Infra 里几乎绕不开的组件:
NVIDIA GPU Operator
所以接下来,我们就可以继续从一块已经能被 Docker 使用的 RTX 3060 开始,看看 GPU 怎样第一次真正成为:
Kubernetes Resource