CUDA 生态与框架:从 cuBLAS 到多 GPU 训练


Q1:cuBLAS、cuDNN、NCCL 分别是什么?各自用途?

CUDA 生态全景

NVIDIA 构建了一个以 CUDA Runtime 为基础的完整软件栈:

1
2
3
4
5
6
7
8
9
应用层(PyTorch / TensorFlow / JAX)

框架加速库(cuDNN / cuBLAS / cuSPARSE / cuFFT)

通信库(NCCL / NVSHMEM)

CUDA Runtime & Driver

GPU 硬件(SM / HBM / NVLink / NVSwitch)

cuBLAS:GPU 上的线性代数库

cuBLAS(CUDA Basic Linear Algebra Subroutines)是 NVIDIA 官方实现的 BLAS 标准接口,专为 GPU 加速的密集矩阵运算而设计。

核心能力

Level 操作类型 典型函数 说明
BLAS-1 向量操作 cublasSaxpy, cublasSnrm2 向量加法、范数
BLAS-2 矩阵-向量 cublasSgemv 矩阵乘向量
BLAS-3 矩阵-矩阵 cublasSgemm, cublasSgemmBatched 矩阵乘矩阵(最常用)

最重要的接口 —— cublasGemmEx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <cublas_v2.h>

cublasHandle_t handle;
cublasCreate(&handle);

// 支持混合精度:FP16 输入 → FP32 累加 → FP16 输出
cublasGemmEx(
handle,
CUBLAS_OP_N, CUBLAS_OP_N, // A、B 是否转置
M, N, K, // 矩阵维度
&alpha, // 缩放因子
d_A, CUDA_R_16F, M, // A 矩阵:FP16,leading dimension=M
d_B, CUDA_R_16F, K, // B 矩阵:FP16
&beta,
d_C, CUDA_R_32F, M, // C 矩阵:FP32 输出
CUBLAS_COMPUTE_32F, // 计算精度:FP32 累加
CUBLAS_GEMM_DEFAULT_TENSOR_OP // 使用 Tensor Core
);

批量 GEMM(多个小矩阵同时计算,适合 Transformer 的 Multi-Head Attention):

1
2
3
4
5
6
7
8
9
10
11
// 同时计算 batch_count 个独立的 GEMM
cublasSgemmBatched(handle, ..., batch_count,
(const float**)d_A_array, // 指向每个矩阵的指针数组
(const float**)d_B_array,
(float**)d_C_array);

// 或使用步长版本(连续内存布局)
cublasSgemmStridedBatched(handle, ..., batch_count,
d_A, strideA, // 相邻两个矩阵间的步长(元素数)
d_B, strideB,
d_C, strideC);

使用建议

  • cuBLAS 是 PyTorch torch.matmulnn.Linear 的底层实现
  • 始终使用 cublasGemmEx 并指定 CUBLAS_GEMM_DEFAULT_TENSOR_OP 以启用 Tensor Core
  • 对小矩阵(M/N/K < 64),批量 GEMM 的性能远好于逐个调用
  • cuBLAS 内部管理工作空间,初始化句柄(cublasCreate)代价较高,应复用

cuDNN:深度学习基础算子库

cuDNN(CUDA Deep Neural Network library)提供深度学习中最常见操作的 GPU 高度优化实现,是所有主流框架的底层引擎。

核心操作

操作类别 典型算子 说明
卷积 cudnnConvolutionForward 支持 NCHW/NHWC,自动选择最优算法
归一化 BatchNorm, LayerNorm, GroupNorm 前向 + 反向
激活函数 ReLU, GeLU, Sigmoid, Tanh 融合到卷积/矩阵乘中
池化 MaxPool, AvgPool, GlobalAvgPool
注意力 Flash Attention(cuDNN 9+) 高效 Transformer 注意力
RNN/LSTM cudnnRNNForward 序列模型支持
Softmax cudnnSoftmaxForward 多种稳定算法

cuDNN 的关键设计:算法选择

cuDNN 卷积提供多种实现算法(Implicit GEMM、Winograd、FFT 等),应用可通过 Benchmark 模式 自动找最优算法:

