Name Last Update
..
scripts Loading commit data...
API.md Loading commit data...
README.md Loading commit data...
api-run.sh Loading commit data...
api.py Loading commit data...
burst-test.py Loading commit data...
concurrency-test.py Loading commit data...
direct-backend-test.py Loading commit data...
gateway-run.sh Loading commit data...
gateway-test.py Loading commit data...
gateway.py Loading commit data...
long15s.sh Loading commit data...
pin-cache.sh Loading commit data...
prompt.txt Loading commit data...
realrun.sh Loading commit data...
restart-8010.sh Loading commit data...
run-verify.sh Loading commit data...
run.sh Loading commit data...
start-all.sh Loading commit data...
start-gateway.sh Loading commit data...
submit-same.py Loading commit data...
verify-fixes.py Loading commit data...
watch-8012.sh Loading commit data...

README.md

MiniMax-H3 视频生成部署(4 × RTX 2080 Ti)

在 192.168.110.67 上用 DiffSynth-Studio 跑 MiniMax-H3 FL2VA(文生视频 + 原生立体声)。

状态(2026-08-18):环境已装好并通过全部兼容性实测;模型下载中(33 GB / 6 MB/s); 尚未完成首次出片。验收结果见本文末「验收记录」,未填即表示还没跑通。

服务器侧根目录 /data/models/minimax-h3/,本目录是其脚本副本 + 决策记录。


一、为什么不能照抄官方示例

官方 MiniMax-H3.md 的示例在这台机器上跑不起来,有三处必须改。

1. torch.bfloat16 → torch.float16(致命)

官方示例的 vram_config 与 torch_dtype 全用 bf16。sm_75 上实测:

[ERR] SDPA bfloat16  EFFICIENT_ATTENTION → RuntimeError: No available kernel
[ERR] SDPA bfloat16  FLASH_ATTENTION     → RuntimeError: No available kernel
[OK ] SDPA bfloat16  MATH                → 只剩朴素实现
[OK ] SDPA float16   EFFICIENT_ATTENTION → 可用

bf16 不是"慢一点",是没有任何高效 attention kernel。PyTorch 会静默掉到 MATH 后端, 在 H3 的 ~14,100 token 序列上要物化 14100 × 14100 × 56 的注意力矩阵,必然 OOM。

FP16 路径实测(真实 H3 形状 S=14100 H=56 D=128):

SDPA EFFICIENT fp16   → OK, 0.21s
fp16 GEMM 有效算力    → 54.3 TFLOPS   (2080Ti fp16 张量核满血)
fp16 深链数值稳定性   → finite

注意:llm-setup.md 说 "Turing…不支持 bf16,跑 GGUF 走 llama.cpp 是唯一实际路径" —— 前半句对,后半句对扩散模型不成立。PyTorch + bitsandbytes NF4 在 sm_75 上是通的,见下。

2. vram_limit 取错了下标(会 OOM)

vram_limit=torch.cuda.mem_get_info("cuda")[1] / (1024 ** 3) - 2   # 官方:[1] = 总显存
vram_limit=torch.cuda.mem_get_info("cuda")[0] / (1024 ** 3) - 2   # 本机:[0] = 空闲显存

mem_get_info() 返回 (free, total)。这台机器四张卡都有常驻服务(llm38 / embedding / SD / whisper), 按总显存 21.5G 要预算,实际只有 12–15G 可用,必 OOM。

3. offload_device="disk" 不能用

model_inference_low_vram/ 那组示例把权重 offload 到磁盘。本机 /data 是 7200rpm 机械盘(HGST HUS726020ALA610,实测顺序读 184 MB/s)。 NF4 DiT 16 GB,每个去噪步都要过一遍,光 I/O 就 87 秒/步。

改用 CPU 内存 offload:物理内存 128 GB,可用 123 GB,单 worker 常驻约 32 GB,宽裕。


二、attention backend:只用 PyTorch SDPA

