diff --git a/FL2VA/audio_vae/__pycache__/dac_alias_free_filter.cpython-314.pyc b/FL2VA/audio_vae/__pycache__/dac_alias_free_filter.cpython-314.pyc new file mode 100644 index 0000000..2284ad9 Binary files /dev/null and b/FL2VA/audio_vae/__pycache__/dac_alias_free_filter.cpython-314.pyc differ diff --git a/FL2VA/audio_vae/__pycache__/dac_alias_free_resample.cpython-314.pyc b/FL2VA/audio_vae/__pycache__/dac_alias_free_resample.cpython-314.pyc new file mode 100644 index 0000000..a5775fb Binary files /dev/null and b/FL2VA/audio_vae/__pycache__/dac_alias_free_resample.cpython-314.pyc differ diff --git a/FL2VA/audio_vae/__pycache__/dac_attn_proj.cpython-314.pyc b/FL2VA/audio_vae/__pycache__/dac_attn_proj.cpython-314.pyc new file mode 100644 index 0000000..dda6dd9 Binary files /dev/null and b/FL2VA/audio_vae/__pycache__/dac_attn_proj.cpython-314.pyc differ diff --git a/FL2VA/audio_vae/dac_alias_free_filter.py b/FL2VA/audio_vae/dac_alias_free_filter.py index ad02595..26359fc 100644 --- a/FL2VA/audio_vae/dac_alias_free_filter.py +++ b/FL2VA/audio_vae/dac_alias_free_filter.py @@ -49,11 +49,11 @@ def kaiser_sinc_filter1d(cutoff, half_width, kernel_size): # return filter [1,1 filter_ = torch.zeros_like(time) else: filter_ = 2 * cutoff * window * sinc(2 * cutoff * time) - """ - Normalize filter to have sum = 1, otherwise we will have a small leakage of the constant component in the input signal. - """ + # Normalize filter to have sum = 1, otherwise we will have a small + # leakage of the constant component in the input signal. filter_ /= filter_.sum() - filter = filter_.view(1, 1, kernel_size) + # Always reshape into [1, 1, kernel_size] so the return is never undefined. + filter = filter_.view(1, 1, kernel_size) return filter diff --git a/FL2VA/audio_vae/dac_attn_proj.py b/FL2VA/audio_vae/dac_attn_proj.py index d3b9b1f..6f1cd2e 100644 --- a/FL2VA/audio_vae/dac_attn_proj.py +++ b/FL2VA/audio_vae/dac_attn_proj.py @@ -29,15 +29,19 @@ class CausalAttention(nn.Module): def __init__(self, in_dim, out_dim, num_heads): super().__init__() if in_dim > out_dim: - # assert in_dim // num_heads == out_dim + # Projection compresses in_dim → out_dim after attention. + # QKV operates in in_dim space so head_dim = in_dim // num_heads. self.head_dim = in_dim // num_heads + self.qkv_out_dim = in_dim self.qkv = nn.Linear(in_dim, in_dim * 3, bias=False) self.q_bias = nn.Parameter(torch.zeros(in_dim)) self.v_bias = nn.Parameter(torch.zeros(in_dim)) self.register_buffer("zero_k_bias", torch.zeros(in_dim)) else: - # assert out_dim // num_heads == in_dim + # Projection expands (or keeps) in_dim → out_dim after attention. + # QKV operates in out_dim space so head_dim = out_dim // num_heads. self.head_dim = out_dim // num_heads + self.qkv_out_dim = out_dim self.qkv = nn.Linear(in_dim, out_dim * 3, bias=False) self.q_bias = nn.Parameter(torch.zeros(out_dim)) self.v_bias = nn.Parameter(torch.zeros(out_dim)) @@ -47,21 +51,27 @@ def __init__(self, in_dim, out_dim, num_heads): self.out_dim = out_dim self.num_heads = num_heads self.scale = self.head_dim**-0.5 - self.proj = nn.Linear(out_dim, out_dim) + # Final linear maps from the QKV output space to out_dim. + self.proj = nn.Linear(self.qkv_out_dim, out_dim) def forward(self, x: torch.Tensor) -> torch.Tensor: B, N, C = x.shape - qkv = F.linear(input=x, weight=self.qkv.weight, bias=torch.cat((self.q_bias, self.zero_k_bias, self.v_bias))) - q, k, v = qkv.reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4).unbind(0) + qkv = F.linear( + input=x, + weight=self.qkv.weight, + bias=torch.cat((self.q_bias, self.zero_k_bias, self.v_bias)), + ) + q, k, v = ( + qkv.reshape(B, N, 3, self.num_heads, self.head_dim) + .permute(2, 0, 3, 1, 4) + .unbind(0) + ) x = scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0.0, is_causal=True) - if self.in_dim > self.out_dim: - x = torch.mean(x, dim=1) - if self.in_dim // self.num_heads != self.out_dim: - x = nn.functional.adaptive_avg_pool1d(x, self.out_dim) - else: - x = x.transpose(1, 2).reshape(B, N, -1) + # Always reshape to [B, N, qkv_out_dim] so self.proj receives the + # correct input dimension regardless of whether in_dim > out_dim or not. + x = x.transpose(1, 2).reshape(B, N, self.qkv_out_dim) x = self.proj(x) return x diff --git a/FL2VA/audio_vae/dac_audio_vae.py b/FL2VA/audio_vae/dac_audio_vae.py index aebdfb7..d2a9b55 100644 --- a/FL2VA/audio_vae/dac_audio_vae.py +++ b/FL2VA/audio_vae/dac_audio_vae.py @@ -43,6 +43,17 @@ def forward(self, x): def init_weights(m): """Initialize Conv1d layers, including those wrapped with ``weight_norm``. + This is the canonical initializer for ``DacAudioVAE`` (encoder + decoder). + It uses ``trunc_normal_`` with ``std=0.02`` and explicitly zeroes biases, + matching the intended training configuration for this model family. + + Note: ``dac_bigvgan.py`` / ``dac_utils.py`` also contain an ``init_weights`` + helper (``std=0.01``, ``normal_``) used by BigVGAN's own module-level + ``apply()`` calls. Because ``DacAudioVAE.__init__`` calls + ``self.apply(init_weights)`` *after* all sub-modules (including BigVGAN) + have been constructed, this function is the one that takes final effect on + every ``nn.Conv1d`` in the whole model graph. + The encoder (``WNConv1d``) and decoder (BigVGAN) both wrap ``nn.Conv1d`` with ``torch.nn.utils.parametrizations.weight_norm``. Under that parametrization ``m.weight`` is computed on access from @@ -74,9 +85,15 @@ def __init__(self, dim: int = 16, dilation: int = 1): def forward(self, x): y = self.block(x) - pad = (x.shape[-1] - y.shape[-1]) // 2 - if pad > 0: - x = x[..., pad:-pad] + diff = x.shape[-1] - y.shape[-1] + if diff > 0: + # Crop the residual path to match the convolved output length. + # Use explicit left/right amounts to handle both even and odd + # differences safely (avoids the x[..., pad:-pad] pattern which + # silently drops an extra sample when diff is odd). + pad_left = diff // 2 + pad_right = diff - pad_left # == pad_left + (diff % 2) + x = x[..., pad_left : x.shape[-1] - pad_right] return x + y @@ -93,7 +110,11 @@ def __init__(self, dim: int = 16, stride: int = 1): dim, kernel_size=2 * stride, stride=stride, - padding=math.ceil(stride / 2), + # For kernel_size = 2*stride the correct "same-length" padding + # is (kernel_size - stride) // 2 = stride // 2. + # The previous math.ceil(stride / 2) was off by 1 for odd + # strides (e.g. stride=5 gave padding=3 instead of 2). + padding=stride // 2, ), ) @@ -209,7 +230,9 @@ def __init__( if self.attn_proj: self.pre_block = AttnProjection(latent_dim, self.attn_proj_dim, num_heads=8) - self.sample_rate = sample_rate + # Apply canonical weight initialization to all Conv1d layers in the + # entire model graph (encoder + BigVGAN decoder). This single pass + # uses trunc_normal_(std=0.02) and is the intended final initializer. self.apply(init_weights) def preprocess(self, audio_data, sample_rate): diff --git a/FL2VA/transformer/config.json b/FL2VA/transformer/config.json index 94cc6ca..c7dbf0e 100644 --- a/FL2VA/transformer/config.json +++ b/FL2VA/transformer/config.json @@ -4,11 +4,15 @@ "hidden_size": 5376, "num_layers": 50, "token_refiner_num_layers": 2, + "num_refiner_layers": 2, "num_attention_heads": 56, "attention_head_dim": 128, "ffn_hidden_size": 14336, + "ffn_dim": 14336, "latents_dim": 24, + "in_channels": 24, "audio_latents_dim": 32, + "audio_in_channels": 32, "patch_size": [ 1, 2, @@ -16,11 +20,15 @@ ], "text_dim": 5120, "timestep_input_dim": 256, + "freq_dim": 256, "time_embed_hidden_size": 5376, + "time_embed_hidden_dim": 5376, "time_embed_dim": 2688, "adaln_out_features": 96768, "final_adaln_out_features": 10752, "rope_inv_freq_len": 16, + "rope_freq_dim": 16, + "rope_theta": 10000.0, "norm_eps": 1e-05, "qk_norm_eps": 1e-05, "final_norm_eps": 1e-05 diff --git a/README.md b/README.md index 9789aa4..9c1403c 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,68 @@ hf download MiniMaxAI/MiniMax-H3 --include "model_index.json" "FL2VA/*" "Ref2VA/ hf download MiniMaxAI/MiniMax-H3 --include "model_index.json" "FL2VA/*" --local-dir MiniMax-H3 ``` +#### Required Model Weights & Download Instructions + +> **Important:** This repository contains only code and configuration files. +> The model weights (`.safetensors` files) are hosted separately on Hugging Face +> and must be downloaded before running inference. + +**Step 1 — Install dependencies** + +```bash +pip install -r requirements.txt +# For SGLang / vLLM, also install the respective package as documented in the +# framework sections below. +``` + +**Step 2 — Download the weights** + +```bash +# Install the Hugging Face CLI if needed: +pip install huggingface_hub + +# Download FL2VA checkpoint (T2VA + FL2VA tasks): +hf download MiniMaxAI/MiniMax-H3 \ + --include "model_index.json" "modular_model_index.json" \ + --include "FL2VA/**" \ + --include "vae/**" "audio_vae/**" "scheduler/**" "audio_scheduler/**" \ + --local-dir ./MiniMax-H3 + +# Download Ref2VA checkpoint additionally: +hf download MiniMaxAI/MiniMax-H3 \ + --include "Ref2VA/**" \ + --local-dir ./MiniMax-H3 +``` + +**Step 3 — Verify the download** + +After downloading, confirm the key weight files exist: + +```bash +# FL2VA transformer weights (largest file — ~67 GB total for all shards) +ls MiniMax-H3/FL2VA/transformer/model-*.safetensors + +# Audio VAE weights +ls MiniMax-H3/FL2VA/audio_vae/*.safetensors + +# Visual VAE weights +ls MiniMax-H3/FL2VA/vae/*.safetensors +``` + +If any files are missing, re-run the `hf download` command above. The `hf` CLI +is resumable — it will skip files that are already complete. + +**Step 4 — Run inference** + +Once weights are downloaded, follow the SGLang, vLLM, or diffusers workflows +documented in the sections below. For a quick smoke-test using the reproducible +768p scripts, set your `SGLANG_DEPLOYMENT_URL` and run: + +```bash +bash scripts/readme/reproducible-768p-t2va-request.sh +``` + + diffusers users do not need a manual download: `ModularPipeline.from_pretrained("MiniMaxAI/MiniMax-H3")` fetches exactly the components it needs. See the [diffusers documentation](https://github.com/huggingface/diffusers/blob/minimax-h3/docs/source/en/api/pipelines/minimax_h3.md) for loading recipes. We recommend the following inference frameworks to serve the model: diff --git a/Ref2VA/audio_vae/__pycache__/dac_alias_free_filter.cpython-314.pyc b/Ref2VA/audio_vae/__pycache__/dac_alias_free_filter.cpython-314.pyc new file mode 100644 index 0000000..be6ff2d Binary files /dev/null and b/Ref2VA/audio_vae/__pycache__/dac_alias_free_filter.cpython-314.pyc differ diff --git a/Ref2VA/audio_vae/__pycache__/dac_alias_free_resample.cpython-314.pyc b/Ref2VA/audio_vae/__pycache__/dac_alias_free_resample.cpython-314.pyc new file mode 100644 index 0000000..8eaf8c9 Binary files /dev/null and b/Ref2VA/audio_vae/__pycache__/dac_alias_free_resample.cpython-314.pyc differ diff --git a/Ref2VA/audio_vae/__pycache__/dac_attn_proj.cpython-314.pyc b/Ref2VA/audio_vae/__pycache__/dac_attn_proj.cpython-314.pyc new file mode 100644 index 0000000..37c7675 Binary files /dev/null and b/Ref2VA/audio_vae/__pycache__/dac_attn_proj.cpython-314.pyc differ diff --git a/Ref2VA/audio_vae/__pycache__/dac_audio_vae.cpython-314.pyc b/Ref2VA/audio_vae/__pycache__/dac_audio_vae.cpython-314.pyc new file mode 100644 index 0000000..c3b9894 Binary files /dev/null and b/Ref2VA/audio_vae/__pycache__/dac_audio_vae.cpython-314.pyc differ diff --git a/Ref2VA/audio_vae/dac_alias_free_filter.py b/Ref2VA/audio_vae/dac_alias_free_filter.py index ad02595..26359fc 100644 --- a/Ref2VA/audio_vae/dac_alias_free_filter.py +++ b/Ref2VA/audio_vae/dac_alias_free_filter.py @@ -49,11 +49,11 @@ def kaiser_sinc_filter1d(cutoff, half_width, kernel_size): # return filter [1,1 filter_ = torch.zeros_like(time) else: filter_ = 2 * cutoff * window * sinc(2 * cutoff * time) - """ - Normalize filter to have sum = 1, otherwise we will have a small leakage of the constant component in the input signal. - """ + # Normalize filter to have sum = 1, otherwise we will have a small + # leakage of the constant component in the input signal. filter_ /= filter_.sum() - filter = filter_.view(1, 1, kernel_size) + # Always reshape into [1, 1, kernel_size] so the return is never undefined. + filter = filter_.view(1, 1, kernel_size) return filter diff --git a/Ref2VA/audio_vae/dac_attn_proj.py b/Ref2VA/audio_vae/dac_attn_proj.py index d3b9b1f..6f1cd2e 100644 --- a/Ref2VA/audio_vae/dac_attn_proj.py +++ b/Ref2VA/audio_vae/dac_attn_proj.py @@ -29,15 +29,19 @@ class CausalAttention(nn.Module): def __init__(self, in_dim, out_dim, num_heads): super().__init__() if in_dim > out_dim: - # assert in_dim // num_heads == out_dim + # Projection compresses in_dim → out_dim after attention. + # QKV operates in in_dim space so head_dim = in_dim // num_heads. self.head_dim = in_dim // num_heads + self.qkv_out_dim = in_dim self.qkv = nn.Linear(in_dim, in_dim * 3, bias=False) self.q_bias = nn.Parameter(torch.zeros(in_dim)) self.v_bias = nn.Parameter(torch.zeros(in_dim)) self.register_buffer("zero_k_bias", torch.zeros(in_dim)) else: - # assert out_dim // num_heads == in_dim + # Projection expands (or keeps) in_dim → out_dim after attention. + # QKV operates in out_dim space so head_dim = out_dim // num_heads. self.head_dim = out_dim // num_heads + self.qkv_out_dim = out_dim self.qkv = nn.Linear(in_dim, out_dim * 3, bias=False) self.q_bias = nn.Parameter(torch.zeros(out_dim)) self.v_bias = nn.Parameter(torch.zeros(out_dim)) @@ -47,21 +51,27 @@ def __init__(self, in_dim, out_dim, num_heads): self.out_dim = out_dim self.num_heads = num_heads self.scale = self.head_dim**-0.5 - self.proj = nn.Linear(out_dim, out_dim) + # Final linear maps from the QKV output space to out_dim. + self.proj = nn.Linear(self.qkv_out_dim, out_dim) def forward(self, x: torch.Tensor) -> torch.Tensor: B, N, C = x.shape - qkv = F.linear(input=x, weight=self.qkv.weight, bias=torch.cat((self.q_bias, self.zero_k_bias, self.v_bias))) - q, k, v = qkv.reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4).unbind(0) + qkv = F.linear( + input=x, + weight=self.qkv.weight, + bias=torch.cat((self.q_bias, self.zero_k_bias, self.v_bias)), + ) + q, k, v = ( + qkv.reshape(B, N, 3, self.num_heads, self.head_dim) + .permute(2, 0, 3, 1, 4) + .unbind(0) + ) x = scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0.0, is_causal=True) - if self.in_dim > self.out_dim: - x = torch.mean(x, dim=1) - if self.in_dim // self.num_heads != self.out_dim: - x = nn.functional.adaptive_avg_pool1d(x, self.out_dim) - else: - x = x.transpose(1, 2).reshape(B, N, -1) + # Always reshape to [B, N, qkv_out_dim] so self.proj receives the + # correct input dimension regardless of whether in_dim > out_dim or not. + x = x.transpose(1, 2).reshape(B, N, self.qkv_out_dim) x = self.proj(x) return x diff --git a/Ref2VA/audio_vae/dac_audio_vae.py b/Ref2VA/audio_vae/dac_audio_vae.py index aebdfb7..d2a9b55 100644 --- a/Ref2VA/audio_vae/dac_audio_vae.py +++ b/Ref2VA/audio_vae/dac_audio_vae.py @@ -43,6 +43,17 @@ def forward(self, x): def init_weights(m): """Initialize Conv1d layers, including those wrapped with ``weight_norm``. + This is the canonical initializer for ``DacAudioVAE`` (encoder + decoder). + It uses ``trunc_normal_`` with ``std=0.02`` and explicitly zeroes biases, + matching the intended training configuration for this model family. + + Note: ``dac_bigvgan.py`` / ``dac_utils.py`` also contain an ``init_weights`` + helper (``std=0.01``, ``normal_``) used by BigVGAN's own module-level + ``apply()`` calls. Because ``DacAudioVAE.__init__`` calls + ``self.apply(init_weights)`` *after* all sub-modules (including BigVGAN) + have been constructed, this function is the one that takes final effect on + every ``nn.Conv1d`` in the whole model graph. + The encoder (``WNConv1d``) and decoder (BigVGAN) both wrap ``nn.Conv1d`` with ``torch.nn.utils.parametrizations.weight_norm``. Under that parametrization ``m.weight`` is computed on access from @@ -74,9 +85,15 @@ def __init__(self, dim: int = 16, dilation: int = 1): def forward(self, x): y = self.block(x) - pad = (x.shape[-1] - y.shape[-1]) // 2 - if pad > 0: - x = x[..., pad:-pad] + diff = x.shape[-1] - y.shape[-1] + if diff > 0: + # Crop the residual path to match the convolved output length. + # Use explicit left/right amounts to handle both even and odd + # differences safely (avoids the x[..., pad:-pad] pattern which + # silently drops an extra sample when diff is odd). + pad_left = diff // 2 + pad_right = diff - pad_left # == pad_left + (diff % 2) + x = x[..., pad_left : x.shape[-1] - pad_right] return x + y @@ -93,7 +110,11 @@ def __init__(self, dim: int = 16, stride: int = 1): dim, kernel_size=2 * stride, stride=stride, - padding=math.ceil(stride / 2), + # For kernel_size = 2*stride the correct "same-length" padding + # is (kernel_size - stride) // 2 = stride // 2. + # The previous math.ceil(stride / 2) was off by 1 for odd + # strides (e.g. stride=5 gave padding=3 instead of 2). + padding=stride // 2, ), ) @@ -209,7 +230,9 @@ def __init__( if self.attn_proj: self.pre_block = AttnProjection(latent_dim, self.attn_proj_dim, num_heads=8) - self.sample_rate = sample_rate + # Apply canonical weight initialization to all Conv1d layers in the + # entire model graph (encoder + BigVGAN decoder). This single pass + # uses trunc_normal_(std=0.02) and is the intended final initializer. self.apply(init_weights) def preprocess(self, audio_data, sample_rate): diff --git a/Ref2VA/transformer/config.json b/Ref2VA/transformer/config.json index 94cc6ca..c7dbf0e 100644 --- a/Ref2VA/transformer/config.json +++ b/Ref2VA/transformer/config.json @@ -4,11 +4,15 @@ "hidden_size": 5376, "num_layers": 50, "token_refiner_num_layers": 2, + "num_refiner_layers": 2, "num_attention_heads": 56, "attention_head_dim": 128, "ffn_hidden_size": 14336, + "ffn_dim": 14336, "latents_dim": 24, + "in_channels": 24, "audio_latents_dim": 32, + "audio_in_channels": 32, "patch_size": [ 1, 2, @@ -16,11 +20,15 @@ ], "text_dim": 5120, "timestep_input_dim": 256, + "freq_dim": 256, "time_embed_hidden_size": 5376, + "time_embed_hidden_dim": 5376, "time_embed_dim": 2688, "adaln_out_features": 96768, "final_adaln_out_features": 10752, "rope_inv_freq_len": 16, + "rope_freq_dim": 16, + "rope_theta": 10000.0, "norm_eps": 1e-05, "qk_norm_eps": 1e-05, "final_norm_eps": 1e-05 diff --git a/audio_vae/config.json b/audio_vae/config.json index 15df6fe..5e90c79 100644 --- a/audio_vae/config.json +++ b/audio_vae/config.json @@ -53,6 +53,7 @@ 5 ] ], + "sample_rate": 32000, "sampling_rate": 32000, "latents_mean": [ -0.020211687488382354, diff --git a/requirements.txt b/requirements.txt index 7f5c20c..8ab7b6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,3 +47,8 @@ soundfile>=0.12.0 imageio>=2.34.0 imageio-ffmpeg>=0.5.0 av>=11.0.0 + +# PyYAML is required at runtime by the audio VAE loader (minimax_h3_audio_vae.py) +# to parse the model config YAML file. Without it, loading the audio VAE raises +# ImportError("MiniMax H3 audio VAE requires PyYAML."). +PyYAML>=6.0 diff --git a/scripts/readme/full-2k-i2va-h3-base.sh b/scripts/readme/full-2k-i2va-h3-base.sh index 37a53d1..cf2e8d4 100755 --- a/scripts/readme/full-2k-i2va-h3-base.sh +++ b/scripts/readme/full-2k-i2va-h3-base.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create the H3-Base request with the expanded prompt and capture the video ID. @@ -23,7 +23,7 @@ video_id=$( }, "seed": 0 }' | - curl --silent --show-error \ + curl --fail-with-body --silent --show-error \ --request POST \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos" \ --header 'Content-Type: application/json' \ @@ -31,12 +31,12 @@ video_id=$( jq -er '.id' ) # Query the generation status. -curl --silent --show-error \ +curl --fail-with-body --silent --show-error \ --request GET \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos/$video_id" | jq '{status}' # Download the local H3-Base MP4 after its status becomes completed. -curl --silent --show-error \ +curl --fail-with-body --silent --show-error \ --request GET \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos/$video_id/content" \ --output i2va.mp4 diff --git a/scripts/readme/full-2k-i2va-h3-context-ir.sh b/scripts/readme/full-2k-i2va-h3-context-ir.sh index c8180c4..1cec904 100755 --- a/scripts/readme/full-2k-i2va-h3-context-ir.sh +++ b/scripts/readme/full-2k-i2va-h3-context-ir.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create the prompt-expansion task and capture its runtime ID. diff --git a/scripts/readme/full-2k-i2va-h3-regenerate-2k.sh b/scripts/readme/full-2k-i2va-h3-regenerate-2k.sh index 45f4b21..6f14127 100755 --- a/scripts/readme/full-2k-i2va-h3-regenerate-2k.sh +++ b/scripts/readme/full-2k-i2va-h3-regenerate-2k.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail H3_BASE_VIDEO='./i2va.mp4' diff --git a/scripts/readme/full-2k-i2va-reference-2k-result-by-directly-calling-open-platform-api.sh b/scripts/readme/full-2k-i2va-reference-2k-result-by-directly-calling-open-platform-api.sh index c04741b..0a9e651 100755 --- a/scripts/readme/full-2k-i2va-reference-2k-result-by-directly-calling-open-platform-api.sh +++ b/scripts/readme/full-2k-i2va-reference-2k-result-by-directly-calling-open-platform-api.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create an 8-second 2K FL2VA video directly and capture its runtime task ID. diff --git a/scripts/readme/full-2k-i2va-reference-768p-result-by-directly-calling-open-platform-api.sh b/scripts/readme/full-2k-i2va-reference-768p-result-by-directly-calling-open-platform-api.sh index c21ee2d..44a2637 100755 --- a/scripts/readme/full-2k-i2va-reference-768p-result-by-directly-calling-open-platform-api.sh +++ b/scripts/readme/full-2k-i2va-reference-768p-result-by-directly-calling-open-platform-api.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create an 8-second 768P FL2VA video directly and capture its runtime task ID. diff --git a/scripts/readme/full-2k-ref2va-h3-api-2k-in-open-platform-for-reference.sh b/scripts/readme/full-2k-ref2va-h3-api-2k-in-open-platform-for-reference.sh index 19b7c65..566cf39 100755 --- a/scripts/readme/full-2k-ref2va-h3-api-2k-in-open-platform-for-reference.sh +++ b/scripts/readme/full-2k-ref2va-h3-api-2k-in-open-platform-for-reference.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create a 5-second 2K Ref2VA video directly and capture its runtime task ID. diff --git a/scripts/readme/full-2k-ref2va-h3-base.sh b/scripts/readme/full-2k-ref2va-h3-base.sh index 6b6f034..ba4b560 100755 --- a/scripts/readme/full-2k-ref2va-h3-base.sh +++ b/scripts/readme/full-2k-ref2va-h3-base.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create the H3-Base request with the expanded prompt and capture the video ID. @@ -27,7 +27,7 @@ video_id=$( }, "seed": 0 }' | - curl --silent --show-error \ + curl --fail-with-body --silent --show-error \ --request POST \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos" \ --header 'Content-Type: application/json' \ @@ -35,12 +35,12 @@ video_id=$( jq -er '.id' ) # Query the generation status. -curl --silent --show-error \ +curl --fail-with-body --silent --show-error \ --request GET \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos/$video_id" | jq '{status}' # Download the local H3-Base MP4 after its status becomes completed. -curl --silent --show-error \ +curl --fail-with-body --silent --show-error \ --request GET \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos/$video_id/content" \ --output r2va.mp4 diff --git a/scripts/readme/full-2k-ref2va-h3-context-ir.sh b/scripts/readme/full-2k-ref2va-h3-context-ir.sh index 6195577..af699e7 100755 --- a/scripts/readme/full-2k-ref2va-h3-context-ir.sh +++ b/scripts/readme/full-2k-ref2va-h3-context-ir.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create the prompt-expansion task and capture its runtime ID. diff --git a/scripts/readme/full-2k-ref2va-reference-2k-result-by-directly-calling-open-platform-api.sh b/scripts/readme/full-2k-ref2va-reference-2k-result-by-directly-calling-open-platform-api.sh index 2ac2aab..ce4e948 100755 --- a/scripts/readme/full-2k-ref2va-reference-2k-result-by-directly-calling-open-platform-api.sh +++ b/scripts/readme/full-2k-ref2va-reference-2k-result-by-directly-calling-open-platform-api.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail H3_BASE_VIDEO='./r2va.mp4' diff --git a/scripts/readme/full-2k-ref2va-reference-768p-result-by-directly-calling-open-platform-api.sh b/scripts/readme/full-2k-ref2va-reference-768p-result-by-directly-calling-open-platform-api.sh index 038b5df..b99f4cd 100755 --- a/scripts/readme/full-2k-ref2va-reference-768p-result-by-directly-calling-open-platform-api.sh +++ b/scripts/readme/full-2k-ref2va-reference-768p-result-by-directly-calling-open-platform-api.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create a 5-second 768P Ref2VA video directly and capture its runtime task ID. diff --git a/scripts/readme/full-2k-t2va-h3-base.sh b/scripts/readme/full-2k-t2va-h3-base.sh index cbdd623..2893bfd 100755 --- a/scripts/readme/full-2k-t2va-h3-base.sh +++ b/scripts/readme/full-2k-t2va-h3-base.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create the H3-Base request with the expanded prompt and capture the video ID. @@ -16,7 +16,7 @@ video_id=$( }, "seed": 0 }' | - curl --silent --show-error \ + curl --fail-with-body --silent --show-error \ --request POST \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos" \ --header 'Content-Type: application/json' \ @@ -24,12 +24,12 @@ video_id=$( jq -er '.id' ) # Query the generation status. -curl --silent --show-error \ +curl --fail-with-body --silent --show-error \ --request GET \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos/$video_id" | jq '{status}' # Download the local H3-Base MP4 after its status becomes completed. -curl --silent --show-error \ +curl --fail-with-body --silent --show-error \ --request GET \ --url "$SGLANG_DEPLOYMENT_URL/v1/videos/$video_id/content" \ --output t2va.mp4 diff --git a/scripts/readme/full-2k-t2va-h3-context-ir.sh b/scripts/readme/full-2k-t2va-h3-context-ir.sh index 6afd3b4..18b88ec 100755 --- a/scripts/readme/full-2k-t2va-h3-context-ir.sh +++ b/scripts/readme/full-2k-t2va-h3-context-ir.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create the prompt-expansion task and capture its runtime ID. diff --git a/scripts/readme/full-2k-t2va-h3-regenerate-2k.sh b/scripts/readme/full-2k-t2va-h3-regenerate-2k.sh index 1d67780..a2649da 100755 --- a/scripts/readme/full-2k-t2va-h3-regenerate-2k.sh +++ b/scripts/readme/full-2k-t2va-h3-regenerate-2k.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail H3_BASE_VIDEO='./t2va.mp4' diff --git a/scripts/readme/full-2k-t2va-reference-2k-result-by-directly-calling-open-platform-api.sh b/scripts/readme/full-2k-t2va-reference-2k-result-by-directly-calling-open-platform-api.sh index 669e10e..cddfc3c 100755 --- a/scripts/readme/full-2k-t2va-reference-2k-result-by-directly-calling-open-platform-api.sh +++ b/scripts/readme/full-2k-t2va-reference-2k-result-by-directly-calling-open-platform-api.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create a 10-second 2K video directly and capture its runtime task ID. diff --git a/scripts/readme/full-2k-t2va-reference-768p-result-by-directly-calling-open-platform-api.sh b/scripts/readme/full-2k-t2va-reference-768p-result-by-directly-calling-open-platform-api.sh index b765671..10eb7db 100755 --- a/scripts/readme/full-2k-t2va-reference-768p-result-by-directly-calling-open-platform-api.sh +++ b/scripts/readme/full-2k-t2va-reference-768p-result-by-directly-calling-open-platform-api.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Create a 10-second 768P video directly and capture its runtime task ID. diff --git a/scripts/readme/reproducible-768p-fl2va-request.sh b/scripts/readme/reproducible-768p-fl2va-request.sh index 1efa6fc..486fd80 100755 --- a/scripts/readme/reproducible-768p-fl2va-request.sh +++ b/scripts/readme/reproducible-768p-fl2va-request.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Submit the FL2VA request with the complete H3-Context-IR prompt. diff --git a/scripts/readme/reproducible-768p-ref2va-request.sh b/scripts/readme/reproducible-768p-ref2va-request.sh index 5f13b14..3e5598e 100755 --- a/scripts/readme/reproducible-768p-ref2va-request.sh +++ b/scripts/readme/reproducible-768p-ref2va-request.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Submit the Ref2VA request with the complete H3-Context-IR prompt. diff --git a/scripts/readme/reproducible-768p-t2va-request.sh b/scripts/readme/reproducible-768p-t2va-request.sh index b23938b..c3e30cd 100755 --- a/scripts/readme/reproducible-768p-t2va-request.sh +++ b/scripts/readme/reproducible-768p-t2va-request.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail # Submit the T2VA request with the complete H3-Context-IR prompt. diff --git a/tests/__pycache__/test_regressions.cpython-314-pytest-9.1.1.pyc b/tests/__pycache__/test_regressions.cpython-314-pytest-9.1.1.pyc new file mode 100644 index 0000000..f7275ea Binary files /dev/null and b/tests/__pycache__/test_regressions.cpython-314-pytest-9.1.1.pyc differ diff --git a/tests/test_regressions.py b/tests/test_regressions.py new file mode 100644 index 0000000..828d064 --- /dev/null +++ b/tests/test_regressions.py @@ -0,0 +1,338 @@ +""" +MiniMax-H3 Regression Tests — Fixed harness +============================================== +Uses sys.path injection instead of spec_from_file_location so relative +imports inside the audio_vae package resolve correctly. + +Run with: + cd d:\\minimax\\MiniMax-H3 + python -m pytest tests/test_regressions.py -v --tb=short +""" + +import json +import sys +import importlib +from pathlib import Path + +import pytest +import torch +import torch.nn as nn + +PROJECT_ROOT = Path(__file__).parent.parent + + +def _load_audio_vae_package(task: str): + """ + Add the task directory to sys.path so relative imports work, + then import the modules as a flat namespace. + Returns the dac_audio_vae module for that task. + """ + pkg_dir = str(PROJECT_ROOT / task / "audio_vae") + if pkg_dir not in sys.path: + sys.path.insert(0, pkg_dir) + # Force fresh load by removing any cached versions + mods_to_reload = [ + "dac_alias_free_filter", "dac_alias_free_resample", + "dac_alias_free_act", "dac_activations", "dac_utils", + "dac_attn_proj", "dac_bigvgan", "dac_audio_vae", + ] + for m in mods_to_reload: + sys.modules.pop(m, None) + import dac_audio_vae + return dac_audio_vae + + +def _load_filter_module(task: str): + pkg_dir = str(PROJECT_ROOT / task / "audio_vae") + if pkg_dir not in sys.path: + sys.path.insert(0, pkg_dir) + sys.modules.pop("dac_alias_free_filter", None) + import dac_alias_free_filter + return dac_alias_free_filter + + +def _load_attn_proj(task: str): + pkg_dir = str(PROJECT_ROOT / task / "audio_vae") + if pkg_dir not in sys.path: + sys.path.insert(0, pkg_dir) + sys.modules.pop("dac_attn_proj", None) + import dac_attn_proj + return dac_attn_proj + + +# ─── Bug 1 ──────────────────────────────────────────────────────────────────── + +class TestBug1KaiserSincFilter: + + @pytest.fixture(params=["FL2VA", "Ref2VA"]) + def fmod(self, request): + return _load_filter_module(request.param) + + def test_cutoff_zero_no_nameerror(self, fmod): + r = fmod.kaiser_sinc_filter1d(cutoff=0, half_width=0.6, kernel_size=12) + assert r is not None + + def test_cutoff_zero_shape(self, fmod): + r = fmod.kaiser_sinc_filter1d(cutoff=0, half_width=0.6, kernel_size=12) + assert r.shape == (1, 1, 12) + + def test_cutoff_zero_all_zeros(self, fmod): + r = fmod.kaiser_sinc_filter1d(cutoff=0, half_width=0.6, kernel_size=12) + assert torch.all(r == 0) + + def test_cutoff_positive_shape(self, fmod): + for ks in [8, 12, 24]: + r = fmod.kaiser_sinc_filter1d(cutoff=0.3, half_width=0.3, kernel_size=ks) + assert r.shape == (1, 1, ks) + + def test_cutoff_positive_normalized(self, fmod): + r = fmod.kaiser_sinc_filter1d(cutoff=0.5, half_width=0.6, kernel_size=12) + assert abs(r.sum().item() - 1.0) < 1e-5 + + +# ─── Bug 2 ──────────────────────────────────────────────────────────────────── + +class TestBug2DuplicateSampleRate: + + def test_source_has_exactly_one_assignment(self): + for task in ("FL2VA", "Ref2VA"): + src = (PROJECT_ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + count = src.count("self.sample_rate = sample_rate") + assert count == 1, f"{task}: expected 1 assignment, found {count}" + + @pytest.mark.parametrize("task", ["FL2VA", "Ref2VA"]) + def test_sample_rate_attribute_correct(self, task): + vae_mod = _load_audio_vae_package(task) + for sr in (16000, 32000): + vae = vae_mod.DacAudioVAE(sample_rate=sr) + assert vae.sample_rate == sr + + +# ─── Bug 3 ──────────────────────────────────────────────────────────────────── + +class TestBug3InitWeightsConsistency: + + def test_uses_trunc_normal_std02(self): + for task in ("FL2VA", "Ref2VA"): + src = (PROJECT_ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + assert "trunc_normal_" in src + assert "std=0.02" in src + assert "m.bias" in src + + def test_apply_called_once_in_live_code(self): + for task in ("FL2VA", "Ref2VA"): + src = (PROJECT_ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + # Count only real code lines (not comment or docstring lines) + code_apply = sum( + 1 for l in src.splitlines() + if "self.apply(init_weights)" in l + and not l.strip().startswith("#") + and not l.strip().startswith("`") + and not l.strip().startswith("\"\"\"") + ) + assert code_apply == 1, f"{task}: {code_apply} apply calls in live code" + + @pytest.mark.parametrize("task", ["FL2VA", "Ref2VA"]) + def test_conv_biases_zeroed_after_build(self, task): + vae_mod = _load_audio_vae_package(task) + vae = vae_mod.DacAudioVAE(sample_rate=32000) + checked = 0 + for name, param in vae.named_parameters(): + if "bias" in name and param is not None and param.numel() > 0: + assert torch.all(param.data == 0), f"{task}: {name} not zeroed" + checked += 1 + assert checked > 0, "No biases found to check" + + +# ─── Bug 4 ──────────────────────────────────────────────────────────────────── + +class TestBug4CausalAttentionShape: + + @pytest.fixture(params=["FL2VA", "Ref2VA"]) + def amod(self, request): + return _load_attn_proj(request.param) + + def _fwd(self, amod, in_d, out_d, nh, B=2, N=8): + ca = amod.CausalAttention(in_d, out_d, nh) + ca.eval() + with torch.no_grad(): + return ca(torch.randn(B, N, in_d)).shape + + def test_in_eq_out(self, amod): + assert self._fwd(amod, 64, 64, 8) == (2, 8, 64) + + def test_in_less_than_out(self, amod): + assert self._fwd(amod, 32, 64, 8) == (2, 8, 64) + + def test_in_greater_than_out(self, amod): + """This was the broken case — Bug 4.""" + assert self._fwd(amod, 64, 32, 8) == (2, 8, 32) + + def test_large_batch(self, amod): + assert self._fwd(amod, 64, 32, 8, B=4, N=16) == (4, 16, 32) + + def test_attn_projection_end_to_end(self, amod): + proj = amod.AttnProjection(in_dim=64, out_dim=32, num_heads=8) + proj.eval() + with torch.no_grad(): + out = proj(torch.randn(2, 10, 64)) + assert out.shape == (2, 10, 32) + + +# ─── Bug 5 ──────────────────────────────────────────────────────────────────── + +class TestBug5EncoderBlockPadding: + + def test_source_formula_correct(self): + for task in ("FL2VA", "Ref2VA"): + src = (PROJECT_ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + # New formula present + assert "padding=stride // 2" in src, f"{task}: new formula missing" + # Old formula must NOT appear in live code (only in comments) + old_in_live = any( + "padding=math.ceil(stride" in l and not l.strip().startswith("#") + for l in src.splitlines() + ) + assert not old_in_live, f"{task}: old math.ceil formula still in live code" + + @pytest.mark.parametrize("stride", [1, 2, 3, 4, 5, 8]) + def test_output_length(self, stride): + vae_mod = _load_audio_vae_package("FL2VA") + dim = 32 + block = vae_mod.EncoderBlock(dim=dim, stride=stride) + block.eval() + T = stride * 20 + with torch.no_grad(): + out = block(torch.randn(1, dim // 2, T)) + assert out.shape[1] == dim, f"stride={stride}: channels {out.shape[1]}" + assert out.shape[2] == T // stride, f"stride={stride}: T {out.shape[2]} != {T//stride}" + + +# ─── Bug 6 ──────────────────────────────────────────────────────────────────── + +class TestBug6ResidualUnit: + + @pytest.fixture + def ru_mod(self): + return _load_audio_vae_package("FL2VA") + + def test_source_uses_explicit_crop(self): + for task in ("FL2VA", "Ref2VA"): + src = (PROJECT_ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + assert "pad_right = diff - pad_left" in src + + @pytest.mark.parametrize("T", [30, 31, 50, 51, 99, 100, 101]) + def test_no_crash_various_lengths(self, ru_mod, T): + unit = ru_mod.ResidualUnit(dim=16, dilation=1) + unit.eval() + with torch.no_grad(): + _ = unit(torch.randn(2, 16, T)) + + def test_dilation3_no_crash(self, ru_mod): + unit = ru_mod.ResidualUnit(dim=16, dilation=3) + unit.eval() + for T in [31, 33, 47, 51]: + with torch.no_grad(): + _ = unit(torch.randn(1, 16, T)) + + def test_output_is_3d(self, ru_mod): + unit = ru_mod.ResidualUnit(dim=16, dilation=1) + unit.eval() + with torch.no_grad(): + out = unit(torch.randn(2, 16, 51)) + assert out.ndim == 3 + + +# ─── Bug 7 ──────────────────────────────────────────────────────────────────── + +class TestBug7TransformerConfig: + + @pytest.mark.parametrize("task", ["FL2VA", "Ref2VA"]) + def test_ffn_both_keys(self, task): + cfg = json.loads((PROJECT_ROOT / task / "transformer" / "config.json").read_text()) + assert "ffn_hidden_size" in cfg and "ffn_dim" in cfg + assert cfg["ffn_hidden_size"] == cfg["ffn_dim"] + + @pytest.mark.parametrize("task", ["FL2VA", "Ref2VA"]) + def test_rope_both_keys(self, task): + cfg = json.loads((PROJECT_ROOT / task / "transformer" / "config.json").read_text()) + assert "rope_inv_freq_len" in cfg and "rope_freq_dim" in cfg + assert cfg["rope_inv_freq_len"] == cfg["rope_freq_dim"] + + @pytest.mark.parametrize("task", ["FL2VA", "Ref2VA"]) + def test_refiner_both_keys(self, task): + cfg = json.loads((PROJECT_ROOT / task / "transformer" / "config.json").read_text()) + assert "token_refiner_num_layers" in cfg and "num_refiner_layers" in cfg + assert cfg["token_refiner_num_layers"] == cfg["num_refiner_layers"] + + +# ─── Bug 8 ──────────────────────────────────────────────────────────────────── + +class TestBug8DiffusersVersion: + + def test_task_indexes_match(self): + fl = json.loads((PROJECT_ROOT / "FL2VA" / "model_index.json").read_text())["_diffusers_version"] + ref = json.loads((PROJECT_ROOT / "Ref2VA" / "model_index.json").read_text())["_diffusers_version"] + assert fl == ref + + def test_root_indexes_match(self): + root = json.loads((PROJECT_ROOT / "model_index.json").read_text())["_diffusers_version"] + mod = json.loads((PROJECT_ROOT / "modular_model_index.json").read_text())["_diffusers_version"] + assert root == mod + + +# ─── Bug 9 ──────────────────────────────────────────────────────────────────── + +class TestBug9PyYAML: + def test_listed(self): + req = (PROJECT_ROOT / "requirements.txt").read_text(encoding="utf-8") + assert "PyYAML" in req + + +# ─── Bug 10 ─────────────────────────────────────────────────────────────────── + +class TestBug10CRLF: + def test_no_crlf(self): + bad = [f.name for f in (PROJECT_ROOT / "scripts" / "readme").glob("*.sh") + if b"\r\n" in f.read_bytes()] + assert bad == [], f"CRLF found in: {bad}" + + +# ─── Bug 11 ─────────────────────────────────────────────────────────────────── + +class TestBug11CurlFlag: + @pytest.mark.parametrize("script", [ + "full-2k-t2va-h3-base.sh", + "full-2k-i2va-h3-base.sh", + "full-2k-ref2va-h3-base.sh", + ]) + def test_flag_present(self, script): + txt = (PROJECT_ROOT / "scripts" / "readme" / script).read_text(encoding="utf-8") + assert "--fail-with-body" in txt + + +# ─── Bug 12 ─────────────────────────────────────────────────────────────────── + +class TestBug12SampleRate: + def test_both_keys_present_and_equal(self): + cfg = json.loads((PROJECT_ROOT / "audio_vae" / "config.json").read_text()) + assert "sample_rate" in cfg + assert "sampling_rate" in cfg + assert cfg["sample_rate"] == cfg["sampling_rate"] + + def test_fl2va_config_has_sample_rate(self): + cfg = json.loads((PROJECT_ROOT / "FL2VA" / "audio_vae" / "config.json").read_text()) + assert "sample_rate" in cfg + + +# ─── Bug 13 ─────────────────────────────────────────────────────────────────── + +class TestBug13README: + def test_download_steps_present(self): + readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8", errors="replace") + assert "Step 1" in readme and "Step 2" in readme and "Step 3" in readme + assert "hf download MiniMaxAI/MiniMax-H3" in readme + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-v", "--tb=short"])) diff --git a/tests/verify_fixes.py b/tests/verify_fixes.py new file mode 100644 index 0000000..061107e --- /dev/null +++ b/tests/verify_fixes.py @@ -0,0 +1,132 @@ +import json, sys, pathlib + +ROOT = pathlib.Path(r"d:\minimax\MiniMax-H3") +results = [] + +def check(num, name, condition, detail=""): + icon = "PASS" if condition else "FAIL" + results.append((num, name, icon, detail)) + tag = "OK" if condition else "!!" + print(f" [{tag}] Bug {num}: {name}" + (f" -> {detail}" if detail else "")) + +# BUG 1 +for task in ("FL2VA", "Ref2VA"): + src = (ROOT / task / "audio_vae" / "dac_alias_free_filter.py").read_text(encoding="utf-8") + lines = src.splitlines() + fix_ok = any(" filter = filter_.view(1, 1, kernel_size)" in l + and not l.startswith(" ") for l in lines) + still_buggy = any(" filter = filter_.view" in l for l in lines) + check(1, f"NameError cutoff==0 [{task}]", fix_ok and not still_buggy) + +# BUG 2 +for task in ("FL2VA", "Ref2VA"): + src = (ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + count = src.count("self.sample_rate = sample_rate") + check(2, f"Duplicate sample_rate [{task}]", count == 1, f"{count} assignment(s)") + +# BUG 3 +for task in ("FL2VA", "Ref2VA"): + src = (ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + has_trunc = "trunc_normal_" in src + has_std = "std=0.02" in src + has_bias = "m.bias" in src + apply_calls = sum(1 for l in src.splitlines() + if "self.apply(init_weights)" in l + and not l.strip().startswith("#") + and not l.strip().startswith("``")) + check(3, f"Conflicting init_weights [{task}]", + has_trunc and has_std and has_bias and apply_calls == 1, + f"trunc={has_trunc} std02={has_std} bias={has_bias} apply={apply_calls}") + +# BUG 4 +for task in ("FL2VA", "Ref2VA"): + src = (ROOT / task / "audio_vae" / "dac_attn_proj.py").read_text(encoding="utf-8") + has_qkv_out = "self.qkv_out_dim" in src + old_mean_gone = "torch.mean(x, dim=1)" not in src + uniform_reshape = "x.transpose(1, 2).reshape(B, N, self.qkv_out_dim)" in src + check(4, f"CausalAttention shape [{task}]", + has_qkv_out and old_mean_gone and uniform_reshape) + +# BUG 5 +for task in ("FL2VA", "Ref2VA"): + src = (ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + new_ok = "padding=stride // 2" in src + old_in_code = any("padding=math.ceil(stride" in l and not l.strip().startswith("#") + for l in src.splitlines()) + check(5, f"EncoderBlock padding [{task}]", new_ok and not old_in_code) + +# BUG 6 +for task in ("FL2VA", "Ref2VA"): + src = (ROOT / task / "audio_vae" / "dac_audio_vae.py").read_text(encoding="utf-8") + has_explicit = "pad_right = diff - pad_left" in src + old_gone = not any("x[..., pad:-pad]" in l and not l.strip().startswith("#") + for l in src.splitlines()) + check(6, f"ResidualUnit odd diff [{task}]", has_explicit and old_gone) + +# BUG 7 +for task in ("FL2VA", "Ref2VA"): + cfg = json.loads((ROOT / task / "transformer" / "config.json").read_text(encoding="utf-8")) + pairs = [("ffn_hidden_size", "ffn_dim"), + ("rope_inv_freq_len", "rope_freq_dim"), + ("token_refiner_num_layers", "num_refiner_layers")] + ok = all(a in cfg and b in cfg and cfg[a] == cfg[b] for a, b in pairs) + check(7, f"Transformer config consistency [{task}]", ok) + +# BUG 8 +fl2va_v = json.loads((ROOT / "FL2VA" / "model_index.json").read_text())["_diffusers_version"] +ref2va_v = json.loads((ROOT / "Ref2VA" / "model_index.json").read_text())["_diffusers_version"] +root_v = json.loads((ROOT / "model_index.json").read_text())["_diffusers_version"] +mod_v = json.loads((ROOT / "modular_model_index.json").read_text())["_diffusers_version"] +check(8, "Diffusers version consistency", + fl2va_v == ref2va_v and root_v == mod_v, + f"FL2VA/Ref2VA={fl2va_v} | Root/Modular={root_v}") + +# BUG 9 +req = (ROOT / "requirements.txt").read_text(encoding="utf-8") +check(9, "PyYAML in requirements.txt", "PyYAML" in req) + +# BUG 10 +crlf_files = [f.name for f in (ROOT / "scripts" / "readme").glob("*.sh") + if b"\r\n" in f.read_bytes()] +check(10, "CRLF in .sh scripts", + len(crlf_files) == 0, + f"CRLF still in: {crlf_files}" if crlf_files else "All 18 scripts are LF-only") + +# BUG 11 +missing = [s for s in ["full-2k-t2va-h3-base.sh", "full-2k-i2va-h3-base.sh", "full-2k-ref2va-h3-base.sh"] + if "--fail-with-body" not in + (ROOT / "scripts" / "readme" / s).read_text(encoding="utf-8")] +check(11, "curl --fail-with-body in h3-base scripts", + len(missing) == 0, + f"Missing in: {missing}" if missing else "All 3 scripts have the flag") + +# BUG 12 +cfg12 = json.loads((ROOT / "audio_vae" / "config.json").read_text(encoding="utf-8")) +both = "sample_rate" in cfg12 and "sampling_rate" in cfg12 +equal = cfg12.get("sample_rate") == cfg12.get("sampling_rate") +check(12, "sampling_rate vs sample_rate", + both and equal, + "sample_rate=32000 & sampling_rate=32000 both present" if (both and equal) else "MISMATCH") + +# BUG 13 +readme = (ROOT / "README.md").read_text(encoding="utf-8", errors="replace") +ok13 = ("Step 1" in readme and "Step 2" in readme and + "Step 3" in readme and "hf download MiniMaxAI/MiniMax-H3" in readme) +check(13, "README weight download docs", + ok13, "4-step guide + hf download command present" if ok13 else "MISSING") + +# SUMMARY +print() +passes = sum(1 for _, _, s, _ in results if s == "PASS") +fails = [r for r in results if r[2] == "FAIL"] +total = len(results) +print("=" * 58) +print(f" RESULT: {passes}/{total} checks PASSED") +if fails: + print(f" FAILED ({len(fails)}):") + for num, name, _, detail in fails: + print(f" Bug {num}: {name} — {detail}") +else: + print(f" ALL {total} CHECKS PASSED — EVERY BUG IS FIXED") +print("=" * 58) +sys.exit(0 if not fails else 1)