在双卡 16GB 显存上跑通 LTX Video 2.3 —— 踩坑全记录
硬件:RTX 4060 Ti (16GB) + RTX 5060 Ti (16GB),系统内存仅 16GB
目标:跑通 LTX Video 2.3 (22B 参数) 视频生成工作流
背景
LTX Video 2.3 是一个 22B 参数的视频生成模型,完整加载需要 40GB+ 显存。单张 16GB 消费级显卡根本放不下。本文记录了在双 16GB 显卡配置下,从全黑/崩溃到最终跑通的完整排障过程。
最终架构
┌─────────────────────────────────────┐
│ cuda:0 (16GB) │
│ ├── Gemma3-12B Q3_K_M GGUF 5.6GB │
│ ├── 文本投影层 BF16 2.3GB │
│ ├── Video VAE BF16 1.4GB │
│ ├── Audio VAE BF16 0.3GB │
│ └── PyTorch 开销 ~1.5GB │
│ 合计: ~11.1GB 余量: ~4.9GB │
├─────────────────────────────────────┤
│ cuda:1 (16GB) │
│ ├── UNet Q4_K_M GGUF 13.3GB │
│ └── PyTorch 开销 ~1.5GB │
│ 合计: ~14.8GB 余量: ~1.2GB │
├─────────────────────────────────────┤
│ 采样激活值峰值: ~1-2GB(cuda:1) │
└─────────────────────────────────────┘
踩过的坑
坑 1:双卡架构不一致导致 DLPack 跨设备崩溃
两张卡架构不同(一张 Ada,一张 Blackwell),PyTorch 2.10 (cu130) 的 DLPack 跨设备传输检查变严格,不同架构之间没有 P2P 直接访问能力。
解法: 安装官方 ComfyUI-MultiGPU 插件 v2.6.4+,它包含 cu130 DLPack 兼容性修复。
坑 2:NVIDIA-SMI 编号与 ComfyUI CUDA 编号相反
这是个隐蔽的坑:
| nvidia-smi 显示 | 实际显卡 | ComfyUI CUDA 编号 |
|---|---|---|
| index 0 | 5060 Ti | cuda:1 |
| index 1 | 4060 Ti | cuda:0 |
教训: 用 ComfyUI 的系统信息页面确认设备编号,不要相信 nvidia-smi。
坑 3:文本编码器缺少投影层 → 维度不匹配 (3840 vs 4096)
现象: CLIP 编码正常,但 UNet 报维度错误:期望 4096 维,实际收到 3840 维。
根因: LTX 2.3 的文本编码器需要两个文件配合使用:
gemma_3_12B_it_fp8_e4m3fn.safetensors— Gemma3 基础模型(输出 3840 维)ltx-2.3_text_projection_bf16.safetensors— 投影层(3840→4096+2048 维)
缺少投影层时,ComfyUI 的 sd_detect() 检测不到投影权重,默认使用 single_linear(输出 3840 维),而 UNet 的 connector 期望 4096 维 → 崩溃。
投影层文件只含 4 个 key,2.3GB:
text_embedding_projection.video_aggregate_embed.weight
text_embedding_projection.video_aggregate_embed.bias
text_embedding_projection.audio_aggregate_embed.weight
text_embedding_projection.audio_aggregate_embed.bias
关键:Qwen3-VL 不是文本编码器的一部分,它是视觉编码器(用于图生视频),文本编码只需要 Gemma3 + 投影层。
坑 4:FP8 CLIP 12.3GB 塞满 16GB 显卡 → OOM
即使 DisTorch2 开启了虚拟显存分摊(virtual_vram_gb),FP8 版 Gemma3(12.3GB)+ 投影层(2.3GB)= 14.6GB,加上 PyTorch 开销直接爆显存。
根因: DisTorch2 的 new_partially_load 函数在加载每个模块时,会先把模块 .to(device) 移到 GPU,打补丁后才移到最终目标设备。峰值显存远高于稳态分配。也就是说,即使最终只有一部分留在 GPU 上,加载过程中的峰值会短暂占满整张卡。
解法: 换用 GGUF 量化版本 gemma-3-12b-it-abliterated-v2.q3_k_m.gguf(5.6GB),总量从 14.6GB 降到 7.9GB,峰值也不会爆。
坑 5:GGUF + 非 GGUF 混用时 GGMLOps 崩溃
用 GGUF 版 Gemma3 + safetensors 投影层时,ComfyUI-GGUF 插件会强制用 GGMLOps 构建整个模型,包括非量化的投影层。GGMLOps 的 Linear 层把 weight 初始化为 None,通过专门的加载函数填充。如果加载逻辑有 bug(早返回跳过投影层),cast_bias_weight 就会因为 weight=None 崩溃。
解法: 修复 ComfyUI 的 lt.py 中 LTXAVTEModel.load_sd() 的早返回 bug —— 原代码遇到 Gemma3 的 key 时直接 return,跳过了投影层加载。
坑 6:Q4 量化导致运动区域块状伪影
现象: 视频能生成,但运动部分出现马赛克/块状碎裂,静态区域正常。
根因: UNet 使用 Q4_K_M(4-bit)量化,运动区域对时序精度要求最高,4-bit 量化在这块最先崩溃。
影响层级:
- CLIP 量化(Q3)→ 影响提示词理解力,不影响画面质量
- UNet 量化(Q4)→ 直接影响画面像素质量,运动区域最先出问题
缓解: 可以换用全精度 UNet(21GB)配合 DisTorch2 跨卡分摊,但配置复杂。或者接受 Q4 的画质折中。
坑 7:soft_empty_cache 补丁导致图片生成时反复卸载重载模型
这是从 LTX 视频排障延续到图片生成时的一个隐蔽问题。
现象: 跑 Krea2 图片生成时,每次 prompt 之间都会卸载全部模型(UNet + CLIP + VAE)再重新加载。单次推理只需 8 步 ~3 秒,但模型重新加载耗时数分钟,导致批量生成效率极差(479 秒/张 vs 正常 35 秒/张)。
根因: MultiGPU 插件在 device_utils.py 中 patch 了 ComfyUI 的 mm.soft_empty_cache,替换为 soft_empty_cache_distorch2_patched。这个补丁函数内有一段无条件的 CPU 内存检查:
def soft_empty_cache_distorch2_patched(force=False):
is_distorch_active = False
for lm in mm.current_loaded_models:
if hasattr(inner_model, '_distorch_v2_meta'):
is_distorch_active = True
break
# ⚠️ 无条件执行,不管 DisTorch V2 是否活跃
check_cpu_memory_threshold() # RAM > 85% 就触发 free_memory = True
...
ComfyUI 标准流程是每条 prompt 跑完后都调一次 soft_empty_cache。这段补丁使得:
每条 prompt 跑完
→ soft_empty_cache 被调用
→ check_cpu_memory_threshold() 无条件检查 RAM
→ 16GB RAM 跑 12GB FP8 模型时必然 > 85%
→ 设置 free_memory = True
→ 下一条 prompt 进来 → 卸载全部模型 → 重新加载
→ 循环 ♻️
check_cpu_memory_threshold() 是为 DisTorch V2 视频模式设计的(视频生成时模型在多卡间搬运,确实需要管 RAM),但图片生成场景完全不需要。
解法: 将 CPU 内存检查限制为仅在 DisTorch V2 活跃时执行:
# 仅 DisTorch V2 视频模式需要,避免图片生成时反复卸载模型
if is_distorch_active:
check_cpu_memory_threshold()
修改文件: custom_nodes/comfyui-multigpu/device_utils.py(已备份 .bak)
教训:
CPU_MEMORY_THRESHOLD_PERCENT默认 85%,对 16GB 系统内存极为苛刻。任何 > 12GB 的模型加载经过 RAM 时都会触发- 这个问题在用 NVFP4 模型(
7GB)时不明显(RAM 偶尔超阈值),换成 FP8 模型(12GB)后每条 prompt 必然触发 - MultiGPU 插件的内存保护逻辑是为视频生成(DisTorch V2)设计的,但被无条件应用到了所有场景,对纯图片生成造成副作用
坑 8:ComfyUI Desktop 最小化窗口导致每条 prompt 后自动卸载模型
现象: ComfyUI Desktop 窗口在最前时,模型加载后持续驻留显存,批量生成流畅。但一旦最小化窗口(或切换到其他窗口),每条 prompt 执行完后都会自动卸载所有模型(UNet + CLIP + VAE),下一条 prompt 又要重新加载,导致批量生成时间暴增。
根因: ComfyUI Desktop 基于 Electron (Chromium)。当窗口最小化时,Chromium 的 Page Visibility API 检测到页面不可见,触发了 ComfyUI 前端的自动内存释放逻辑——前端调用 api.freeMemory() 向后端发送 POST /free 请求,后端设置 unload_models flag,导致所有模型在 prompt 执行完成后被卸载。
完整触发链:
窗口最小化 → document.hidden = true
→ 前端触发 api.freeMemory()
→ POST /free { "unload_models": true }
→ server.py: prompt_queue.set_flag("unload_models", True)
→ main.py: comfy.model_management.unload_all_models() 💥
→ 下一条 prompt → 重新加载全部模型(数分钟)
后端关键代码(main.py 第 384-386 行):
flags = q.get_flags()
free_memory = flags.get("free_memory", False)
if flags.get("unload_models", free_memory):
comfy.model_management.unload_all_models()
解法:
- 批量生成时不要最小化 ComfyUI 窗口,保持窗口可见(可以在另一个显示器上放着)
- 如果只有一个显示器,把窗口缩小、移到角落,但不要最小化
- 如果是通过 API 批量提交(如
batch_generate.py),可以在生成期间不打开 ComfyUI 的浏览器界面
教训: 这是 Electron 应用的常见副作用——窗口不可见时"优化"系统资源,但对需要持续驻留大量模型在显存的 AI 推理场景,这种优化适得其反。
关键经验总结
1. 显存预算要算 PyTorch 开销
理论计算模型大小时,务必预留 ~1.5-2GB 给 PyTorch runtime(CUDA context、kernel cache、临时缓冲区)。很多 OOM 不是模型太大,而是忘了这部分开销。
2. DisTorch2 的峰值 ≠ 稳态
DisTorch2 分摊加载时,峰值显存可能远超最终稳态分配。选择 virtual_vram_gb 参数时,不能只看稳态,要给峰值留余量。
3. 量化精度选择
| 组件 | 推荐精度 | 原因 |
|---|---|---|
| CLIP(文本编码器) | Q3/Q4 GGUF 可接受 | 只影响语义理解,不影响像素 |
| UNet(核心生成) | 尽量用高精度 | 直接决定画面质量 |
| VAE | BF16/FP16 | 解码质量不能妥协 |
4. 组件职责隔离排障
遇到画面问题时,按职责定位:
- 内容不对(生成的东西跟提示词不符)→ 查 CLIP
- 画质不行(模糊/碎裂/伪影)→ 查 UNet 精度和采样参数
- 颜色/解码异常 → 查 VAE
5. ComfyUI Desktop 的工作流陷阱
ComfyUI Desktop 运行时,浏览器中保存着工作流的内存副本。直接修改磁盘上的 JSON 文件不会生效——下次关闭 Desktop 时,它还会用内存中的旧版本覆盖你的修改。所有参数调整都应该在 ComfyUI 界面内完成。
6. MultiGPU 插件的内存保护对图片生成是副作用
MultiGPU 插件 patch 了 soft_empty_cache,加入了 CPU RAM 使用率检查(默认阈值 85%)。这个机制对 DisTorch V2 视频模式是必要的(视频生成时模型要在多卡间搬运,RAM 压力大),但对普通图片生成会造成每条 prompt 之间卸载重载模型。
16GB 系统内存是触发条件: 任何 FP8 级别(12GB+)的模型加载经过 RAM 时都会撑破 85% 阈值。如果系统内存 ≥ 32GB,这个问题基本不会出现。
排查方法: 如果日志里反复出现 Triggering PromptExecutor cache reset. Reason: cpu_threshold_exceeded,且每条 prompt 都有 Requested to load Krea2,就是这个问题。
7. 量化精度影响 RAM 过路压力
模型从磁盘加载到 GPU 时,必须先经过系统内存(RAM)。量化精度不仅影响显存占用,还直接影响 RAM 过路压力:
| 量化格式 | 模型体积 | 16GB RAM 压力 | 效果 |
|---|---|---|---|
| BF16 | ~24GB | 💥 爆炸 | 不可用 |
| FP8 | ~12GB | ⚠️ 极限 | 触发反复卸载 |
| NVFP4 | ~7GB | ✅ 可控 | 流畅 |
| Q4 GGUF | ~5GB | ✅ 轻松 | 流畅 |
8. ComfyUI Desktop 最小化窗口会触发模型卸载
ComfyUI Desktop 基于 Electron,窗口最小化时会触发前端的自动内存释放,导致每条 prompt 执行完后卸载所有模型。批量生成时务必保持窗口可见。
9. 模型卸载问题的排查清单
当遇到"每条 prompt 之间都重新加载模型"的问题时,按以下顺序排查:
- ComfyUI 窗口是否最小化? → 恢复窗口(坑 8)
- MultiGPU 插件是否触发了 CPU 内存检查? → 查日志有无
cpu_threshold_exceeded(坑 7) - 前端是否在发送
/free请求? → 在浏览器 Network 面板监控 - 是否有多余的 Loader 节点? → 清理工作流中的废弃节点
系统内存的重要性
本案中系统内存仅 16GB,这成为了另一个瓶颈。DisTorch2 的 offload 目标是系统内存(CPU),当 offload 量超过空闲内存时,操作系统开始换页(swap),虽然不会崩溃但速度极慢。
如果条件允许,32GB 以上系统内存会让多 GPU 显存分摊方案更加从容。
本文基于 ComfyUI v0.27.0 + PyTorch 2.10.0+cu130 环境,2025 年 7 月。