说明:标题中的Stable Diffusion XL(SDXL)在本文写作时(2023年5月)尚未发布(SDXL于2023年7月发布)。

本文基于我排查Stable Diffusion线上Bug的真实经历,分享排查过程、问题原因、解决方法,以及经验教训。

一、问题出现

1. 线上报警

那天晚上,线上报警了。

  • AI绘画服务异常
  • 生成图片失败
  • 错误率飙升
  • 用户投诉
  • 我被电话叫醒了

线上问题,总是在半夜来。

2. 初步判断

初步判断:

  • 是Stable Diffusion服务的问题
  • 不是前端的问题
  • 不是网络的问题
  • 是模型推理的问题
  • 需要尽快排查

模型服务,是核心。

3. 开始排查

我开始排查。

  • 登录服务器
  • 看日志
  • 看监控
  • 看资源使用
  • 一步步来

排查,需要冷静。

二、排查过程

1. 第一步:看日志

第一步:看日志。

# 查看服务日志
tail -f /var/log/sd-service.log
  • 发现有报错
  • CUDA out of memory
  • 显存不足
  • 找到了线索

日志,是第一线索。

2. 第二步:看显存使用

第二步:看显存使用。

# 查看GPU使用情况
nvidia-smi
  • 显存占满了
  • 有几个进程占用
  • 有些是僵尸进程
  • 显存泄漏了

显存,是瓶颈。

3. 第三步:看请求量

第三步:看请求量。

  • 请求量突然增加
  • 有恶意请求
  • 或者是活动
  • 并发太高
  • 服务扛不住了

请求量,是诱因。

4. 第四步:看代码

第四步:看代码。

  • 检查推理代码
  • 发现没有释放显存
  • 每次请求都加载模型
  • 没有复用
  • 代码有问题

代码,是根本原因。

5. 第五步:复现问题

第五步:复现问题。

  • 本地复现
  • 模拟高并发
  • 确实显存泄漏
  • 确认了问题
  • 可以修复了

复现,是修复的前提。

三、问题原因

1. 原因一:显存泄漏

第一个原因:显存泄漏。

  • 每次推理后
  • 没有清空显存
  • torch.cuda.empty_cache()没调用
  • 显存越用越多
  • 最后OOM

显存泄漏,是核心问题。

2. 原因二:模型重复加载

第二个原因:模型重复加载。

  • 每个请求都加载模型
  • 没有单例
  • 没有缓存
  • 显存浪费严重
  • 效率低

重复加载,是设计问题。

3. 原因三:没有队列

第三个原因:没有队列。

  • 并发请求直接处理
  • 没有排队
  • 同时推理太多
  • 显存不够
  • 服务崩溃

队列,是必要的。

4. 原因四:没有限流

第四个原因:没有限流。

  • 没有限流机制
  • 恶意请求可以打满
  • 正常请求也受影响
  • 服务不稳定

限流,是保护。

5. 原因五:监控不完善

第五个原因:监控不完善。

  • 没有显存监控
  • 没有请求量监控
  • 没有错误率监控
  • 问题发现晚
  • 被动应对

监控,是眼睛。

四、解决方法

1. 临时方案:重启服务

临时方案:重启服务。

# 重启服务
systemctl restart sd-service
  • 先重启释放显存
  • 恢复服务
  • 再慢慢优化
  • 先止血

止血,是第一步。

2. 修复显存泄漏

修复显存泄漏:

# 推理后清空显存
with torch.no_grad():
    result = model(prompt)
torch.cuda.empty_cache()
  • 每次推理后清空缓存
  • 及时释放显存
  • 防止泄漏
  • 关键修复

显存释放,是核心修复。

3. 模型单例

模型单例:

# 全局只加载一次模型
model = None
def get_model():
    global model
    if model is None:
        model = load_model()
    return model
  • 模型只加载一次
  • 全局复用
  • 节省显存
  • 提高效率

单例,是优化。

4. 加队列

加队列:

