MiniMax H3: VAEDecodeAudio always returns a constant DC value (-1.0) or silence — video is correct; bf16, --cpu-vae and cu128 all reproduce
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 158
Description
### Expected Behavior
`VAEDecodeAudio` on a MiniMax H3 sampled latent should produce the native audio track (speech / soundscape / score) described in the prompt, as it does in the bundled `video_minimax_h3_i2v` / `_t2v` templates.
### Actual Behavior
The decoded audio is never valid. Every sample in the waveform is a single constant value.
| Server flags | Decoded audio |
|---|---|
| `--use-sage-attention --disable-dynamic-vram` | every sample `-1.0` (full-scale DC, `rms = 1.0`) |
| no flags | every sample `0.0` (pure silence) |
Because the buffer is a full-scale DC constant, `SaveVideo` then fails at the mux step:
```
av.error.ArgumentError: [Errno 22] Invalid argument: 'avcodec_send_frame()' returned 22
```
Existing reports (#14617, #15040, #15614) describe *noisy / distorted / popping* audio — i.e. audio that is generated but degraded. This is different: no audio is produced at all. Note also that the payload contains **no NaN and no Inf** (verified with `soundfile` + `numpy`), so this is not the NaN case discussed in #14617.
**The video output is completely correct** in every run — correct motion, correct duration, correct first-frame adherence. Only the audio branch is dead.
### Steps to Reproduce
Run the bundled `video_minimax_h3_i2v` template (or the flattened API graph below), then inspect the `VAEDecodeAudio` output saved via `SaveAudio` rather than through `SaveVideo`:
```python
import numpy as np, soundfile as sf
a, sr = sf.read("cm_greet_audio_00001.flac")
print(a.shape, sr, np.isnan(a).sum(), a.min(), a.max(), np.sqrt((a**2).mean()))
# (301600, 2) 32000 0 -1.0 -1.0 1.0
```
Flattened graph (identical wiring to the bundled template — verified by expanding its subgraph):
```python
{
"1": {"class_type": "LoadImage", "inputs": {"image": "first_frame.png"}},
"2": {"class_type": "UNETLoader", "inputs": {"unet_name": "minimax_h3_fl2va_pruned_int8_convrot.safetensors", "weight_dtype": "default"}},
"3": {"class_type": "CLIPLoader", "inputs": {"clip_name": "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors", "type": "minimax", "device": "default"}},
"4": {"class_type": "VAELoader", "inputs": {"vae_name": "minimax_h3_video_vae_fp16.safetensors"}},
"5": {"class_type": "VAELoader", "inputs": {"vae_name": "minimax_h3_audio_vae_fp32.safetensors"}},
"6": {"class_type": "MiniMaxH3ImageToVideo", "inputs": {"clip": ["3",0], "vae": ["4",0], "prompt": PROMPT,
"width": 768, "height": 768, "length": 226, "first_frame": ["1",0]}},
"7": {"class_type": "KSamplerSelect", "inputs": {"sampler_name": "res_multistep"}},
"8": {"class_type": "BasicScheduler", "inputs": {"model": ["2",0], "scheduler": "simple", "steps": 20, "denoise": 1}},
"9": {"class_type": "RandomNoise", "inputs": {"noise_seed": 42}},
"10": {"class_type": "BasicGuider", "inputs": {"model": ["2",0], "conditioning": ["6",0]}},
"11": {"class_type": "SamplerCustomAdvanced", "inputs": {"noise": ["9",0], "guider": ["10",0], "sampler": ["7",0],
"sigmas": ["8",0], "latent_image": ["6",1]}},
"12": {"class_type": "VAEDecode", "inputs": {"samples": ["11",0], "vae": ["4",0]}},
"13": {"class_type": "VAEDecodeAudio", "inputs": {"samples": ["11",0], "vae": ["5",0]}},
"16": {"class_type": "SaveAudio", "inputs": {"audio": ["13",0], "filename_prefix": "h3_audio"}},
}
```
The prompt follows the official base contract (alignment line + `integrated_multimodal_description` / `overall_soundscape` / `non_diegetic_music`) with Korean dialogue in `[Korean] ...` and a non-empty soundscape and score, so the request is clearly asking for audio.
### Variables ruled out
Every one of these reproduces the identical constant-DC output:
| Variable | Tried | Result |
|---|---|---|
| Checkpoint | `fl2va_pruned_int8_convrot`, `fl2va_int8_convrot`, **`fl2va_bf16`** | all fail |
| Mode | I2V (`first_frame` set) and T2V (`first_frame` omitted) | all fail |
| `length` | 56, 124, 226 (all on the 17k+5 grid) | all fail |
| Seed | several | all fail |
| Sampler | `res_multistep`, `euler`, `dpmpp_2m`, `ddim` | all fail |
| Text encoder | `qwen3vl_32b_minimax_h3_nvfp4_awq`, **`qwen3vl_32b_minimax_h3_bf16`** | all fail |
| venv / CUDA | cu130 + sage instance, **cu128 no-flag instance** | all fail |
| VAE device | GPU, **`--cpu-vae`** | all fail |
| Audio VAE file | header verified: 1087 F32 tensors, encoder+decoder present | file is intact |
`--cpu-vae` still failing is the key data point: this is **not** a GPU kernel or Blackwell-specific issue (cf. #15337). The audio latent is already degenerate before it reaches the VAE.
Switching quantization (int8_convrot → bf16) and switching CUDA toolkit / venv also change nothing, which rules out the INT8 ConvRot concerns raised in #15760.
### Environment
```
ComfyUI 0.33.1 (72865f4f27eaf5396f8f36370e0a2be3a9a090ee)
OS Windows 11 Pro 26200
GPU NVIDIA RTX PRO 6000 Blackwell, 96 GB
Python venvs cu130 (torch, sageattention) and cu128 — both reproduce
Weights Comfy-Org MiniMax-H3 (fl2va int8_convrot / pruned / bf16)
minimax_h3_video_vae_fp16 / minimax_h3_audio_vae_fp32
```
Already present in 0.33.1 and therefore not the fix: #15390, #15377, #15243, #15502.
### Additional note
On the same machine, with the same MiniMax H3 weights, audio reportedly generates correctly under a different local runner (a Pinokio-installed H3 app). That suggests the weights and the hardware are fine and the problem is specific to the ComfyUI audio path.
Happy to run any additional diagnostic — dumping the raw audio latent statistics before `VAEDecodeAudio`, testing a patch, or trying whatever the AV latent builder work in #15735 lands as.
Contributor guide
Research direction
Start by reproducing the flattened graph or bundled MiniMax H3 templates and inspect the raw audio latent immediately before VAEDecodeAudio, then compare its output with SaveAudio and SaveVideo. The issue is done when the audio waveform is non-constant and valid, and SaveVideo completes its mux step without an avcodec_send_frame error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- ai, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100