1
2
3
4
// PyTorch 中的等价操作(自动 Benchmark)
import torch
torch.backends.cudnn.benchmark = True # 启用自动算法搜索(首次慢,后续快)
torch.backends.cudnn.deterministic = False # 允许非确定性,换取最高性能

cuDNN Graph API(cuDNN 8+)允许将多个算子融合为一个图,减少中间结果写回显存:

1
2
GEMM → BiasAdd → GeLU → Dropout
整个序列融合为一个 Kernel,省去 3 次全局内存写入

NCCL:多 GPU 集合通信库

NCCL(NVIDIA Collective Communications Library)专为 GPU 集群上的集合通信(Collective Operations)设计,是分布式深度学习训练的核心通信基础。

支持的集合操作

操作 说明 典型用途
ncclAllReduce 所有 GPU 的数据归约后广播到所有 GPU 数据并行梯度同步
ncclBroadcast 一个 GPU 的数据广播到所有 GPU 模型参数初始化
ncclReduce 归约到指定 GPU Pipeline 并行
ncclAllGather 收集所有 GPU 数据到每个 GPU ZeRO 优化器、TP
ncclReduceScatter 归约并分片到各 GPU ZeRO Stage-2/3
ncclSend/ncclRecv 点对点通信 Pipeline 并行消息传递

自动拓扑感知:NCCL 会探测 GPU 间的物理连接(NVLink / PCIe / IB),自动选择最优通信算法和路由,无需手动配置。

与 PyTorch DDP 的关系

1
2
3
4
5
# PyTorch DDP 底层就是调用 NCCL 的 AllReduce
import torch.distributed as dist
dist.init_process_group("nccl") # 使用 NCCL 后端

model = DDP(model) # 前向完成后自动触发 AllReduce 同步梯度

三者定位对比

定位 操作类型 使用者
cuBLAS 稠密线性代数 GEMM、向量运算 框架核心、需要手写算子的工程师
cuDNN 深度学习算子 卷积、归一化、注意力 框架(PyTorch/TF),基本透明
NCCL 多 GPU 集合通信 AllReduce、AllGather 等 分布式训练框架、通信感知代码

Q2:TensorRT 的优化原理是什么?

TensorRT 是什么

TensorRT 是 NVIDIA 的推理优化引擎,将训练好的模型(来自 PyTorch/ONNX/TensorFlow)转换为高度优化的推理引擎(Engine),专注于最大化推理吞吐和最小化延迟。

1
2
3
4
5
训练模型(PyTorch .pt / ONNX .onnx)
↓ TensorRT Builder
优化后的 Engine(.plan / .trt)
↓ TensorRT Runtime
高效推理(最低延迟,最高吞吐)

优化技术一:层融合(Layer Fusion)

TensorRT 分析计算图,将可以合并执行的连续算子融合为单一 Kernel,消除中间结果的全局内存读写。

水平融合(Horizontal Fusion):将多个相同类型的并行算子合并:

1
2
3
Conv1 ──┐
Conv2 ──┤── 三个卷积并行 → 融合为一个宽 Kernel
Conv3 ──┘

垂直融合(Vertical Fusion):将连续依赖的算子合并:

1
2
Conv ──→ BatchNorm ──→ ReLU
三个 Kernel → 融合为 CBR(一个 Kernel)

常见融合模式:

  • Conv + BN + ReLU(CBR)
  • GEMM + Bias + Activation
  • LayerNorm(多个 elementwise op 合并)
  • MultiHead Attention(GEMM + Softmax + GEMM 一体化)

优化技术二:精度校准(Quantization/Calibration)

TensorRT 支持将 FP32 模型量化为 FP16 或 INT8,在精度损失极小的前提下大幅提升性能:

精度 相对 FP32 速度 模型大小 精度损失
FP32 1x 100%
FP16 2x ~ 4x 50% 极微小
INT8 4x ~ 8x 25% 需要校准
INT4 8x+ 12.5% 需谨慎

INT8 校准:需要一批代表性数据(Calibration Dataset),TensorRT 通过测量各层的激活值分布,找到最优的量化比例,使量化误差最小:

1
2
3
4
5
6
7
8
9
10
import tensorrt as trt

