上一篇,我们先画出了整个 AI Infra 实验室的地图。
我们知道,后面的路线大概会是:
GPU ↓ CUDA ↓ PyTorch ↓ 模型 ↓ vLLM ↓ Docker ↓ Kubernetes ↓ Model Serving同时,我们也把现在这台实验机当成了一台未来 AI Cluster 中的GPU Node,简单盘点了一遍:
Ubuntu 24.04 Server Intel Core i5-10400F 16GB RAM NVIDIA GeForce RTX 3060并且执行过:
lspci | grep -i nvidia nvidia-smi所以这一篇如果只是再执行一遍这两个命令,意义并不大。
上一篇我们做的是:
确认机器里确实有一张 GPU。
这一篇准备进一步回答:
这张 GPU 到底是什么?为什么它比 CPU 更适合 AI?我们从 Linux 里又能观察到它的哪些硬件特征?
换句话说:
上一篇:看见 GPU 这一篇:读懂 GPU 下一篇:真正使用 GPU这才是这一篇的任务。
一、CPU 已经这么强了,AI 为什么还需要 GPU?
先从最根本的问题开始。
我们的实验机已经有一颗:
Intel Core i5-10400F 6 Core / 12 ThreadCPU 本来就是计算机的中央处理器。
操作系统在 CPU 上跑,Python 在 CPU 上跑,数据库、浏览器、网络协议、Kubernetes,也都离不开 CPU。
既然 CPU 已经这么强了:
为什么今天做 AI,大家还要专门购买昂贵的 GPU?
甚至训练大型模型时,经常不是:
1 张 GPU而是:
几百张 几千张 甚至几万张 GPU原因并不是简单的一句:
GPU 比 CPU 快。
准确一点应该说:
深度学习中存在大量高度并行的数学计算,而 GPU 的架构恰好特别适合完成这种工作。
二、CPU 和 GPU,其实走的是两条完全不同的设计路线
先看一个非常简单的计算。
假设有两个数组:
A = [a1, a2, a3, ...] B = [b1, b2, b3, ...]现在计算:
C = A + B也就是:
c1 = a1 + b1 c2 = a2 + b2 c3 = a3 + b3 ...这里有一个很重要的特点:
计算 c1 不需要等待 c2 计算 c2 也不需要等待 c3这些计算之间基本独立。
因此理论上完全可以:
同时计算大量数据而神经网络中大量存在的矩阵乘法,本质上就是海量:
乘法 + 加法组合在一起。
例如:
Y = X × W这类工作负载天然就非常适合并行。
三、CPU 像几个高手,GPU 像一支大规模流水线
可以把 CPU 想象成:
几个能力很强的高级工程师。
一个现代 CPU Core 非常复杂。
它需要处理:
复杂控制逻辑 条件分支 中断 操作系统 各种不同程序 乱序执行 分支预测 Cache它更关心的是:
怎样尽快完成一个复杂任务。
所以 CPU 通常是:
核心数量不多 但每个核心都非常强例如我们的:
i5-10400F只有:
6 Core / 12 Thread但是每个 Core 都是非常复杂的通用处理器核心。
GPU 的设计目标则不同。
GPU 最开始就是为了处理图形。
一张屏幕可能有几百万个像素,而大量像素都需要进行类似计算。
所以 GPU 更关心的是:
怎样同时处理数量巨大的相似任务。
可以粗略理解成:
CPU 少量复杂核心 ↓ 复杂控制 低延迟 通用任务而:
GPU 大量并行计算单元 ↓ 重复计算 高吞吐 矩阵 / 向量运算所以:
| CPU | GPU |
|---|---|
| 少量复杂核心 | 大量并行执行单元 |
| 强调单线程性能 | 强调总体吞吐 |
| 擅长复杂控制 | 擅长重复计算 |
| 擅长串行和分支 | 擅长高度并行任务 |
| 操作系统、业务逻辑 | 图形、科学计算、AI |
这里最重要的一句话是:
不是 GPU 在任何场景都比 CPU 快,而是 AI 中大量数学计算恰好非常符合 GPU 的设计方式。
这才是 AI 时代计算资源从“主要关注 CPU”逐渐走向“高度关注 GPU”的根本原因。
四、打开 GPU:里面到底是什么?
知道 GPU 为什么适合 AI 后,我们再稍微把 GPU 打开一点。
暂时不需要研究晶体管级别的微架构。
对于后面的 AI Infra 学习来说,先认识下面几个东西就够了:
GPU │ ├── SM │ │ │ ├── CUDA Core │ ├── Tensor Core │ ├── Warp Scheduler │ ├── Register │ └── Shared Memory │ ├── SM │ ├── SM │ └── ... + VRAM而放到整台电脑中,大致是:
CPU │ │ System Memory RAM │ │ PCIe │ ▼ ┌────────────────────┐ │ GPU │ │ │ │ ┌────┐ ┌────┐ │ │ │ SM │ │ SM │ │ │ └────┘ └────┘ │ │ ┌────┐ ┌────┐ │ │ │ SM │ │ SM │ │ │ └────┘ └────┘ │ │ │ │ VRAM │ └────────────────────┘以后研究:
CUDA PyTorch vLLM GPU Scheduling GPU Observability其实就是不断往这张图里面增加细节。
五、SM:GPU 真正的“计算车间”
首先是一个以后会经常看到的缩写:
SM全称:
Streaming Multiprocessor也就是流式多处理器。
现代 NVIDIA GPU 并不是:
几千个 CUDA Core 直接平铺在芯片上而是先组成很多:
SM每一个 SM 可以理解成一个计算车间。
里面有:
CUDA Core Tensor Core 寄存器 Shared Memory Warp Scheduler 其他执行单元CUDA 程序产生大量 GPU Thread 后,这些线程会被安排到不同的 SM 中运行。
所以以后进行 GPU 性能分析,经常会看到:
SM Utilization SM Occupancy SM Clock这些指标。
它们实际上都在回答类似的问题:
GPU 里面这些计算车间,到底有没有充分忙起来?
六、CUDA Core:千万别和 CPU Core 直接比较
RTX 3060 有几千个 CUDA Core。
而我们的 i5-10400F:
6 CPU Core第一次看到时很容易产生一种直觉:
GPU 几千个 Core CPU 只有 6 个 Core 那 GPU 岂不是快几百倍?这个比较是不成立的。
因为:
CUDA Core 和 CPU Core 根本不是同一个级别的东西。
一个 CPU Core 是一个相对完整、非常复杂的通用处理器核心。
而 CUDA Core 更接近:
SM 内部负责执行大量数值运算的计算单元。
所以不能理解成:
1 CUDA Core = 1 个小号 CPU更合理的理解还是:
CPU 少量非常复杂的通用核心 GPU 大量用于并行数值计算的执行单元这也是为什么看到:
3584 CUDA Cores这样的规格时,不能直接拿它去除以:
6 CPU Cores得出一个“快多少倍”的结论。
七、Tensor Core:GPU 为什么越来越像 AI 专用计算机
如果 CUDA Core 已经能够进行大量并行计算:
NVIDIA 为什么后来还要增加 Tensor Core?
原因还是 AI 的计算特点。
神经网络里面反复出现:
矩阵 × 矩阵以及:
乘法 + 累加比如:
D = A × B + C既然这种计算如此重要,就可以直接设计专门的硬件单元去加速。
于是出现了:
Tensor Core可以先粗略理解:
CUDA Core ↓ 更通用的 GPU 数值计算而:
Tensor Core ↓ 更专门针对矩阵计算 ↓ AI Training / Inference所以到了 AI 时代,评价 GPU 已经不能简单只看:
CUDA Core 数量还需要关注:
Tensor Core 支持什么数据精度 显存容量 显存带宽 GPU 架构以后我们还会遇到:
FP32 TF32 FP16 BF16 INT8 INT4到那时就会进一步发现:
很多所谓的 AI GPU 性能优化,本质上就是让特定类型的数据更高效地进入这些矩阵计算单元。
这一篇先建立概念就够了。
八、Warp:几千个线程到底怎么运行?
GPU 可以同时处理大量线程。
但 GPU 并不是一个线程一个线程单独调度。
在 NVIDIA GPU 中,一个非常重要的执行概念叫:
Warp一个 Warp:
32 Threads可以简单画成:
GPU │ └── SM │ ├── Warp │ ├ Thread │ ├ Thread │ ├ ... │ └ Thread × 32 │ ├── Warp ├── Warp └── ...这里暂时不研究:
Block Grid Warp Divergence Occupancy但先记住:
GPU 强大的并行能力,不是简单的“几千个核心一起工作”,而是一整套 Thread、Warp、SM 的并行执行体系。
后面真正写 CUDA 程序时,我们再展开。
九、VRAM:为什么 AI 玩家总是在问显存?
GPU 除了计算单元,还有一个极其重要的资源:
VRAM也就是:
显存。
在游戏领域,大家也关注显存。
但是到了 AI 领域,显存的重要程度进一步提高。
因为运行模型首先遇到的问题并不是:
GPU 算得够不够快?
而经常是:
模型到底装不装得进去?
可以非常粗略地理解:
SSD ↓ System RAM ↓ PCIe ↓ VRAM ↓ SM ↓ CUDA Core / Tensor Core模型运行过程中,显存里不仅可能需要保存:
模型权重还有:
Activation KV Cache Runtime Buffer等等。
例如一个:
7B模型大约有:
70 亿参数如果简单假设每个参数使用 FP16:
7B × 2 Byte ≈ 14GB仅仅权重就已经大约 14GB。
如果量化到 4 bit:
7B × 0.5 Byte ≈ 3.5GB才会明显降低。
实际运行还有额外开销,所以不能只靠这个简单公式判断是否能运行。
但它已经非常直观地解释了两个问题:
为什么大模型这么关心 VRAM?
以及:
为什么 Quantization,也就是量化,会成为本地大模型特别重要的技术?
以后运行真正的模型时,我们会亲眼看到显存怎么变化。
十、PCIe:CPU 和 GPU 中间还有一条高速公路
GPU 并不是一台完全独立的计算机。
CPU、系统内存和 GPU 还需要不断交换数据。
普通 PC 中,它们之间很重要的一条通道就是:
PCI Express也就是 PCIe。
可以画成:
CPU / RAM │ │ PCIe │ ▼ GPU / VRAM这就引出了 AI Infra 中另一个重要认识:
GPU 算得快,并不代表整个程序一定快。
例如:
CPU 准备数据 ↓ 通过 PCIe ↓ 复制到 VRAM ↓ GPU 开始计算如果数据准备或者数据传输成为瓶颈:
GPU完全可能在那里等数据。
所以以后观察 AI 性能时,不能只盯着:
GPU Utilization还可能涉及:
CPU RAM VRAM PCIe 数据加载这就是 Infra 视角和单纯“会调用 GPU”之间很重要的区别。
十一、Compute Capability:GPU 的“硬件能力版本号”
还有一个以后安装 CUDA 软件时经常出现的概念:
Compute Capability例如:
7.5 8.0 8.6 8.9 9.0它并不是简单表示:
GPU 性能等级而更接近:
这一代 NVIDIA GPU 支持哪些 CUDA 硬件能力。
例如我们的 RTX 3060:
Compute Capability 8.6以后编译 CUDA 程序,还经常会看到:
sm_86这里的:
86就和:
Compute Capability 8.6对应。
所以以后碰到:
这个 CUDA 程序支持 sm_80 但不支持 sm_86或者:
这个软件包没有为你的 GPU Architecture 编译就知道问题大概发生在哪一层了。
十二、实验:上一篇已经看见 GPU,这次我们换个观察目标
现在回到实验机。
上一篇已经执行过:
lspci | grep -i nvidia以及:
nvidia-smi当时它们的意义主要是:
GPU Node 盘点 + 记录初始 Baseline这次不再满足于一句:
能看到 RTX 3060,说明正常。
而是利用相同的工具继续往下看。
十三、深化实验一:lspci 不只是用来确认“有没有显卡”
首先:
lspci -nn | grep -i nvidia这次除了显卡名字,还关注最左边的:
PCI Address例如可能看到:
01:00.0 01:00.1这里首先就可能产生一个疑问:
我明明只有一张 RTX 3060,为什么出现两个 NVIDIA 设备?
通常一个是:
VGA / 3D Controller另一个可能是:
Audio Device因为 HDMI / DisplayPort 不仅可以传输图像,也能够传输音频。
所以:
两条 NVIDIA PCI 设备并不意味着:
两张 GPU再执行:
lspci -nnk | grep -A 3 -i nvidia这次关注:
Kernel driver in use Kernel modules如果 NVIDIA Driver 工作正常,可以继续把:
PCI Hardware和:
Linux Driver对应起来。
于是我们看到的已经不是简单一句:
Linux 看到了 RTX 3060而是一条更完整的关系:
PCIe Device ↓ Linux PCI Subsystem ↓ NVIDIA Kernel Driver ↓ GPU这就是第一次深化。
十四、深化实验二:看看 PCIe 这条“高速公路”
前面说过,CPU / RAM 和 GPU 之间主要通过 PCIe 连接:
CPU / RAM │ PCIe │ ▼ GPU / VRAM上一篇我们用lspci只是确认 Linux 能看到 RTX 3060。
这一次继续看它实际跑在什么 PCIe 状态。
先找到显卡地址:
lspci | grep -i nvidia我的 RTX 3060 是:
01:00.0然后执行:
sudo lspci -s 01:00.0 -vv | grep -E 'LnkCap|LnkSta'实际输出:
LnkCap: Speed 8GT/s, Width x16 LnkSta: Speed 2.5GT/s (downgraded), Width x16 LnkCap2: Supported Link Speeds: 2.5-16GT/s这里需要知道 PCIe 速率和代际的大致对应关系:
| PCIe | 速率 |
|---|---|
| Gen1 | 2.5 GT/s |
| Gen2 | 5 GT/s |
| Gen3 | 8 GT/s |
| Gen4 | 16 GT/s |
这样就比较容易看懂了。
LnkCap2最高支持16GT/s,说明RTX 3060 本身支持 PCIe Gen4。
但是:
LnkCap: Speed 8GT/s, Width x16说明在当前这台机器上,这条链路最高是:
PCIe Gen3 x16原因也很简单:这台实验机使用 i5-10400F 平台,主机侧最高是 PCIe Gen3,所以即使 RTX 3060 支持 Gen4,最终也只能按照双方共同支持的 Gen3 工作。
而当前状态:
LnkSta: Speed 2.5GT/s, Width x16对应的是:
PCIe Gen1 x16这并不代表显卡出了问题。
GPU 空闲时,为了降低功耗,PCIe 链路可以自动降低速度;真正有负载时再提升。
所以这次实验刚好能看出三个不同概念:
RTX 3060 自身能力:PCIe Gen4 ↓ 当前平台最高:PCIe Gen3 x16 ↓ 当前空闲状态:PCIe Gen1 x16这里最值得记住的是:
硬件支持什么、当前平台最多能跑什么、此刻实际运行在什么状态,是三件不同的事情。
以后分析 GPU 性能时,PCIe 也可能成为 CPU / RAM 和 GPU 之间的数据传输瓶颈,但目前我们的单卡实验室先知道它的位置和含义就够了。
十五、深化实验三:不要再只看 nvidia-smi 那张大表
上一篇我们第一次运行:
nvidia-smi主要关注:
GPU Name Driver Version Memory Usage GPU Utilization Temperature Power这次我们进一步把需要的数据主动查询出来。
先看看当前驱动支持哪些查询字段:
nvidia-smi --help-query-gpu甚至可以:
nvidia-smi --help-query-gpu | less然后查询几个最关键的数据,例如:
nvidia-smi \ --query-gpu=name,compute_cap,memory.total,memory.used,utilization.gpu,pstate,temperature.gpu,power.draw,pci.bus_id \ --format=csv这样一行数据就开始和我们这一篇讲过的架构对应起来了。
例如:
name告诉我们:
GPU 是谁compute_cap告诉我们:
它属于什么 CUDA Hardware Capabilitymemory.total memory.used对应:
VRAMutilization.gpu开始反映:
GPU 当前有没有计算任务pstate则让我们第一次看到 GPU 的:
Performance Statepci.bus_id又可以和:
lspci看到的 PCI 地址联系起来。
到这里,两个原本看起来毫不相关的 Linux 命令:
lspci和:
nvidia-smi其实已经被串起来了。
十六、这一次为什么还要记录 Baseline?
上一篇其实已经说过:
GPU Utilization 0% Memory Used xxx MiB可以作为:
初始 GPU Baseline这一篇我们可以把这个思想再往前推进一步。
现在机器基本处于空闲状态。
记录:
| 指标 | Idle Baseline |
|---|---|
| GPU Util | 实测 |
| VRAM Used | 实测 |
| Temperature | 实测 |
| Power Draw | 实测 |
| P-State | 实测 |
| PCIe Link | 实测 |
现在这张表可能看起来没什么特别。
真正有意思的是后面。
当我们开始运行:
PyTorch Matrix Multiplication再运行:
LLM然后:
vLLM我们可以重新观察:
GPU Util VRAM Temperature Power Clock P-State会发生什么变化。
于是实验就变成:
Idle ↓ CUDA Workload ↓ PyTorch ↓ LLM ↓ vLLM每往上增加一层 workload,都回来观察一次 GPU。
这时候今天记录的 Baseline 才真正产生价值。
所以:
上一篇的 Baseline 是“资源盘点”;这一篇开始把它变成后续性能实验的对照组。
这也是为什么同一个nvidia-smi,仍然值得继续使用。
观察目标已经不同了。
十七、有些 GPU 信息为什么 nvidia-smi 里看不到?
这里还有一个值得注意的问题。
这一篇我们讲了:
SM CUDA Core Tensor Core Warp但是打开:
nvidia-smi却不会直接出现:
SM: xx CUDA Core: xxxx Tensor Core: xxx为什么?
因为:
SM 数量 CUDA Core 数量 Tensor Core 数量主要属于 GPU 芯片本身的:
静态架构规格。
而nvidia-smi更偏向:
GPU 当前运行状态和 Driver/NVML 暴露出来的管理、监控信息。
例如:
温度 功耗 显存 利用率 进程 Clock Performance State因此以后分析 GPU,要逐渐养成一个习惯:
GPU Architecture Spec + Runtime Telemetry结合起来看。
也就是:
这块 GPU 理论上有什么和:
这块 GPU 现在在干什么其实是两个不同的问题。
这已经开始有一点 GPU Observability 的味道了。
十八、几个这一篇特别值得记住的坑
1. CUDA Core 不是 CPU Core
不能这样计算:
3584 CUDA Core ÷ 6 CPU Core = GPU 快 597 倍两种 Core 根本不是同一种东西。
2. GPU Utilization 低,不等于 GPU 太慢
可能是:
CPU 喂数据慢 PCIe 搬数据 Batch 太小 模型太小 同步等待 程序没有充分并行以后做性能实验时,要看整个数据路径。
3. PCIe 当前速度低,不一定有问题
要区分:
LnkCap和:
LnkSta空闲状态下 GPU 可能主动降低 Link Speed 节能。
4. 一张显卡可能出现多个 PCI Function
例如:
GPU + HDMI Audio不要看到两个 NVIDIA PCI Device 就认为机器里有两张 GPU。
5. nvidia-smi 的 CUDA Version 依然不要理解成 Toolkit Version
这个问题上一篇已经碰到过,这里不再展开。
先继续记住:
nvidia-smi 看到 CUDA Version并不等价于:
nvcc 一定已经安装因为这正好就是下一篇要解决的问题。
十九、现在重新看这张 GPU 架构图
做完实验以后,再来看这张图:
Intel i5-10400F │ │ RAM │ │ PCIe ←──── lspci │ ▼ ┌───────────────────┐ │ RTX 3060 │ ←── nvidia-smi │ │ │ VRAM │ │ │ │ ┌───────────┐ │ │ │ SM │ │ │ │ │ │ │ │ CUDA Core │ │ │ │Tensor Core│ │ │ └───────────┘ │ │ │ │ ┌───────────┐ │ │ │ SM │ │ │ └───────────┘ │ │ ... │ └───────────────────┘现在里面的每一个词,都已经开始有具体意义:
| 名词 | 暂时怎么理解 |
|---|---|
| CPU | 通用计算和复杂控制 |
| GPU | 大规模并行计算 |
| SM | GPU 内部主要计算与调度单元 |
| CUDA Core | 通用 GPU 数值计算执行单元 |
| Tensor Core | 矩阵和 AI 计算加速单元 |
| Warp | 32 个 Thread 的执行/调度单位 |
| VRAM | GPU 自己的高速显存 |
| PCIe | CPU/RAM 与 GPU 的数据通道 |
| Compute Capability | CUDA 硬件能力版本 |
这基本就是后面整个 GPU 学习路线的第一张地图。
二十、这一篇我们真正解决了什么问题?
上一篇我们只是知道:
我的机器里 有一张 RTX 3060这一篇则开始知道:
为什么 AI 需要 GPU ↓ CPU 和 GPU 架构为什么不同 ↓ GPU 内部为什么有很多 SM ↓ CUDA Core 在哪里 ↓ Tensor Core 为什么为 AI 而生 ↓ 模型为什么受 VRAM 限制 ↓ CPU 和 GPU 为什么还受到 PCIe 影响 ↓ Compute Capability 又决定什么同时,我们把上一篇的两个简单命令:
lspci nvidia-smi继续向下挖了一层。
从:
有没有 GPU?变成:
GPU 挂在哪? Driver 是谁? PCIe 怎么连接? Compute Capability 是多少? 显存多大? 空闲功耗是多少? P-State 是什么? GPU 当前到底有没有忙?这就是这一次实验真正新增的意义。
二十一、下一篇为什么自然会出现 CUDA?
现在硬件这一层已经逐渐清楚了:
CPU ↓ PCIe ↓ GPU ↓ SM ↓ CUDA Core / Tensor Core但仍然有一个最关键的问题没有解决:
软件到底怎么使用这些硬件?
Python 不可能直接告诉某一个 Tensor Core:
帮我算这个矩阵。中间一定还有软件层。
而上一篇和这一篇,我们其实已经不断看到几个名字:
NVIDIA Driver CUDA CUDA Version Compute Capability接下来还会出现:
CUDA Toolkit CUDA Runtime nvcc cuBLAS cuDNN PyTorch它们到底是什么关系?
为什么:
nvidia-smi明明可以正常运行,
而:
nvcc --version却可能提示:
command not found为什么 PyTorch 有时甚至不需要系统安装完整 CUDA Toolkit,也能使用 GPU?
为什么安装 CUDA 最容易出现的反而是:
Driver Version CUDA Version PyTorch Version之间的各种兼容问题?
所以认识完 GPU 之后,下一步正好就是把:
GPU 和 AI 软件之间的桥梁
真正搭起来。
下一篇:
《从零搭建一个 AI Infra 实验室③:CUDA 到底是什么——拆开 NVIDIA 的软件栈》
到那一篇,我们就不再只是:
观察 RTX 3060而会第一次让它真正开始:
执行计算