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

把 Codex CLI 的登录态"搬"到一台新服务器

场景:你在一台老机器上早就登录好了 Codex CLI,现在开了台新服务器、装好了 codex,但它没登录。你不想在新机上重新走一遍 OAuth 网页授权(有时候服务器上根本打不开浏览器),只想把老机器上那份"已经登录好的身份"复制过去。 这篇讲的就是这个搬运动作的完整方法论——为什么能搬、怎么搬、有哪些坑。命令里所有隐私都用占位符,照着换成你自己的即可。 一、先理解一件事:Codex 的登录就是一个文件 这是整个操作的地基。Codex CLI(ChatGPT OAuth 登录模式下)的登录状态,不在什么系统钥匙串里,也不在环境变量里,就是家目录下一个单独的 JSON 文件: ~/.codex/auth.json 它长这样(字段名是真的,值我打码了): { "auth_mode": "

By ladydd

哨兵机制:让 Agent 一触即醒

0. 一句话点破本质 **让"等"发生在便宜的子进程里,让贵的 agent 只在有事时醒。**心跳解决"最迟多久必有人查岗",探针解决"事情一发生几乎立刻有人到场"——两个机制回答的是两个不同的问题,谁也替代不了谁。 1. 机制全貌:会自杀的轮询进程 + 宿主的"尸体通知" 我的实现只有两块积木: 积木一:一个有明确死法的后台循环 # 放行任务的同时,后台挂上(run_in_background) for i in $(seq 1 20); do 信号=$(ssh data "tmux capture-pane -t dna

By ladydd

Agent 心跳机制·设计与实现

0. 一句话点破本质 **心跳不是闹钟,是"带着完整世界快照的自我唤醒"。**闹钟只解决"什么时候醒";心跳真正要解决的是你点出的那个问题——醒来的那个瞬间,清楚自己是谁、任务到哪了、这一跳该干什么。我所有跑得好的心跳,提示词都写得像给一个失忆的陌生人看的;所有出过事的心跳,都是因为假设"我还记得"。 1. 第一性原理:为什么"醒来知道干啥"这么难 一个长期任务里的 agent 面临三重失忆: 1. 上下文会被压缩——多轮之后早期细节只剩摘要,心跳打进来时,那条心跳提示词可能是上下文里唯一高保真的任务描述 2. 世界在你睡着时变了——下属可能干完了、卡死了、跑偏了,你脑子里的"进度"从睡着那刻就开始过期 3. 任务本身会变—

By ladydd

我没手动映射 3000,公网为什么还能访问?一次 UPnP 误开孔复盘

写在前面:标题里的“自己打开”只是当时的主观感受。路由器没有失控,也不存在神秘穿透。真正发生的是:排障自动化从局域网主动调用了 UPnP AddPortMapping,路由器按协议新增了公网映射。 1. 原本的设计边界 家里的 Open WebUI 跑在一台 Ubuntu 主机的 Docker 中: 内网主机 192.168.x.x:3000 路由器上手动配置的入口是: 公网 TCP 13000 → 内网主机:3000 外部用户不直接访问家宽端口,而是先到云端 Caddy: 用户浏览器 → https://ai.example.com (云端 Caddy) → http://home.example.com:13000 (DDNS → 家宽公网

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