# 创建校准器
class MyCalibrator(trt.IInt8MinMaxCalibrator):
def get_batch(self, names):
# 返回校准数据
return [d_input_calibration]

builder.int8_mode = True
builder.int8_calibrator = MyCalibrator(calibration_data)

优化技术三:Kernel 自动调优(Auto-Tuning)

TensorRT 在构建 Engine 时会对关键 Kernel(特别是卷积和 GEMM)运行大量候选算法,选择在当前 GPU 上最快的那个。这个过程类似 torch.backends.cudnn.benchmark = True,但更彻底——TensorRT 还会考虑 Tensor Core 对齐、内存布局、分块大小等因素。

这就是为什么 TensorRT 构建 Engine 很慢(可能需要几分钟),但推理很快。

优化技术四:动态形状与多优化配置

现代推理服务的 Batch Size 和序列长度往往是动态的。TensorRT 支持为每个可变维度指定范围,构建支持动态 Shape 的 Engine:

1
2
3
4
5
6
7
profile = builder.create_optimization_profile()
# 为 input 张量指定:最小/最优/最大 Shape
profile.set_shape("input",
min=(1, 3, 224, 224),
opt=(8, 3, 224, 224), # 最优 batch size,TRT 围绕此调优
max=(32, 3, 224, 224))
config.add_optimization_profile(profile)

优化技术五:内存复用(Memory Pooling)

TensorRT 分析整个计算图的张量生命周期,让不同层的中间结果复用同一块内存,显著减少推理所需的显存总量。这对于 LLM 推理(KV Cache 管理)尤为重要。

完整工作流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import tensorrt as trt

# ① 解析 ONNX 模型
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
network = builder.create_network(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
parser = trt.OnnxParser(network, logger)
parser.parse_from_file("model.onnx")

# ② 配置优化选项
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB 工作空间
config.set_flag(trt.BuilderFlag.FP16) # 启用 FP16

# ③ 构建 Engine(慢,一次性)
serialized_engine = builder.build_serialized_network(network, config)
with open("model.plan", "wb") as f:
f.write(serialized_engine)

# ④ 推理(快,反复使用)
runtime = trt.Runtime(logger)
engine = runtime.deserialize_cuda_engine(serialized_engine)
context = engine.create_execution_context()
context.execute_async_v2(bindings=[d_input, d_output], stream_handle=stream)

TensorRT 与 torch.compile 的关系

PyTorch 2.x 的 torch.compile 使用 Inductor 后端,也能做类似的算子融合和代码生成。但 TensorRT 在 NVIDIA GPU 上的优化更彻底(利用了更多专有指令集知识),适合生产推理服务。

torch_tensorrt(原 TRTorch)可以直接将 TorchScript/FX Graph 送入 TensorRT 编译,兼顾灵活性和性能。


Q3:什么是 Triton(OpenAI)?与手写 CUDA Kernel 相比有何异同?

Triton 是什么

Triton 是 OpenAI 开源的 GPU 编程语言和编译器,目标是让开发者能用类 Python 的语法写出接近手写 CUDA 的高性能 Kernel,大幅降低高性能 Kernel 的开发门槛。

Triton 的核心理念:以 Block 为单位编程,让编译器处理线程级并行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Triton 版向量加法(对比 CUDA 版)
import triton
import triton.language as tl

@triton.jit
def add_kernel(x_ptr, y_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(axis=0) # "Block" 的 ID,类似 blockIdx.x
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE) # 当前 Block 负责的下标范围

mask = offsets < n_elements # 边界掩码
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
tl.store(output_ptr + offsets, x + y, mask=mask)

# 启动
grid = lambda meta: (triton.cdiv(n, meta['BLOCK_SIZE']),)
add_kernel[grid](x, y, output, n, BLOCK_SIZE=1024)

Triton vs CUDA 手写:编程模型对比

维度 Triton 手写 CUDA
编程粒度 Block(程序实例) Thread(单个线程)
线程管理 编译器自动处理 手动(threadIdx, blockIdx)
共享内存 编译器自动分配和优化 手动声明 __shared__
Bank Conflict 编译器自动避免 手动 padding 处理
内存合并 自动(Block 级批量加载) 手动设计访问模式
Tensor Core 通过 tl.dot 自动调用 需手写 WMMA 或调 cuBLAS
开发难度 低(Python 语法) 高(C++,大量细节)
灵活性 中等(受 Block 抽象限制) 极高(完全控制)
性能上限 通常达到手写的 80% ~ 100% 理论 100%
调试便利性 较好(Python 生态) 较难(需要专用工具)

Triton 的编译流程

1
2
3
4
5
6
7
8
9
Triton Python 代码
↓ Triton 前端
Triton IR(中间表示)
↓ 优化 Pass(循环展开、向量化、共享内存分配)
LLVM IR(GPU 后端)
↓ NVIDIA LLVM GPU 后端
PTX(NVIDIA 汇编)
↓ ptxas
SASS(GPU 机器码)

Triton 编译器在 Block IR 层面做优化,自动完成很多 CUDA 手写中需要手工处理的工作:

  • 自动推断哪些数据需要缓存在共享内存
  • 自动做循环展开和向量化
  • 自动安排共享内存布局以避免 Bank Conflict

Triton 实现 Flash Attention 风格的 GEMM

Triton 最广为人知的是 Flash Attention 的 Triton 实现(已集成进 torch.nn.functional.scaled_dot_product_attention):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@triton.jit
def matmul_kernel(A, B, C, M, N, K,
stride_am, stride_ak, stride_bk, stride_bn,
stride_cm, stride_cn,
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr):
# 每个 "程序实例" 负责计算 C 的一个 [BLOCK_M, BLOCK_N] 分块
pid_m = tl.program_id(0)
pid_n = tl.program_id(1)

# 初始化累加器(保持在寄存器中)
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)