Backend sm_75 处置
FlashAttention 2 / 3 / 4 ❌ 仅 sm_80–sm_121 不装
SageAttention ❌ 需 sm_80+ 不装
xFormers ⚠️ 系统已装 0.0.33,fp16 可用 不用(见下)
PyTorch SDPA ✅ EFFICIENT(cutlass)后端 采用

DiffSynth 的 diffsynth/core/attention/attention.py 里有自动优先级, xformers 排在 torch 前面会被自动选中。实测:

不设环境变量  → selected = xformers
DIFFSYNTH_ATTENTION_IMPLEMENTATION=torch → selected = torch

两者 fp16 下都能跑,但锁死 torch 保证可复现。run.sh 已导出该变量。

H3 的 DiT 只用最干净的 attention 调用(无 mask / 无 causal / 无 sliding window, 见 minimax_h3_dit.py:79),不会踩 flex_attention 需要 torch.compile 的坑。


三、NF4 量化在 Turing 上成立

NF4 走 bitsandbytes 的 Linear4bit。bitsandbytes 的 4-bit 最低要求正好是 sm_75,2080 Ti 卡在支持线上;其 CMakeLists 的架构列表明确含 75。

服务器上原有的 bitsandbytes 0.45.1 是坏的(在 ~/.local):

AttributeError: 'NoneType' object has no attribute 'cquantize_blockwise_fp16_nf4'

CUDA 动态库没加载上。已在 venv 内装 0.50.1 覆盖。实测通过:

[OK ] NF4 fp16 forward         out=(1024,14336) finite=True rel_err=0.0913
[OK ] NF4 prequantized reload  match=True      ← 这正是加载 .safetensors 时走的分支

四、多卡:H3 不支持模型并行

DiffSynth 的序列并行(diffsynth/utils/xfuser/)只被 wan_video.py 和 mova_audio_video.py 引用;minimax_h3_audio_video.py 里零引用 xfuser / torch.distributed。

叠加硬件事实(llm-setup.md 第 2.2 节):四卡无 P2P(topo -p2p r 全 CNS), GPU0/1 与 GPU2/3 跨 NUMA。

结论:不要试图把 4×22G 当成一张 88G 卡。 多卡只能做独立 worker 并发(每卡一个进程),不能拆单个任务。

并发数上限由内存而非显存决定:每 worker 常驻约 32 GB(DiT NF4 16 GB + 文本编码器 NF4 14.3 GB + VAE 1.8 GB),128 GB 内存 → 最多 2–3 个 worker。


五、模型与算力

DiT 是稠密 30.9B(50 层 / hidden 5376 / 56 heads × 128 / ffn 14336,无 MoE)。 480×832×124 帧 → 视频 latent 35×30×52,patch(1,2,2) → ≈14,100 token。 每步约 830 TFLOP。

文件 大小
minimax-h3-fl2va-nf4.safetensors 15.98 GB
minimax-h3-text-encoder-nf4.safetensors(Qwen3-VL 32B) 14.27 GB
video_vae_nf4.safetensors 1.50 GB
audio_vae_nf4.safetensors 0.26 GB
minimax_h3_fl2v_turbo_4step_v1.0_768p_bf16.safetensors(LoRA) 1.29 GB
合计 33.3 GB

Turbo LoRA 能叠在 NF4 上——core/vram/layers.py:445 的 AutoWrappedQuantizedModule(AutoTorchModule, LoRAHotLoadMixin) 在量化输出之后做 out += x@A@B,不需要反量化基座权重。这是最大的性能杠杆:

方案 步数 单条 5 秒视频(估算)
NF4 全量 50 20–35 分钟
NF4 + Turbo LoRA 4 2–4 分钟

文件名里的 bf16 只是 LoRA 权重的存储精度,加载时会按 pipe.torch_dtype 转成 fp16,不影响。


六、安装了什么(以及没动什么)

