两个兼职开发者在6天内将H3视频模型落地生产,踩坑torch 2.11+cu128、diffusers未合并PR等依赖陷阱,并实现了图生视频/视频生视频功能。
MiniMax 于 8 月 3 日发布了 H3(他们的全模态视频模型)的开放权重。到 8 月 6 日,它已经在我们的自有 GPU 上投入生产——text-to-video 和 image-to-video,附带联合音频,通过 gen-image、Imference API 和 Imference Desktop 提供服务。我们是两个兼职做这件事的开发者。
这篇文章讲的是那些不光彩的中间过程:当你不写自定义内核、也没有实验室级 GPU 预算时,权重放出后三天内把一个前沿视频模型跑起来到底需要什么。
搭上一条未合并的 PR
"我们用的是 Hugging Face diffusers"这句话的诚实版本是:H3 支持在 diffusers PR #14355 中,该 PR 尚未合并也未发布。我们锁定经过验证的精确 commit。该 PR 引入了 torch.nn.functional.ScalingType,这仅在 torch 2.10 及以上版本存在——所以技术栈是 torch 2.11.0+cu128、torchao 0.18,以及一个官方还不存在的 diffusers 构建。
这种速度的代价:H3 worker 无法与我们的其他fleet(固定在 diffusers 0.39.0)共享 venv。专用 Pod、专用技术栈,等 PR 发版后我们会与之统一。**我们不写推理内核——我们骑在 Hugging Face 的工作还在温热的时候。**这就是全部策略,commit 级别的 pin 就是它的代价。
真正的三天:转换 checkpoint
关于第三天支持,没人告诉你的事情是:存在的权重不是你需要的权重。
唯一可用的 H3 int8 量化是 Comfy-Org 以 ConvRot 格式发布的——块级 Hadamard 旋转加上 per-channel scale,为 ComfyUI 布局。Diffusers 期望的是另一个世界。所以三天真正的工作是一个转换器:去量化 ConvRot,重新序列化为 torchao int8,重新合成音频 VAE 的 weight_g/weight_v 参数(发布的文件里 weight_norm 已融合),然后把整个东西重建为 diffusers 模型树。
我最喜欢的细节:H3 的文本编码器是截断到 50 层的 Qwen3-VL,但加载器的守卫坚持要求超过 50 层。转换器合成一个假的第 51 层——norm 为 1,投影接近零——纯粹是为了走过这个检查。它什么都不做。它存在只是为了满足一个断言。
输出是一个跨 27 个文件的约 63 GB 树,通过 HTTP range 请求对照原始 safetensors 头信息进行验证——你可以检查 tensor shape 和 dtype 而无需下载 63 GB,这感觉像是最好的作弊方式。我们把这个树推到了自己的 R2 镜像;生产 Pod 以 HF_HUB_OFFLINE=1 运行,运行时从不触碰 Hugging Face。
int8 值得这么麻烦吗?我们在相同 seed 上与 bf16 做了 A/B 测试:画面视觉上完全相同。int8 是生产环境配置,无需争论。(int4 尝试过并被放弃——torchao 0.18 移除了旧的 int4 路径,而新路径依赖一个尚未发布的库。Comfy 的"剪枝" bf16 变体原来是一个修改过的架构,低秩 AdaLN 等——不可转换,被拒绝了。)
H3 的惊喜不是 VRAM。使用块级 offload 它峰值约 21 GB;使用叶级 offload,10.7 GB——而且由于吞吐量受内存带宽而非计算约束,激进的 offload 几乎不花你什么钱。一张 12-16 GB 的游戏卡就能跑这个模型。
地板不是 VRAM。是 host RAM:你需要大约 75 GB 来持有用于 offloading 的权重。这个单一数字解释了我们大多数部署决策——包括为什么 Desktop 把 H3 作为云模型(下面会详述)。
我们在租来的 RTX 6000 Ada(48 GB,SD 分辨率约 14.9 秒/step)上验证,生产服务跑在 RTX 5090 Pod(约 8.1 秒/step——几乎是 Ada 的 2 倍,GDDR7 带宽在发挥作用)。我们更广泛的 fleet 跨越 A4000 到 5090,这迫使我们做了一个谨慎的选择:CUDA 12.8 是最后一个在单个镜像中覆盖整个范围的构建版本,从 Turing 到 Blackwell。torch 2.12 丢弃了它。所以 pytorch:2.11.0-cuda12.8 是据我们所知存在的最新"到处都能跑"的组合。
一个值得传递的部署经验:我们的 GPU preflight 以前检查设备的计算能力与支持列表的匹配情况。一张 4090 通过了检查,然后在元数据说不可能失败的方式上失败了。修复方法简单得令人尴尬——运行一个 kernel,向 zeros(8) 加 1,断言 sum 是 8。测试行为,不是元数据。
真实数字,RTX 5090,6.58 秒 clip(24 fps),固定 seed:SD(960×544)20 步约需 4 分钟。HD(原生 1344×768)约需 9 分钟。HD 30 步需 14 分钟——但被拒绝了,因为在盲测 A/B 中我分辨不出 20 步的区别。注意力成本随 token 计数的平方增长,所以分辨率是残酷地昂贵的。
这就是为什么我们提供 540p 和 720p,再往上没有了:我们没有 GPU 预算来以我们能接受的延迟提供更多。SD 是默认选项;HD 是可选的,定价也相应更高。(2K 模块从未进入讨论——MiniMax 把它保持在 API 独占。)冷启动一个全新 Pod 需要 2 分 15 秒——从 CDN 镜像拉取 63 GB 加上加载时间。
一个运营注脚:为图像作业调整的队列超时在与视频接触时无法存活。对于 SDXL 渲染来说慷慨的天花板,对 9 分钟 clip 来说是致命的——我们的视频 rail 现在以比图像作业长得多的 active timeout 运行。视频作业是另一种生物,栈的每一层都需要知道它。
一个引擎,一个队列,三个产品
H3 现在跑在与我们所提供所有其他服务相同的 rail 上:一个推理引擎,一个队列(runqy,我们的开源 Go 任务队列),分发到 gen-image、Imference API 和 Imference Desktop。在 Desktop 上它是一个云模型——本地栈留在稳定的 diffusers 上,而且 75 GB host RAM 不是笔记本电脑的事。
还有一件事对我们来说也是第一次,几乎是顺带一提:H3 与视频联合生成音频,在同一个 DiT 中——mp4 输出时已 muxed 了 AAC。这是屋子里第一条音频+视频 rail,发布它没有额外成本。
三天,一个转换器,一个假层。模型已上线——去用它创造点什么吧。