# 使用队列处理请求
from queue import Queue
request_queue = Queue(maxsize=100)

def worker():
    while True:
        request = request_queue.get()
        process(request)
  • 请求排队
  • 控制并发
  • 防止同时推理太多
  • 保护服务

队列,是缓冲。

5. 加限流

加限流:

  • 限制并发数
  • 限制QPS
  • 限制用户频率
  • 防止恶意请求
  • 保护服务

限流,是保护。

6. 完善监控

完善监控:

  • 显存使用监控
  • 请求量监控
  • 错误率监控
  • 响应时间监控
  • 告警及时

监控,是保障。

五、验证效果

1. 测试

测试:

  • 本地测试
  • 模拟高并发
  • 显存不再泄漏
  • 服务稳定
  • 性能提升

测试,验证修复。

2. 上线

上线:

  • 灰度发布
  • 观察监控
  • 没有问题
  • 全量发布
  • 服务恢复

上线,完成修复。

3. 观察

观察:

  • 观察几天
  • 显存稳定
  • 没有OOM
  • 错误率正常
  • 问题解决

观察,确保稳定。

六、经验教训

1. 教训一:显存管理

第一个教训:显存管理。

  • GPU服务要注意显存
  • 及时释放
  • 防止泄漏
  • 监控显存
  • 显存是宝贵资源

显存,是GPU服务的生命线。

2. 教训二:模型加载

第二个教训:模型加载。

  • 模型只加载一次
  • 全局复用
  • 不要重复加载
  • 节省资源
  • 提高效率

模型加载,要优化。

3. 教训三:并发控制

第三个教训:并发控制。

  • GPU服务并发有限
  • 要加队列
  • 要限流
  • 控制并发
  • 保护服务

并发控制,是稳定的关键。

4. 教训四:监控告警

第四个教训:监控告警。

  • 完善的监控
  • 及时的告警
  • 早发现早处理
  • 不要等用户投诉
  • 主动监控

监控,是眼睛。

5. 教训五:应急预案

第五个教训:应急预案。

  • 要有应急预案
  • 出问题知道怎么办
  • 先止血再修复
  • 快速恢复
  • 减少影响

预案,是保障。

七、GPU服务的最佳实践

1. 模型管理

最佳实践一:模型管理。

  • 模型单例
  • 懒加载
  • 模型热更新
  • 版本管理
  • 模型缓存

模型管理,是基础。

2. 显存管理

最佳实践二:显存管理。

  • 及时释放
  • 显存池
  • 批量推理
  • 混合精度
  • 显存优化

显存管理,是核心。

3. 并发管理

最佳实践三:并发管理。

  • 队列
  • 限流
  • 并发控制
  • 超时处理
  • 优雅降级

并发管理,是稳定的保障。

4. 监控运维

最佳实践四:监控运维。

  • 资源监控
  • 业务监控
  • 日志管理
  • 告警机制
  • 自动化运维

监控运维,是长期保障。

5. 性能优化

最佳实践五:性能优化。

  • 模型优化
  • 推理优化
  • 批量处理
  • 缓存
  • 持续优化

性能优化,是永恒的主题。

八、写在最后

线上出了个Stable Diffusion的Bug,我排查了一夜。

问题是显存泄漏导致的OOM,加上模型重复加载、没有队列、没有限流、监控不完善。排查过程:看日志、看显存、看请求量、看代码、复现问题。解决方法:重启服务止血、修复显存泄漏、模型单例、加队列、加限流、完善监控。

2023年了,AI服务越来越多,GPU服务的稳定性越来越重要。显存管理、模型加载、并发控制、监控告警、应急预案,这些都是GPU服务的必修课。出问题不可怕,可怕的是不从问题中学习。

最后,用一句话总结:"GPU服务,显存是生命线,并发是关键,监控是眼睛。出问题不可怕,可怕的是不总结。每次排查都是一次成长。"

愿你的线上服务,永远稳定,永远不半夜报警。