全部落在 /data/models/minimax-h3/。系统级零改动。

  • 没装 CUDA、没动驱动(570.153.02 保持)、没碰 /usr、没改 ~/.local
  • 系统 nvcc 是 11.5,很老,但推理用不到——pip 的 torch wheel 自带 CUDA runtime
  • venv 用 --system-site-packages 复用已验证的 torch 2.9.0+cu128 (其 arch_list 含 sm_75),省 3 GB 下载
  • 只装了 bitsandbytes 0.50.1 + diffsynth(-e . --no-deps),其余依赖系统已齐
  • 不要设 PYTHONNOUSERSITE=1:torch / transformers / av 都住在 ~/.local, 屏蔽掉会连 torch 一起丢。venv 的 site-packages 本来就排在 ~/.local 前面, 我们的 bnb 0.50.1 自然覆盖坏掉的 0.45.1

容器变更(用户已授权,可逆)

容器 动作 恢复
llm(旧 llama.cpp,unhealthy 5 天,已被 llm38 取代) 已停 docker start llm
whisper 停后已恢复 —
llm38(Qwen3.8-27B,经 FRP 对外供 qwen.wudaima.com) 全程未碰 —

启动参数完整备份在 /data/models/minimax-h3/backup/(llm.inspect.json / whisper.inspect.json / restore-notes.txt)。两者都是 restart=unless-stopped, 手动 stop 不会被自动拉起。


七、目录与路径

用途 路径
根目录 /data/models/minimax-h3/
venv /data/models/minimax-h3/venv/
DiffSynth 源码 /data/models/minimax-h3/DiffSynth-Studio/(HEAD 0361581)
模型权重 /data/models/minimax-h3/models/
日志 /data/models/minimax-h3/logs/
输出视频 /data/models/minimax-h3/outputs/
脚本 /data/models/minimax-h3/scripts/
容器备份 /data/models/minimax-h3/backup/

八、启动命令

cd /data/models/minimax-h3

./run.sh 1                          # GPU1,Turbo 4-step,480x832x124f(默认)
./run.sh 1 --no-turbo --steps 50    # 全质量 50 步
./run.sh 2 --prompt "..." --seed 7
./run.sh 1 --fp32-accum             # 若出现 NaN/伪影:关 fp16 累加(约慢一倍)

多 worker(每卡一个独立进程,内存上限 2–3 个):

./run.sh 0 --prompt "..." &     # GPU0(NUMA0)
./run.sh 1 --prompt "..." &     # GPU1(NUMA0)
./run.sh 2 --prompt "..." &     # GPU2(NUMA1)
./run.sh 3 --prompt "..." &     # GPU3(NUMA1)

run.sh 会按 GPU 编号选 NUMA 节点绑核(GPU0/1→node0,GPU2/3→node1), 但服务器上没装 numactl,目前这段会自动跳过。装了会更快。

九、脚本

脚本 作用
run.sh 启动器:设环境变量 + 选 GPU + 落日志
t2va.py 推理主体(fp16 / SDPA / CPU offload)
download.py 用 DiffSynth 自己的 ModelConfig 下载,保证落盘布局与加载时解析一致
verify.py 逐文件比对字节数 vs ModelScope + 解析 safetensors 头
probe-turing-caps.py sm_75 能力探测(bf16/fp16 × 各 attention 后端)
probe-h3-shapes.py 真实 H3 形状下的 attention + GEMM 吞吐
probe-nf4-fp16.py NF4 fp16 前向 + prequantized 重载
probe-attention-backend.py 验证 DiffSynth 实际选中的后端

换硬件或升级 torch/bnb 后,先跑 probe-* 四个再动别的。

十、下载注意

出口带宽 5.9 MB/s 封顶(llm-setup.md 第三节),33 GB 约 95 分钟。

  • 必须 setsid,只用 nohup 不够——SSH 通道关闭时进程仍会被带走(本次踩过)
  • 不要开两个下载进程:并行不会更快(出口带宽是瓶颈),只会争抢同一个 ._____temp 分片互相破坏(本次踩过,事后用 verify.py 校验)
  • ModelScope 可达;HuggingFace / ghcr 不通