# 沿 K 维度循环(Tiling)
for k in range(0, K, BLOCK_K):
# tl.load 自动生成合并内存访问指令
a = tl.load(A + ..., mask=...)
b = tl.load(B + ..., mask=...)
# tl.dot 自动映射到 Tensor Core 的 MMA 指令
acc = tl.dot(a, b, acc)

tl.store(C + ..., acc, mask=...)

tl.dot 会自动调用 Tensor Core(当数据类型和维度满足条件时),无需手写 WMMA 或调用 cuBLAS。

Triton 的优势场景

  1. 自定义融合算子:标准库不支持的 Fused 操作(如 GEMM + 自定义 Activation + Quantization)
  2. 快速原型验证:用 1/10 的代码量验证新算子的性能思路
  3. AI 编译器后端torch.compile 的 Inductor 后端默认使用 Triton 生成 GPU Kernel
  4. 跨平台:Triton 也支持 AMD ROCm(无需修改代码),手写 CUDA 不行

手写 CUDA 的优势场景

  1. 极致性能调优:需要精细控制 Warp 调度、Bank Conflict 的特定 padding、异步内存拷贝流水线时
  2. 特殊硬件功能:使用 __shfl_sync__ballot_synccuda::pipeline、TMA 等 Triton 还未完全支持的特性
  3. 非标准访问模式:Triton 的 Block 抽象在某些复杂 Sparse 操作中不够灵活
  4. 生产库代码:cuBLAS、cuDNN、CUTLASS 等都是手写 CUDA,性能由厂商保证

实际选择建议

1
2
3
4
5
6
7
需求分析

已有 cuBLAS/cuDNN? → 是 → 直接用,性能最好
↓ 否
能用 Triton 表达? → 是 → 用 Triton,开发效率高
↓ 否
复杂访问模式/极致性能 → 手写 CUDA(或 CUTLASS)

Q4:多 GPU 训练中,如何使用 NCCL 进行 AllReduce 通信?

为什么需要 AllReduce?

数据并行训练的标准流程:

1
2
3
4
5
6
7
8
GPU 0: forward(batch_0) → loss_0 → backward → grad_0
GPU 1: forward(batch_1) → loss_1 → backward → grad_1
GPU 2: forward(batch_2) → loss_2 → backward → grad_2
GPU 3: forward(batch_3) → loss_3 → backward → grad_3

AllReduce: 所有 GPU 上的 grad 求和/平均,每个 GPU 都得到相同的全局梯度

