共计 2182 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么你的 2060 显卡跑不满?
用 RTX 2060 训练 YOLOv5 时,经常发现 nvidia-smi 显示的 GPU-Util 长期在 60% 以下波动,而显存占用却高达 80%。通过分析发现两个典型现象:

-
SM 单元利用率不足 :使用 Nsight Systems 抓取数据时,可以看到 SM(Streaming Multiprocessor,流处理器)活跃周期呈现锯齿状,大量时间在等待内存访问
-
显存带宽瓶颈 :运行带宽测试工具时,实测带宽只有 288GB/s(理论值 336GB/s),尤其在多卡数据并行时更明显
# 典型 nvidia-smi 监控输出示例
+-----------------------------------------------------------------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util. Compute M. |
|===============================+======================+======================|
| 0 RTX 2060 On | 00000000:01:00.0 Off | N/A |
| 45% 62C P2 87W / 170W | 5879MiB / 5921MiB | 58% Default |
+-----------------------------------------------------------------------------+
技术方案:从 CUDA 内核到框架的立体优化
传统方案的三大局限
- CUDA Stream:手动划分流需要精确计算依赖关系,容易引发同步错误
- PyTorch DataLoader:默认设置下 num_workers 超过 4 会导致 CPU 成为瓶颈
- 显存碎片 :频繁创建 / 释放小张量会产生不可用显存空洞
我们的改进方案
-
CUDA Graph 流水线 :将频繁调用的 kernel 序列(如前向 + 反向)打包成图结构,减少启动开销
-
TensorRT 融合策略 :
- 使用 FP16 量化减少 50% 显存占用
-
自动合并 Conv+BN+ReLU 等连续操作
-
动态分时复用 :
# 创建高 / 低优先级流 high_pri_stream = torch.cuda.Stream(priority=-1) low_pri_stream = torch.cuda.Stream(priority=0) # 关键路径放在高优先级流 with torch.cuda.stream(high_pri_stream): model(inputs).backward() # 数据预处理放低优先级流 with torch.cuda.stream(low_pri_stream): next_batch = preprocess(data)
代码实现:从原子操作到性能分析
避免 Bank Conflict 的共享显存访问
__global__ void reduce_kernel(float* input, float* output) {extern __shared__ float sdata[];
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
// 使用填充解决 bank conflict
sdata[tid * 2] = input[idx]; // 每个 bank 存储间隔 2 个元素
__syncthreads();
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {if (tid < s) {sdata[tid * 2] += sdata[(tid + s) * 2];
}
__syncthreads();}
if (tid == 0) atomicAdd(output, sdata[0]);
}
Nsight Compute 关键指标对比
| 指标 | 优化前 | 优化后 |
|---|---|---|
| IPC(每周期指令数) | 0.72 | 1.35 |
| SM 利用率 | 61% | 89% |
| 显存延迟 | 220ns | 180ns |
测试环境:Ubuntu 20.04, Driver 515.65.01, CUDA 11.7
生产环境避坑指南
多进程锁争用解决方案
-
使用进程级 GPU 分配:
os.environ["CUDA_VISIBLE_DEVICES"] = str(rank % num_gpus) -
避免使用
torch.cuda.empty_cache(),改为固定显存池
ECC 显存自检脚本
# 检查 ECC 错误计数
nvidia-smi --query-gpu=ecc.errors.uncorrected --format=csv
# 定期重置计数器
sudo nvidia-smi --reset-ecc-errors=0
功耗墙规避技巧
- 手动锁定基础频率:
sudo nvidia-smi -lgc 1200 # 锁定 1200MHz - 使用持续监控:
while True: temp = get_gpu_temp() if temp > 80: reduce_batch_size()
开放问题
当 batch size 超过 L2 缓存容量时,warp 调度器会因为缓存抖动频繁切换执行上下文。这种情况下,是应该:
1. 增加 warp 中的线程数量来提升计算密度?
2. 还是主动降低 occupancy 以减少缓存竞争?
欢迎在评论区分享你的实战经验!
正文完
发表至: 未分类
近两天内