十一、失败时的降级顺序

  1. fp16 出 NaN → --fp32-accum(关 fp16 累加,约慢一倍)
  2. 仍不行 → 把 norm / adaln 提到 fp32
  3. 显存不够 → 调小 --vram-reserve,或换 pruned 权重 (minimax-h3-fl2va-pruned-nf4.safetensors,9.76 GB / 18.7B,但 Turbo LoRA 与之不兼容,层数不同)

★ 2026-08-18 实测推翻本文档第一节:fp16 是故障本身,不是修复 ★

本 README 第一节写「torch.bfloat16 → torch.float16(致命)」,把改用 fp16 当作必须的修正。 实测结论相反:改成 fp16 才是这条线出不了片的根本原因。

对照实验(唯一变量是 dtype)

同分辨率 448×256×28f、同 prompt、同权重、同 Turbo LoRA,只改 torch_dtype:

fp32 fp16
视频像素 mean=140.9 std=50.6 max=255 mean=0 std=0 max=0
音频 finite=True finite=False
生成耗时 62s 40s
峰值显存 16.3G 16.3G
结果 正常出片 ✅ 124 帧纯黑 ❌

模型、权重、管线、Turbo LoRA、VAE 全部正常——唯一的问题是 fp16 的动态范围装不下这个模型。

为什么之前的 probe 没测出来

probe-nf4-fp16.py / probe-h3-shapes.py 用的是合成小张量,测的是「NF4 fp16 前向不报错、数值有限」。 这类测试测不出真实模型在 50 层残差累积后的量级。实测 dit.blocks.0 输入 absmax 89 → 输出 38176, 逼近 fp16 上限 65504;再往后必然溢出。「单层不溢出」推不出「50 层不溢出」。

溢出机理:.to(x.dtype) 这个模式在库里反复出现

# minimax_h3_dit.py:61-67
def _modulate_scale_shift(x, shift, scale, indices):
    return (x * (1.0 + scale.index_select(0, indices)) + shift.index_select(0, indices)).to(x.dtype)
def _modulate_gate(x, gate, other, indices):
    return (x + gate.index_select(0, indices) * other).to(x.dtype)

库作者自己的注释说「index_select on the AdaLN params can promote the expression」—— 表达式确实被提升到 fp32 算,但结果又降回 fp16,恰好丢掉了提升带来的动态范围。 DiT 有 50 个 block × 每个 2 次 = 100 个这样的降回点。condition_proj 那里是同一个病灶的另一个实例 (fp32 真值 absmax 79554 > 65504)。

逐点修 fp16 是行不通的(四条弯路,都实测过)

  1. --fp32-accum —— 开关生效(fp16_reduced_accum True→False)但输出逐字节相同。 它管的是 reduced-precision reduction,与这里的溢出无关。README 第十一节把它列为降级第一档,对此故障不对症。
  2. 替换 AutoWrappedLinear.forward —— 会绕过 preparing()/onload 换入机制。
  3. 只替换 linear_forward、最后仍 .to(fp16) —— 无效:fp32 算得对,溢出发生在那次类型转换上。 (自检若只查转换前的张量,会永远显示"正常"——踩过。)
  4. 钳位到 65504 再降回 —— 反而更糟:blocks.0 是 h = x + attn(norm1(x)), x 恰好等于 fp16 最大可表示值时再加任何正数立刻变 inf(实测 3.45% 非有限)。钳位不留余量等于自造故障。

每修一处,NaN 就移到下一处——因为问题是全局的动态范围,不是某几个点。

另一个方法教训:诊断配置必须与故障配置逐项一致

前几轮诊断用的是中文 prompt「一只橘猫…」,而实际失败的是 t2va.py 的英文默认 prompt。 不同文本 → 不同嵌入量级 → 溢出点不同。定位出的路径在真实故障里根本不触发,白跑了几轮。