GPU 0, 1, 2, 3 各自用全局梯度更新参数 → 下一步所有 GPU 参数一致

AllReduce 是每个训练步骤必须执行一次的操作,其延迟直接影响训练速度。

NCCL 初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <nccl.h>

int num_gpus = 4;
int devs[4] = {0, 1, 2, 3};

ncclComm_t comms[4]; // 每个 GPU 一个通信子

// 方法一:在同一进程内初始化多 GPU 通信
ncclCommInitAll(comms, num_gpus, devs);

// 方法二:多进程(每进程一块 GPU,生产环境常用)
// 每个进程分别调用:
ncclUniqueId id;
if (rank == 0) ncclGetUniqueId(&id);
MPI_Bcast(&id, sizeof(id), MPI_BYTE, 0, MPI_COMM_WORLD); // 广播 id
ncclCommInitRank(&comm, num_gpus, id, rank);

AllReduce 的使用

基本 AllReduce(求和后广播到所有 GPU):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 每个进程/线程在自己的 GPU 上调用
// 归约类型:ncclSum(求和)/ ncclAvg(平均)/ ncclMax / ncclMin
ncclAllReduce(
sendbuff, // 输入:当前 GPU 的梯度
recvbuff, // 输出:全局归约后的梯度(可与 sendbuff 相同,in-place)
count, // 元素数量
ncclFloat, // 数据类型(ncclFloat / ncclHalf / ncclInt 等)
ncclSum, // 归约操作
comm, // NCCL 通信子
stream // CUDA Stream(NCCL 操作在 GPU Stream 上异步执行)
);

// 等待通信完成
cudaStreamSynchronize(stream);

// 梯度平均(若使用 ncclSum)
scale_kernel<<<grid, block, 0, stream>>>(recvbuff, 1.0f / num_gpus, count);

分组通信(将多个小 AllReduce 合并,提高效率):

1
2
3
4
5
ncclGroupStart();
for (int i = 0; i < num_layers; i++) {
ncclAllReduce(grads[i], grads[i], grad_sizes[i], ncclFloat, ncclSum, comm, stream);
}
ncclGroupEnd(); // 一起提交,NCCL 内部可以合并通信

ncclGroupStart/End 让 NCCL 将多次通信合并为批量操作,降低每次通信的启动开销。

NCCL 的通信算法

NCCL 根据 GPU 数量、数据大小和拓扑结构自动选择 AllReduce 算法:

Ring AllReduce(最常用):

1
2
3
4
5
GPU0 → GPU1 → GPU2 → GPU3 → GPU0(环形)
分两阶段:
① Reduce-Scatter:每个 GPU 发送部分数据,逐步归约
② AllGather:将归约好的分片广播到所有 GPU
通信量:2(N-1)/N × 数据量(接近最优)

Tree AllReduce(GPU 数量少时):

1
2
树形归约(Reduce 阶段)+ 树形广播(Broadcast 阶段)
延迟为 O(log N),适合 GPU 数量较少(2~8)或数据量较小的场景

NVLink All-to-All(NVLink 拓扑):

当 GPU 间通过 NVLink 全互联时,NCCL 可以直接利用 NVLink 的高带宽做 AllReduce,不需要走 PCIe,大幅提升通信速度。

计算-通信重叠(Gradient Overlapping)

高效的分布式训练会在 Backward Pass 中,一边计算梯度,一边通信已完成的梯度,实现计算与通信的流水线重叠:

1
2
3
4
5
6
7
8
9
10
11
12
时间轴:
Backward Layer N: ████████
AllReduce Layer N: ████████
Backward Layer N-1: ████████
AllReduce Layer N-1: ████████
...

重叠后:
Backward Layer N: ████████
Backward Layer N-1: ████████
AllReduce Layer N: ████████ ← 与下一层 Backward 重叠
AllReduce Layer N-1: ████████

PyTorch DDP 通过 register_hook 在每个参数梯度计算完成时自动触发 NCCL AllReduce,实现这种重叠。手动实现:

1
2
3
4
5
6
7
8
# 将 NCCL 通信放在独立的 Stream
comm_stream = torch.cuda.Stream()

