HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于

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 的成本怎么算。