LTX-2.3 本地部署完整复盘

先把结论放前面:LTX-2.3(22B)这条 pipeline 在 4×RTX 3090(24GB)这套硬件上,按官方默认推理方式基本跑不起来。我最终得到的不是“没跑通”,而是一个更有价值的结果:把它为什么跑不起来、卡在哪、该怎么判断“物理不可行”,完整验证了一遍。

这篇文章是一次本地部署的工程复盘:从模型文件下载、依赖链补齐、环境和代码层踩坑,到显存拆分、多卡 device 规划,再到最终 OOM 的边界判断。希望你在遇到类似“看起来只要把权重放进去就能跑”的大模型工程时,可以少走很多弯路。


TL;DR(1 分钟读完)

  • LTX-2.3 不是单模型,而是一个多组件 pipeline:文本编码器(Gemma)+ 视频 diffusion 主模型(22B)+ 可能的增强/上采样等。
  • 仅下载 ltx-2.3-22b-distilled.safetensors(≈46GB)并不够,运行时会继续依赖 HuggingFace 生态的一整套配置文件与子模型。
  • 我在 3090 上遇到两类 OOM:
    • Gemma(12B)加载阶段先爆一次(prompt encoder 直接吃掉接近一张 3090 的可用显存)。
    • diffusion 主模型推理阶段仍然爆(不是调小分辨率/帧数就能解决的那种)。
  • 做到“能跑”的三条现实路线:
    1. CPU / NVMe offload(能出结果但会很慢);
    2. 真正意义上的模型并行/张量并行(需要改造实现,不是简单多卡);
    3. 换更现实的模型/蒸馏版本。

1. 背景

目标很直接:

  • 在本地 GPU 服务器上部署 LTX-2.3(22B)视频生成模型
  • 硬件:4×RTX 3090(每张 24GB)
  • 系统:Ubuntu 24.04
  • 网络受限:需要手工下载模型(避免 git-lfs 拉爆、断点续传等)

我以为这会是一件“把权重下载下来、装依赖、跑脚本”的事。事实证明:当模型规模跨过某个阈值后,部署问题本质上会从“软件配置”变成“系统工程 + 资源规划”。


2. 一个关键认知:LTX 是 pipeline,不是“一个模型”

很多部署失败,是从最初的心理模型就错了。

LTX-2.3 的推理链路更像这样:

Prompt(文本)
  ↓
Text Encoder(Gemma)
  ↓
Diffusion 主模型(视频生成)
  ↓
(可选) Upsampler / Enhancer
  ↓
Video Output

也就是说你不是在部署一个 .safetensors,而是在部署一条 由多个模型 + 多套配置 + 多段推理代码组成的 pipeline。

只要这条链上的任何一环缺文件、缺 config、版本不匹配,你就会看到“看似随机”的报错。


3. 第一步:下载主权重,但很快发现“这只是开始”

我先手工下载了 diffusion 主模型的权重:

ltx-2.3-22b-distilled.safetensors  ≈ 46GB

这一步本身没什么技术含量,但它会带来一个误导:你会以为“权重有了就能跑”。

很快,运行就报错(典型的第一类缺失):

找不到 preprocessor_config.json

这意味着:代码实际上在按 HuggingFace 的模型目录结构去找东西——不仅是权重文件,还包括 tokenizer / processor / config 这一整套元数据。


4. 最大的坑:Gemma 依赖与版本匹配

继续顺着报错和代码路径往下挖,我定位到:

PromptEncoder → GemmaTextEncoder

结论很明确:必须提供 Gemma 模型。并且不是“随便一个 Gemma”。

我走了三次典型的错误分支:

4.1 gemma-2:架构不匹配

这类错误的特征是:你能加载一部分,然后在模型结构对齐/层数/命名上出现不兼容。

4.2 gemma-3-4b:尺寸不匹配

报错类似:

hidden_size mismatch(2560 vs 3840)

这基本可以断定:上游代码/权重期望的是更大规格的文本编码器。

4.3 gemma-3-12b-it:终于对上

最终匹配的版本是 gemma-3-12b-it。除了分片权重外,还必须补齐:

  • tokenizer
  • processor
  • config
  • preprocessor_config.json(关键)