with torch.cuda.stream(comm_stream):
dist.all_reduce(grad_tensor, op=dist.ReduceOp.SUM, async_op=True)

# 同时在默认 Stream 继续 Backward
compute_stream.wait_stream(comm_stream) # 确保通信在用新梯度前完成

ReduceScatter + AllGather(ZeRO 优化器模式)

ZeRO(Zero Redundancy Optimizer)将 AllReduce 拆分为 ReduceScatter + AllGather,允许每个 GPU 只保存参数/梯度/优化器状态的 1/N:

1
2
3
4
5
6
7
8
9
// ZeRO Stage 1/2 的通信模式
// ① ReduceScatter:每个 GPU 收到全局梯度的一个分片(1/N 大小)
ncclReduceScatter(grad_full, grad_shard, count / num_gpus, ncclFloat, ncclSum, comm, stream);

// 在本 GPU 上只更新自己负责的 1/N 参数分片
optimizer_step(param_shard, grad_shard);

// ② AllGather:收集所有 GPU 更新后的参数分片,恢复完整参数
ncclAllGather(param_shard, param_full, count / num_gpus, ncclFloat, comm, stream);

这使得每个 GPU 的峰值内存占用降低到 1/N,是训练超大模型的关键技术。


背景:GPU 间通信的带宽墙

GPU 要协同工作,需要交换数据。数据路径决定了通信带宽上限。CUDA 编程指南明确指出:PCIe 和 NVLink 是 CPU-GPU 及 GPU-GPU 之间的两种主要互连方式,选择哪种会显著影响通信性能。

PCIe:通用总线,CPU-GPU 的标配

PCIe(Peripheral Component Interconnect Express)是服务器/消费级平台的标准扩展总线,GPU 通过 PCIe 槽连接到 CPU。

带宽参数(双向):

PCIe 版本 x16 带宽 发布年份 常见平台
PCIe 3.0 16 GB/s 2010 旧服务器
PCIe 4.0 32 GB/s 2017 现代服务器
PCIe 5.0 64 GB/s 2022 最新平台(H100 主板)

PCIe 的局限

  • 带宽相对 GPU 计算能力是严重瓶颈(H100 计算峰值 ~3958 TFLOPS,PCIe 5 x16 只有 64 GB/s)
  • 延迟较高(微秒级,相比片内通信的纳秒级)
  • 多 GPU 通信需经 CPU Root Complex 中转(除非有 PCIe switch)

IOMMU 与 P2P 的 PCIe 限制:Linux 裸机上若启用 IOMMU,PCIe Peer-to-Peer 传输可能被禁止,必须先关闭 IOMMU 或使用 VM 透传方式。

NVLink:GPU 专用高速互连

NVLink 是 NVIDIA 专为 GPU 间通信设计的高带宽点对点互连技术,完全绕开 PCIe 路径。

带宽参数(双向,单 GPU 视角):

NVLink 版本 双向总带宽/GPU 代表产品 链路数
NVLink 1.0 160 GB/s P100 4
NVLink 2.0 300 GB/s V100 6
NVLink 3.0 600 GB/s A100 12
NVLink 4.0 900 GB/s H100 18
NVLink 5.0 1800 GB/s B100/B200 18+

A100 的 NVLink 3.0 带宽(600 GB/s)是 PCIe 4.0 的 ~19 倍;H100 的 NVLink 4.0(900 GB/s)相比 PCIe 5.0(64 GB/s)仍有 ~14 倍优势。

NVSwitch:扩展到全互联

单 GPU 的 NVLink 端口数有限,无法实现 8+ GPU 全互联。NVSwitch 是一块专用的互连芯片,允许多个 GPU 全互联:

1
2
3
4
5
6
7
不用 NVSwitch(4 GPU 直连):
GPU0 -- GPU1 点对点:每对 GPU 独享带宽
GPU2 -- GPU3

使用 NVSwitch(8 GPU 全互联):
GPU0, GPU1, ..., GPU7 通过 NVSwitch 全互联
任意两 GPU 间带宽等同直连,同时多对 GPU 可并发通信

