GPU 利用率优化
GPU 很贵,利用率上不去就是在烧钱。
这篇讲怎么诊断和优化 GPU 利用率。
利用率低的表现
nvidia-smi
# 典型低利用率
# GPU-Util: 10-30%
# Memory: 只用了一小部分
这种情况下,GPU 大部分时间在等,实际计算很少。
利用率低的原因
1. 数据加载慢
CPU 加载数据跟不上 GPU 计算。
诊断:
# 打印数据加载时间
import time
for batch in dataloader:
data_time = time.time()
batch = batch.to('cuda')
data_time = time.time() - data_time
compute_time = time.time()
output = model(batch)
loss.backward()
compute_time = time.time() - compute_time
print(f"Data: {data_time:.3f}s, Compute: {compute_time:.3f}s")
如果 Data 时间和 Compute 时间差不多甚至更长,说明数据加载是瓶颈。
优化:
# 增加 DataLoader workers
dataloader = DataLoader(
dataset,
batch_size=32,
num_workers=8, # 增加 worker 数量
pin_memory=True, # 使用锁页内存
prefetch_factor=2, # 预取
)
2. Batch Size 太小
batch size 小,GPU 并行度不够。
诊断:
# 看显存使用
nvidia-smi
# 如果显存才用了 10%,说明 batch size 可以加大
优化:
- 增大 batch size
- 如果显存不够,用梯度累积
# 梯度累积
accumulation_steps = 4
for i, batch in enumerate(dataloader):
loss = model(batch) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
3. 模型太小
小模型计算量不够,GPU 吃不饱。
诊断: 单次 forward 时间特别短(毫秒级),batch size 已经很大了还是利用率低。
解决:
- 这种情况正常,小模型就是这样
- 可以合并多个模型在一张卡上
- 或者用 CPU 推理更划算
4. CPU-GPU 同步
频繁的 CPU-GPU 同步会阻塞。
常见问题:
# 不好:频繁同步
for batch in dataloader:
output = model(batch)
print(output.item()) # .item() 会同步
# 好:减少同步
losses = []
for batch in dataloader:
output = model(batch)
losses.append(output)
# 最后统一处理
total_loss = sum(l.item() for l in losses)
5. 推理场景的特殊性
大模型推理本身就是带宽瓶颈,利用率不会很高。
正常现象:
- 单请求推理:利用率 10-30%
- 批量推理:利用率可以提高
优化:
- 用 Continuous Batching
- 增大并发
- 用量化减少带宽需求
诊断工具
Nsight Systems
NVIDIA 的性能分析工具,可以看时间线。
nsys profile python train.py
nsys-ui report.qdrep
能看到:
- CUDA kernel 执行时间
- CPU-GPU 数据传输
- 空闲时间
PyTorch Profiler
from torch.profiler import profile, ProfilerActivity
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
record_shapes=True,
with_stack=True,
) as prof:
model(input)
print(prof.key_averages().table(sort_by="cuda_time_total"))
输出:
Name CPU Time CUDA Time
---------------------- --------- ----------
aten::mm 10ms 50ms
aten::addmm 5ms 30ms
...
nvidia-smi dmon
持续监控 GPU 指标:
nvidia-smi dmon -s u # 利用率
nvidia-smi dmon -s m # 显存
训练优化
混合精度
用 FP16/BF16 减少显存和计算:
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for batch in dataloader:
with autocast():
output = model(batch)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
效果:
- 显存减少约 50%
- 训练速度提升 1.5-2x
- 可以用更大 batch size
梯度 Checkpointing
用时间换空间:
from torch.utils.checkpoint import checkpoint
class Model(nn.Module):
def forward(self, x):
# 不存中间激活,需要时重新计算
x = checkpoint(self.layer1, x)
x = checkpoint(self.layer2, x)
return x
效果:
- 显存大幅减少
- 计算时间增加约 30%
编译优化
PyTorch 2.0 的 compile:
model = torch.compile(model)
效果:
- 算子融合
- 减少 kernel launch 开销
- 通常 10-30% 提升
FlashAttention
优化 Attention 计算:
from flash_attn import flash_attn_func
output = flash_attn_func(q, k, v)
效果:
- 显存大幅减少
- 长序列尤其明显
推理优化
Continuous Batching
不等凑批,动态处理请求:
# vLLM 自动做
from vllm import LLM
llm = LLM(model="llama-7b")
# Continuous batching 是默认开启的
效果:
- GPU 利用率大幅提升
- 吞吐量提升 2-4x
量化
减少模型大小和计算量:
# INT8 量化
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(
"llama-7b",
load_in_8bit=True,
)
效果:
- 显存减少 50%(INT8)或 75%(INT4)
- 推理速度提升
- 效果略有下降
并发请求
单请求利用率低,增加并发:
# 压测
import asyncio
import aiohttp
async def request():
async with aiohttp.ClientSession() as session:
await session.post(url, json=data)
# 并发 100 个请求
await asyncio.gather(*[request() for _ in range(100)])
找到最优并发数:
- 太少:利用率低
- 太多:延迟上升、OOM
Speculative Decoding
用小模型预测,大模型验证:
# vLLM 支持
llm = LLM(
model="llama-70b",
speculative_model="llama-7b",
num_speculative_tokens=5,
)
效果:
- 端到端延迟降低
- 大模型利用率提升
集群层面
任务调度优化
避免小任务独占大资源:
# 小任务用 GPU 分时
resources:
limits:
nvidia.com/gpu: 1 # 配合时间分片
资源池化
把 GPU 资源池化,按需分配:
- 使用 MIG 切分大卡
- 使用时间分片提高利用率
- 合理设置队列优先级
监控驱动优化
定期看监控,发现利用率低的任务:
-- 找利用率低于 20% 的任务
SELECT job_name, avg(gpu_utilization)
FROM metrics
WHERE gpu_utilization < 20
GROUP BY job_name
优化 checklist
训练任务
- [ ] batch size 是否够大
- [ ] DataLoader workers 是否够多
- [ ] 是否用了混合精度
- [ ] 是否有不必要的 CPU-GPU 同步
- [ ] 是否用了高效的 Attention 实现
推理服务
- [ ] 是否用了 Continuous Batching
- [ ] 并发数是否合理
- [ ] 是否考虑量化
- [ ] KV Cache 配置是否合理
集群管理
- [ ] 是否有 GPU 空置
- [ ] 小任务是否占用大资源
- [ ] 是否有任务长期利用率低
小结
GPU 利用率优化核心:
常见原因:
- 数据加载慢
- Batch size 小
- CPU-GPU 同步
- 推理本身特性
训练优化:
- 增加 DataLoader workers
- 混合精度
- 梯度 checkpointing
- torch.compile
推理优化:
- Continuous Batching
- 量化
- 增加并发
- Speculative Decoding
核心认知:利用率低不一定是问题,要看具体场景。推理场景 30-40% 可能是正常的。
下一篇讲 AI Infra 的成本怎么算。