OOTDiffusion 推理优化心路历程:从 108 秒到 14 秒的硬核调优

OOTDiffusion 推理优化心路历程:从 108 秒到 14 秒的硬核调优 OOTDiffusion 推理优化心路历程从 108 秒到 14 秒的硬核调优背景接手了一个在外接 USB SSD 上跑的 OOTDiffusion 虚拟试衣服务硬件配置堪称凑合用GPUNVIDIA GeForce RTX 2060 SUPER 8GBCPUIntel Core i3-7100 3.90GHz4核内存8GB → 16GB后来升级的系统盘111GB 外接 USB SSD你没看错系统就装在上面用户反馈第一次换装 108 秒切换衣物类型直接 OOM 崩溃。第一轮纯推理优化1. 空间注意力保留 GPU13.6%原始代码中UNet Garm 的输出spatial_attn_outputs在每步推理中被反复cpu()→to(device)# 原始代码spatial_attn_outputs[t.cpu()fortinspatial_attn_outputs]# 移去CPU...fori,tinenumerate(timesteps):spatial_attn_inputs[t.to(latents.device)fortinspatial_attn_outputs]# 拷回GPU优化RTX 2060 SUPER 有 8GB 显存完全装得下这些中间张量。消除每步的 PCIe 拷贝10 步省掉 10 次 CPU↔GPU 传输。# 优化后# spatial_attn_outputs 直接保留在 GPU...fori,tinenumerate(timesteps):spatial_attn_inputsspatial_attn_outputs# 零拷贝效果每步推理从 ~1.30s 降至 ~1.06s提升 ~13.6%。2. torch.compile 惨遭 OOM尝试对两个 UNet 应用torch.compile(modereduce-overhead)。编译成功后 CUDA Graph 需要额外预留显存8GB VRAM 基线已占 7.5GB直接 OOM。改用modedefault虽然能跑但加速仅有 ~9%且编译耗时 50s。RTX 2060 SUPER 8GB 带不动 torch.compile 的开销。3. 移除虚假的 CUDA 内核编译原始代码中有一段_compile_kernels()def_compile_kernels(self):dummyImage.new(RGB,(768,1024))...self.pipe(...)# 1步推理 torch.cuda.synchronize()打印着Compiling CUDA kernels (one-time, ~40s)…实测发现 ——这 40 秒毫无收益。加了编译的推理反而比不加慢 2 秒。直接变pass省下 40 秒启动时间。第二轮模型切换——真正的噩梦发现模型切换 bugHD↔DC 模型切换时_unload_model的参数是反的# Bug: 参数传反了self._unload_model(dc)# 加载 HD 时应该保留 HD卸载 DC# 实际行为excludedc → 保留 DC卸载 HD → 反向操作修复后模型切换不再 OOM但另一个问题浮出水面。to(cpu)的 70 秒陷阱模型切换时需要把旧模型从 GPU 移到 CPU再加载新模型。self.pipe.to(cpu)调用慢到令人发指 —— 花了~70 秒。调试发现diffusers 的to(cpu)遍历 150 子模块每个模块都触发logging.warning()写入日志。而日志文件在外接 USB SSD 上每次flushTrue都是一次慢速写盘。150 次写盘叠加 70 秒。# 解决方案在 to(cpu) 前临时关闭日志importlogging logging.disable(logging.WARNING)self.pipe.to(cpu)logging.disable(logging.NOTSET)效果70 秒 → 1 秒。对比之震撼令人无语。第三轮内存战争8GB → 16GB 内存升级原计划加载双模型常驻内存实现即时切换。但第一次尝试就导致系统完全挂死 ——USB autosuspend 加上内存吃满 死锁。USB autosuspend隐藏的罪魁祸首cat/sys/module/usbcore/parameters/autosuspend# 输出: 2系统盘是外接 USB SSD空闲 2 秒后 USB 自动断电。下次读写时要等盘片起转、重新枚举。监控面板上看到的 “I/O error” 就是这个原因。# 永久修复echo-1|sudotee/sys/module/usbcore/parameters/autosuspendechooptions usbcore autosuspend-1|sudotee/etc/modprobe.d/usb-autosuspend.confsudoupdate-initramfs-u共享 VAE/CLIP内存优化的关键一击观察模型结构发现VAE CLIP 编码器在两个模型中完全一样却各自加载一遍。组件每个模型HDDCVAE350MB✅✅ 浪费CLIP Image Encoder600MB✅✅ 浪费CLIP Text Encoder250MB✅✅ 浪费UNet Garm1.6GB✅✅UNet Vton1.6GB✅✅抽出SharedComponentsVAE CLIP 只加载一次。切换时只交换 UNet~3.2GBPCIe 1sclassSharedComponents:def__init__(self,gpu_id0):devicefcuda:{gpu_id}self.vaeAutoencoderKL.from_pretrained(...)self.image_encoderCLIPVisionModelWithProjection.from_pretrained(...)self.text_encoderCLIPTextModel.from_pretrained(...)内存从 15.5GB/16GB → 11GB/16GB~4GB 空闲余量再也不用担心 swap 了。第四轮INT8 量化——此路不通想进一步压榨性能尝试torch.ao.quantization.quantize_dynamic。发现UNet Vton 能量化但只影响nn.Linear层10% 计算量UNet Garm 有自定义 attention 代码量化直接卡死quantize_dynamic不支持nn.Conv2dUNet 的主力结论INT8 动态量化在这套代码上行不通。放弃。最终成果场景优化前优化后首次换装加载模型108s OOM~67s同类型第二次18s14s切换衣物类型OOM 崩溃16s系统 RAM 占用15.5/16GB11/16GB服务稳定性经常挂死稳定运行经验总结先看日志—— 70 秒的to(cpu)问题追根溯源只是 150 行logging.warning()刷盘USB 外接系统盘是陷阱—— autosuspend 会无声地搞崩一切 I/Odiffusers 的to()调用很重—— 尽量只移动需要的子模块8GB VRAM 的 GPU 不能 torch.compile—— CUDA Graph 预留内存会撑爆模型组件共享比想象中重要—— 两个模型有 1.2GB 的重复组件抽出来立省 26% 内存最后RTX 2060 SUPER 8GB i3-7100 USB SSD 的组合能压榨到 14 秒推理、16 秒切换已接近物理极限。