DGX A100(8xA100)内置 6 块 NVSwitch,提供 600 GB/s × 8 = 4.8 TB/s 的总 NVLink 带宽。NCCL AllReduce 在此拓扑上的带宽可接近理论峰值。

P2P 内存访问:CUDA 层面的差异

NVLink 支持比 PCIe 更细粒度的 Peer-to-Peer 内存访问

1
2
3
4
5
6
7
// 启用 P2P 访问(两种互连都支持)
cudaDeviceCanAccessPeer(&accessible, src_dev, dst_dev);
cudaSetDevice(1);
cudaDeviceEnablePeerAccess(0, 0); // GPU 1 可直接访问 GPU 0 的内存

// GPU 1 的 Kernel 可以直接解引用 GPU 0 的指针
MyKernel<<<1, 1>>>(p0_on_gpu0); // Kernel 在 GPU 1 运行,访问 GPU 0 的 p0

PCIe P2P:需经 PCIe switch(若无则绕道 CPU),延迟高,带宽受限
NVLink P2P:直接 GPU-to-GPU 负载/存储,延迟低,带宽接近 NVLink 峰值

CUDA 指南明确:对于 NVLink 连接的 GPU,P2P 直接访问性能远优于通过主机中转。不需要 cudaMemcpy,Kernel 内直接 *ptr 即可访问对端显存。

NVLink-C2C(Chip-to-Chip)是 NVLink 的特殊变体,用于连接 NVIDIA CPU(Grace)和 GPU(Hopper/Blackwell),例如 Grace Hopper Superchip:

1
2
3
Grace CPU (ARM) ─── NVLink C2C ─── Hopper GPU
LPDDR5X HBM3
~512 GB/s ~3.35 TB/s

NVLink-C2C 提供 ~900 GB/s 双向带宽(远超 PCIe 5.0 的 64 GB/s),并支持硬件 地址转换服务(ATS),实现 CPU 内存和 GPU 内存的硬件一致性访问——GPU Kernel 可以直接以普通指针访问 CPU 的 Pageable 内存,无需 cudaMemcpy 或固定内存,这是 CUDA 的全统一内存(Full-Hardware Unified Memory)模式的基础。

总对比表

对比项 PCIe 5.0 x16 NVLink 4.0(H100) NVLink-C2C(GH200)
用途 CPU-GPU 通用总线 GPU-GPU 专用互连 CPU-GPU 高带宽融合
双向带宽 64 GB/s 900 GB/s ~900 GB/s
延迟 较高(μs 级) 极低(硬件一致性)
P2P 支持 有限(需绕道) 完整(细粒度 LD/ST) 硬件一致性访问
拓扑扩展 PCIe Switch NVSwitch(全互联) 单节点融合
内存一致性 否(需软件同步) 是(ATS 硬件一致性)
适用平台 通用服务器 DGX / HGX 集群 Grace Hopper/Blackwell
成本 高(专有互连) 极高

对 NCCL 的影响

NCCL 在检测到 NVLink 拓扑时,会优先使用 NVLink 路径而非 PCIe,AllReduce 性能差异显著:

平台 AllReduce 带宽(8xA100) 说明
PCIe only(无 NVSwitch) ~30 GB/s 受 PCIe 瓶颈
NVLink + NVSwitch ~200+ GB/s 接近 NVLink 峰值

这也是为什么 DGX 系统(带 NVSwitch)的多 GPU 训练扩展效率(Scaling Efficiency)可以接近线性,而普通服务器的多 GPU 训练效率往往在 50% ~ 70%。


总结

技术/工具 核心价值 最适场景
cuBLAS 经 NVIDIA 深度优化的 GEMM 矩阵运算、框架底层
cuDNN 深度学习算子(卷积/归一化/注意力) 所有 DL 框架
NCCL GPU 集合通信(AllReduce 等) 分布式训练
TensorRT 推理优化(融合+量化+自动调优) 生产推理服务
Triton 用 Python 写高性能 Kernel 快速定制融合算子
NVLink GPU 间高带宽专用互连 多 GPU 训练集群
NVSwitch 8+ GPU 全互联扩展 DGX 类大规模系统
NVLink-C2C CPU-GPU 硬件一致性融合 Grace Hopper/Blackwell