到这一步,依赖链才算完整。


5. 环境与依赖:不是“装不装得上”,而是“能不能稳定复现”

我这次遇到的环境问题并不花哨,但非常常见:

5.1 Conda strict priority 导致依赖不可解

典型报错:

LibMambaUnsatisfiableError

如果你用了国内镜像(例如开启 strict priority),很容易把解空间卡死。处理方式也很工程化:

  • 切回默认源,或
  • 调整 channel priority 策略

5.2 uv / pip / .venv 混用导致“看起来装了但其实没装”

我这里踩到的是:项目自带 .venv 与我的 conda 环境产生混乱。一个简单但重要的结论是:

  • 项目自带 .venv 不等于必须使用
  • 你完全可以用 conda + pip 管理,只要保证解释器和 site-packages 指向一致

6. 第一次真正跑起来前:Gemma 先 OOM

当依赖补齐之后,我第一次遇到的硬性问题是:Gemma(12B)加载/初始化阶段直接 OOM。

报错类似:

CUDA out of memory(发生在 PromptEncoder)

直觉上也合理:

  • Gemma 12B 本身就接近 20GB+ 的显存消耗
  • 3090 标称 24GB,但可用显存会被 buffer、碎片和 runtime 开销进一步挤压

6.1 解决:手动做“分卡”而不是幻想多卡自动分摊

我采取的策略:

  • GPU1:Gemma / PromptEncoder
  • GPU0:LTX diffusion 主模型

我在 DistilledPipeline.__init__ 里增加了类似 text_device 的参数,把 prompt encoder 显式放到 cuda:1,而 diffusion 放到 cuda:0

这类改动的意义在于:多卡不是自动解决显存问题的。你必须明确:每个大组件在哪张卡上。


7. 代码层的“工程坑”(不难但很耗时间)

在继续推进时,我遇到了一组很典型的“不是算法问题,但会让你卡很久”的坑:

  • SyntaxError:复制/粘贴残留(例如多余的引号、括号)
  • TabError:tabs vs spaces(Python 最经典的坑)
    • 我用 sed -i 's/\\t/ /g' 统一掉缩进
  • __init__:后定义覆盖前定义,导致你以为参数传进去了但实际没生效
  • CUDA device ordinal:你以为有 cuda:1,但其实进程只看见一张卡
    • 典型原因是设置了 CUDA_VISIBLE_DEVICES=0
    • 解决:unset CUDA_VISIBLE_DEVICES

这些问题都不“高级”,但它们会把你从“调模型”拖进“调工程”泥潭。


8. 第二次 OOM:主模型推理阶段依然爆

当 Gemma 分卡后,我以为最难的部分过去了。但真正决定“能不能跑”的,是 diffusion 主模型推理阶段的显存峰值。

即使我做了:

  • 降分辨率
  • 减少帧数
  • 尝试进一步拆分

依然出现 OOM,且位置更接近主生成阶段。

这里的核心结论是:

LTX-2.3 22B 的权重(≈46GB)只是显存需求的一部分。

推理时还会叠加:

  • 激活(diffusion 的迭代过程会放大峰值)
  • 各类中间 buffer
  • 可能的 KV cache / attention 临时张量
  • 后处理/增强组件(如果启用)

最终,你会发现 24GB 这条线对它来说太窄了。


9. 最终结论:这是一次“边界验证”

我最终得到的最重要结论是:

  • 在 4×3090(24GB)上,按当前实现与默认推理方式,LTX-2.3 22B 很难稳定跑通
  • 这不是“参数没调好”,而是资源约束与实现方式共同决定的不可行

这类项目最怕的是:你投入很多时间,最后只留下一个模糊的“没跑通”。我希望这篇复盘留下的是一个可复用的方法论:

  1. 先把系统拆成 pipeline(谁在吃资源、谁是硬依赖)
  2. 对每一段做“可测量的资源预算”(显存、CPU、磁盘、带宽)
  3. 多卡要显式规划(组件级 device placement),不要期待自动分摊
  4. 当 OOM 多次落在主模型推理峰值时,要学会及时判断“物理不可行”,避免无效优化