退出码不可信

原 t2va.py 只检查 audio 是否非有限,从不检查 video,全黑视频照样打印 [done] 并返回 0。 已补上 video 的有限性 + 纯色检查(std 退出码 3 退出。


冒烟测试前置检查(2026-08-18 17:59,llm38 切 256K 之后)

环境侧已全部验证,剩下的唯一变量是权重本身。

检查项 结果
attention 后端 @ 真实 DiT 形状 (1,56,14100,128) fp16 ✅ SDPA 选中,finite=True(xformers 可用但被 env 钉死为 torch)
bitsandbytes 版本 ✅ 0.50.1(成功压住 ~/.local 里坏的 0.45.1)
NF4 fp16 前向 + 预量化重载 ✅ rel_err=0.0913,reload match=True
已落盘 3 文件字节 + safetensors 头 ✅ video_vae / audio_vae / turbo LoRA 全对

⚠️ t2va.py:47 的地板值现在是个雷

vram_limit = max(6.0, free_gb - args.vram_reserve)

这个 6.0 地板是在每卡还有 12–15 GB 空闲时写的。2026-08-18 llm38 上下文提到 256K 后(KV 从 8 GiB 涨到 32 GiB),空闲显存腰斩,地板值开始反过来盖掉安全余量:

空闲 实得 vram_limit 余量
GPU0 6.58G 6.00G +0.58G 勉强,无缓冲
GPU1 7.51G 6.00G +1.51G 勉强,最宽裕 → 冒烟测试用这张
GPU2 5.25G 6.00G −0.75G ❌ 必 OOM:地板要的比卡上有的还多
GPU3 6.59G 6.00G +0.59G 勉强,无缓冲

--vram-reserve 调不动这个地板——max() 在外层。

✅ 正解:跑 H3 之前先停 llm38

用户 2026-08-18 明确:这是测试机,不是生产环境,停 llm38 让路是预期做法(见工作区 CLAUDE.md)。所以上面整张表都不必绕,串行跑就行:

docker stop llm38          # 让出约 57 GB
./run.sh 1 ...             # 四卡各约 21 GB 空闲 → vram_limit 约 19 G,地板值失效
docker start llm38         # 跑完起回来,约 15 秒

这样测出来的还是干净数字(无抢占),比在争抢状态下测更有参考价值。

地板值仍值得修(低优先级):max(6.0, ...) 在放不下时会去要一个不存在的额度而不是报错。真要多负载共存时才会再咬人;目前串行跑绕开了。

脚本漂移(已修)

本 README 第九节列了 8 个脚本、并说「换硬件后先跑 probe-* 四个」,但服务器上原本只有 3 个(download.py / t2va.py / verify.py),4 个 probe 从没上过服务器,那条指示在服务器上无法执行。已补齐到 /data/models/minimax-h3/scripts/。

另注:run.sh 在服务器上位于 /data/models/minimax-h3/run.sh(顶层),本地副本放在 scripts/ 下,两边路径不一致。README 第八节的 cd /data/models/minimax-h3 && ./run.sh 1 以服务器为准。


验收记录

2026-08-18 22:31 首次成功出片。 配置:dtype=fp32 · Turbo 4-step · 832×480×124f · GPU1 · vram_limit=7.3G

产物验证(ffprobe + 逐像素 + 音频样本)

项目 实测
文件 outputs/t2va_20260818-223136.mp4 7,859,869 字节 (7.5 MB)
视频流 h264 832×480 · 24 fps · 124 帧 · 5.167 s
实际解码帧数 124(与声明一致,无截断)
像素 mean 69.33 · std 57.85 · 全域 0–255
帧间平均差异 16.99 → 真实动态视频,非静止图
音频流 aac · 32000 Hz · 双声道 · 163 帧 · 5.175 s
音频电平 mean −34.0 dB · max −14.9 dB(静音应为 −91 dB)
音频样本 absmax 0.1799 · rms 0.0199 · 非零样本 100%

验证方法上的教训:ffmpeg -af volumedetect -f null - 的输出容易被 grep 漏掉, 误报成"无音频流"。直接用 PyAV 解码样本算 rms 更可靠。

性能与显存(fp32,全部实测)

分辨率 步数 生成耗时 每步 峰值显存 结果
448×256×28f 4 (turbo) 62 s 15.5 s 16.3 G ✅ std 50.6
640×384×61f 4 (turbo) 171 s 42.8 s 11.4 G ✅ std 51.7
832×480×124f 4 (turbo) 553 s 107 s 12.2 G ✅ std 66.8
832×480×124f 4 (turbo),真实 prompt 556 s 138.9 s 12.3 G ✅ 本次验收

同配置两次每步耗时 107 s vs 138.9 s,差异来自并发:验收那次有另一条 50 步任务 在别的卡上同时跑,抢 CPU 与内存带宽(权重 offload 在 CPU 侧)。单任务跑约 107 s/步。

加载耗时:首次从机械盘读 fp32 权重 238 s;page cache 命中后 fp16 只要 12–15 s。 反复调试时第二次之后极快,不必每次预留 6 分钟。

与 fp16 的对照(唯一变量是 dtype)

fp32 fp16
每步耗时 107 s 36.8 s(快 2.9×)
峰值显存 12.2 G 15.9 G
产物 正常出片 124 帧纯黑 / 音频 NaN

fp16 更快更省显存,但根本出不了片。这不是速度取舍,是能用与不能用的区别。详见上一节。

启动命令(已验证)

cd /data/models/minimax-h3
echo "你的提示词" > prompt.txt          # 从文件读,避开多层 shell 引号
./realrun.sh turbo                      # Turbo 4-step,全分辨率,约 10 分钟
./realrun.sh full50                     # 50 步全质量,约 90 分钟

直接调用也可(--vram-reserve 现按 dtype 自适应,fp32 默认 14):

./run.sh 1 --prompt "$(cat prompt.txt)" --seed 42

prompt 必须从文件读。 直接在 ssh … bash -c "…" 里传含引号的 prompt 会被 三层 shell 吃掉,传进去是空串,报错是 RuntimeError: cannot reshape tensor of 0 elements into shape [1, 0, -1, 128] ——看着像 transformers 的 bug,实际是输入为空。0 元素张量崩在 reshape 是这个的典型指纹。

50 步全质量(2026-08-18 23:51 完成,已验收)

同 prompt、同 seed,--no-turbo --steps 50:

项目 实测
文件 outputs/t2va_20260818-235141.mp4 6,726,194 字节 (6.4 MB)
生成耗时 5364 s(89 分钟)· 107.3 s/步
峰值显存 9.1 G(比 turbo 的 12.3 G 更低——无 LoRA 层)
视频 h264 832×480 · 24 fps · 124 帧 · 5.167 s
像素 mean 71.63 · std 59.68 · 全域 0–255
帧间平均差异 7.366
音频 mean −27.2 dB · max −9.2 dB · 331776 样本

与 Turbo 4-step 的对比:

Turbo 4-step 50 步全质量
耗时 556 s 5364 s(9.6×)
峰值显存 12.3 G 9.1 G
像素 std 57.85 59.68
帧间差异 16.99 7.366
音频电平 −34.0 / −14.9 dB −27.2 / −9.2 dB

50 步的帧间差异明显更小、音频更响。帧间差异小不等于质量差——更多去噪步数带来更平滑的时序连贯性; Turbo 的 16.99 里有一部分是 4 步蒸馏残留的帧间抖动。哪个观感更好需要人眼判断, 这两条客观指标只能说明两者都是真实动态视频,且 50 步的时序更稳。

每步耗时 107.3 s 与单任务 turbo 的 107 s 完全一致——证明这个数字是这台机器上 fp32 全分辨率的稳定值,不随步数或 LoRA 变化。