MiniMax H3 AudioVAE input is center-cropped, dropping leading audio samples and producing incorrect latent lengths
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 158
Description
### Custom Node Testing
- [x] I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)
### Expected Behavior
MiniMax H3 audio should reach MiniMaxH3AudioVAE.encode() without generic center-cropping.
The H3 AudioVAE already handles non-aligned audio lengths itself by right-padding to the next 800-sample boundary, so the beginning of the input waveform should be preserved.
### Actual Behavior
ComfyUI's generic VAE preprocessing first center-crops H3 audio down to a multiple of 800 samples.
For example, an input of 437333 samples is cropped to 436800 samples before reaching MiniMaxH3AudioVAE.encode().
This removes approximately 266 samples from the beginning (~8.3 ms at 32 kHz) and causes the encoder to produce 546 audio latent steps instead of the 547 steps its own right-padding behavior would produce.
### Steps to Reproduce
Start the latest ComfyUI with all custom nodes disabled.
Create or use a 32 kHz WAV file whose sample count is not divisible by 800.
For example, a 437333-sample file is about 13.67 seconds long.
Load minimax_h3_audio_vae_fp32.safetensors with the stock VAELoader.
Use the audio as a reference input to the stock MiniMax H3 Reference to Video node. This node passes reference audio through the normal ComfyUI H3 AudioVAE encode path.
Trace/log the waveform shape entering MiniMaxH3AudioVAE.encode().
For a 437333-sample input, the AudioVAE receives only:
436800 samples
rather than the original 437333 samples.
The missing 533 samples are removed by the generic VAE preprocessing before the H3 AudioVAE is called. Because this is a centered crop, roughly 266 samples are removed from the beginning.
MiniMaxH3AudioVAE.encode() itself is designed to receive the complete waveform and right-pad it to an 800-sample boundary.
### Debug Logs
```powershell
python main.py \
--listen \
--disable-all-custom-nodes \
--log-stdout
[INFO] setup plugin alembic.autogenerate.schemas
[INFO] setup plugin alembic.autogenerate.tables
[INFO] setup plugin alembic.autogenerate.types
[INFO] setup plugin alembic.autogenerate.constraints
[INFO] setup plugin alembic.autogenerate.defaults
[INFO] setup plugin alembic.autogenerate.comments
[INFO] Found comfy_kitchen backend cuda: {'available': True, 'disabled': False, 'unavailable_reason': None, 'capabilities': ['adaln', 'apply_rope', 'apply_rope1', 'apply_rope1_', 'apply_rope_', 'apply_rope_split_half', 'apply_rope_split_half1', 'apply_rope_split_half1_', 'apply_rope_split_half_', 'convrot_w4a4_linear', 'dequantize_convrot_w4a4_weight', 'dequantize_int8_convrot_weight', 'dequantize_int8_convrot_weight_dtype', 'dequantize_int8_simple', 'dequantize_int8_simple_dtype', 'dequantize_nvfp4', 'dequantize_per_tensor_fp8', 'dequantize_w4a8_int8_weight', 'gemv_awq_w4a16', 'int8_linear', 'na3d', 'prepare_int4_weight_for_int8_linear', 'quantize_and_rotate_rowwise', 'quantize_convrot_w4a4_weight', 'quantize_int8_convrot_weight', 'quantize_int8_rowwise', 'quantize_int8_tensorwise', 'quantize_mxfp8', 'quantize_nvfp4', 'quantize_per_tensor_fp8', 'quantize_svdquant_w4a4', 'quantize_w4a8_int8_weight', 'rms_adaln', 'rms_rope', 'rms_rope1', 'rms_rope1_', 'rms_rope_', 'rms_rope_split_half', 'rms_rope_split_half1', 'rms_rope_split_half1_', 'rms_rope_split_half_', 'rotate_int8_convrot_weight', 'scaled_mm_nvfp4', 'scaled_mm_svdquant_w4a4', 'stochastic_rounding_fp8', 'w4a8_int8_linear']}
[INFO] Found comfy_kitchen backend triton: {'available': True, 'disabled': True, 'unavailable_reason': None, 'capabilities': ['adaln', 'apply_rope', 'apply_rope1', 'apply_rope1_', 'apply_rope_', 'apply_rope_split_half', 'apply_rope_split_half1', 'apply_rope_split_half1_', 'apply_rope_split_half_', 'dequantize_nvfp4', 'dequantize_per_tensor_fp8', 'int8_linear', 'na3d', 'quantize_and_rotate_rowwise', 'quantize_int8_rowwise', 'quantize_mxfp8', 'quantize_nvfp4', 'quantize_per_tensor_fp8', 'rms_adaln', 'rms_rope', 'rms_rope1', 'rms_rope1_', 'rms_rope_', 'rms_rope_split_half', 'rms_rope_split_half1', 'rms_rope_split_half1_', 'rms_rope_split_half_', 'w4a8_int8_linear']}
[INFO] Found comfy_kitchen backend eager: {'available': True, 'disabled': False, 'unavailable_reason': None, 'capabilities': ['adaln', 'apply_rope', 'apply_rope1', 'apply_rope1_', 'apply_rope_', 'apply_rope_split_half', 'apply_rope_split_half1', 'apply_rope_split_half1_', 'apply_rope_split_half_', 'convrot_w4a4_linear', 'dequantize_convrot_w4a4_weight', 'dequantize_int8_convrot_weight', 'dequantize_int8_convrot_weight_dtype', 'dequantize_int8_embedding', 'dequantize_int8_simple', 'dequantize_int8_simple_dtype', 'dequantize_mxfp8', 'dequantize_nvfp4', 'dequantize_per_tensor_fp8', 'dequantize_w4a8_int8_weight', 'gemv_awq_w4a16', 'int8_linear', 'na3d', 'prepare_int4_weight_for_int8_linear', 'quantize_and_rotate_rowwise', 'quantize_convrot_w4a4_weight', 'quantize_int8_convrot_weight', 'quantize_int8_rowwise', 'quantize_int8_tensorwise', 'quantize_mxfp8', 'quantize_nvfp4', 'quantize_per_tensor_fp8', 'quantize_svdquant_w4a4', 'quantize_w4a8_int8_weight', 'rms_adaln', 'rms_rope', 'rms_rope1', 'rms_rope1_', 'rms_rope_', 'rms_rope_split_half', 'rms_rope_split_half1', 'rms_rope_split_half1_', 'rms_rope_split_half_', 'rotate_int8_convrot_weight', 'scaled_mm_mxfp8', 'scaled_mm_nvfp4', 'scaled_mm_svdquant_w4a4', 'stochastic_rounding_fp8', 'w4a8_int8_linear']}
[INFO] Found comfy_kitchen backend hip: {'available': False, 'disabled': False, 'unavailable_reason': 'PyTorch ROCm/HIP runtime not available', 'capabilities': []}
[INFO] Checkpoint files will always be loaded safely.
[INFO] Total VRAM 32111 MB, total RAM 256808 MB
[INFO] pytorch version: 2.13.0+cu130
[INFO] Set vram state to: NORMAL_VRAM
[INFO] Device: cuda:0 NVIDIA GeForce RTX 5090 : cudaMallocAsync
[INFO] Using async weight offloading with 2 streams
[INFO] Enabled pinned memory 231127.0
[INFO] Using pytorch attention
[INFO] aimdo: /project/src/control.c:276:INFO:comfy-aimdo inited for GPU: NVIDIA GeForce RTX 5090 (VRAM: 32110 MB)
[INFO] DynamicVRAM support detected and enabled
[INFO] Python version: 3.13.14 | packaged by Anaconda, Inc. | (main, Jul 9 2026, 14:36:31) [GCC 14.3.0]
[INFO] ComfyUI version: 0.34.0
[INFO] comfy-aimdo version: 0.4.15
[INFO] comfy-kitchen version: 0.2.31
[INFO] comfyui-frontend-package version: 1.51.9
[INFO] comfyui-workflow-templates version: 0.11.50
[INFO] comfyui-embedded-docs version: 0.5.10
[INFO] comfy-kitchen version: 0.2.31
[INFO] comfy-aimdo version: 0.4.15
[INFO] Asset seeder disabled
[INFO] No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
[INFO] Skipping loading of custom nodes
[INFO] Context impl SQLiteImpl.
[INFO] Will assume non-transactional DDL.
[INFO] Using RAM pressure cache.
[INFO] Starting server
[INFO] got prompt
[INFO] VAE load device: cuda:0, offload device: cpu, dtype: torch.float32
[INFO] Requested to load MiniMaxH3AudioVAE
[INFO] Model MiniMaxH3AudioVAE prepared for dynamic VRAM loading. 576MB Staged. 0 patches attached. Force pre-loaded 401 weights: 539 KB.
[INFO] Model MiniMaxH3AudioVAE prepared for dynamic VRAM loading. 576MB Staged. 0 patches attached. Force pre-loaded 401 weights: 539 KB.
[INFO] Prompt executed in 1.21 seconds
python - <<'PY'
import av
from pathlib import Path
files = [
Path("input/h3_audio_crop_repro_437333.wav"),
max(Path("output/h3_crop_repro").glob("decoded*.flac"),
key=lambda p: p.stat().st_mtime),
]
for path in files:
container = av.open(str(path))
stream = container.streams.audio[0]
samples = 0
sample_rate = stream.codec_context.sample_rate
for frame in container.decode(stream):
samples += frame.samples
print(path)
print(f" sample_rate: {sample_rate}")
print(f" samples: {samples}")
print(f" duration: {samples / sample_rate:.9f} s")
PY
input/h3_audio_crop_repro_437333.wav
sample_rate: 32000
samples: 437333
duration: 13.666656250 s
output/h3_crop_repro/decoded_00001.flac
sample_rate: 32000
samples: 436800
duration: 13.650000000 s
```
### Other
I found this while debugging an H3 V2V workflow where the target required 547 audio latent steps but source-audio encoding produced only 546.
The initial workaround was to append one zero latent step. Tracing the encode path showed that the mismatch originates earlier: the generic VAE crop happens before the H3 AudioVAE gets the waveform, and also shifts the beginning of the audio.
A possible narrow fix is to disable generic VAE input cropping specifically for the MiniMax H3 audio VAE:
self.crop_input = False
This would allow MiniMaxH3AudioVAE.encode() to perform its existing model-specific right-padding without changing generic VAE behavior for other models.
Contributor guide
Research direction
Start at MiniMaxH3AudioVAE.encode() and trace the generic VAE preprocessing that receives the 32 kHz waveform before it. Reproduce with the 437333-sample WAV and --disable-all-custom-nodes; done means the full waveform reaches encode unchanged and the existing right-padding produces 547 latent steps without changing other VAEs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- audio-video-rtc, backend, machine-learning
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100