10. 后续可行路线(现实版)

如果你也在类似硬件上想“出结果”,大致只有三条路:

  1. CPU/NVMe offload:能跑,但速度会非常慢
  2. 真正的模型并行:需要系统性改造(不是简单把 .to(cuda:1)
  3. 换模型/换规模:选择更适合 24GB 单卡推理的版本(或更激进的蒸馏/量化方案)

结语

这次复盘对我最有价值的点,不是“搞定了一个部署”,而是把一条大模型 pipeline 的依赖链、资源峰值、以及多卡拆分的工程边界走了一遍。

一句话总结:

从“我能不能跑这个模型”
到“这个模型在这套硬件上按当前实现物理不可行”

Read more

传统 SaaS 转向 AI 时代,我目前的一点理解:先把数据能力变成 Agent 可调用的基础设施

最近我一直在思考一个问题:传统 SaaS 到底应该怎么转向 AI? 一开始很容易想到的方向是:给原来的系统加一个 AI 助手。 比如在页面右下角放一个聊天框,让用户可以问数据、生成报告、总结内容、解释指标。这个当然有价值,但我现在越来越觉得,这只是比较表层的一种转型。 真正的变化,可能不是“在 SaaS 里面加 AI”,而是 SaaS 本身的能力形态发生变化。 过去的 SaaS,核心是给人使用。 人登录系统,看页面、点按钮、筛选数据、导出报表、判断问题,然后再去做决策。数据库是给 Web 页面供数的,后端 API 是给前端页面服务的,整个产品的中心是“人如何操作软件”。 但 AI 时代,尤其是 Agent 逐渐发展之后,

By ladydd

对 Python 应用场景的一次重新思考:FastAPI、协程、线程、数据库与任务系统边界

最近在重新设计一个任务系统时,我顺便把自己对 Python,尤其是 CPython 应用场景的理解重新梳理了一遍。 这次讨论的背景是一个典型的异步任务服务: 上游提交任务 API 立即返回 task_id 后台 worker 慢慢执行 用户通过 task_id 查询任务状态 任务主要是 LLM 调用、图片下载、外部 HTTP 请求这类 I/O 型工作。 一开始关注的是队列、Redis、PostgreSQL、worker 并发控制这些问题。但聊到后面,其实更核心的问题变成了: Python 到底应该放在什么位置? 哪些并发适合 Python? 哪些并发不要硬塞给 Python? FastAPI、协程、线程、数据库之间应该怎么分工? 这篇文章就是这次思考的整理。 一、我不想抛弃 Python,

By ladydd

Go 和 Python 的并发模型对比:进程、线程、协程、并发和并行到底怎么理解?

最近我在写 worker 任务系统的时候,重新理解了一遍 Python 和 Go 的并发差异。 以前写 Python,多 worker 经常要考虑: 多进程怎么管理? 日志会不会串? 一个 worker 崩了怎么办? 怎么吃满多核心? 后来换成 Go,发现一个进程里开多个 goroutine worker 就很自然: go worker(1) go worker(2) go worker(3) go worker(4) 日志也好管,状态也好管,而且单进程还能利用多个 CPU 核心。 一开始很容易误会成: Python 不行,Go 行 但更准确的理解应该是: Python 和

By ladydd

Python 进程和 Go 进程的区别:为什么 Go 单进程多 worker 用起来更爽?

最近我在做 worker 任务系统的时候,突然意识到一个很关键的问题: 以前写 Python,多 worker 的时候经常要小心日志串、文件切割乱、时间不好管理。 但是换成 Go 以后,一个进程里开多个 goroutine worker,反而可以比较自然地写到同一个日志文件里。 一开始我以为这是“Python 和 Go 写日志能力不一样”,后来想明白了,核心不是日志本身,而是: Python 常见 worker 模型:多进程 Go 常见 worker 模型:单进程 + 多 goroutine 这背后其实是两个语言在并发模型上的巨大差异。 一、进程、线程、goroutine 先分清楚 先把几个概念捋一下。 进程:操作系统分配资源的单位 线程:CPU 调度执行的基本单位

By ladydd
陕公网安备61011302002223号 | 陕ICP备2025083092号