Skip to content

[feat] MiniMax H3: block-FP8 text encoder on Hopper, its converter, and a 4xH100 FastH3 profile - #1829

Open
stayinalive181 wants to merge 1 commit into
hao-ai-lab:mainfrom
stayinalive181:pr2-h3-fp8-te-sm90
Open

[feat] MiniMax H3: block-FP8 text encoder on Hopper, its converter, and a 4xH100 FastH3 profile#1829
stayinalive181 wants to merge 1 commit into
hao-ai-lab:mainfrom
stayinalive181:pr2-h3-fp8-te-sm90

Conversation

@stayinalive181

Copy link
Copy Markdown

Purpose

FastVideo gates the serialized block-FP8 Qwen3-VL text encoder on Blackwell, but only the FlashInfer groupwise GEMM is Blackwell-specific. On Hopper the encoder therefore cannot stay resident beside an FSDP-sharded DiT on 80 GB and is offloaded on every request: 1.82 s per clip on 8xH200 versus 0.04 s resident (hlander-ai/minimax-h3). This PR lets sm_90 load the same FP8 checkpoint: the weights stay FP8-resident (35.5 GB instead of 66.7 GB) and each language-model linear dequantizes its block-scaled weight to BF16 right before a plain matmul, which is the arithmetic the FlashInfer path performs internally. FP8 buys residency here rather than speed. The route and the converter recipe come from hlander-ai/minimax-h3 (Apache-2.0).

With that in place a 4xH100 FastH3 profile becomes runnable: FSDP-sharded DiT, ThunderKittens sm_90a VSA kernel, resident FP8 encoder.

Changes

  • fastvideo/models/encoders/minimax_h3_checkpoint_fp8.py: get_min_capability 100 -> 90; validate_runtime no longer requires the Triton quantizer or the FlashInfer GEMM on sm_90; apply dequantizes through _dequantize_block_fp8_weight on sm_90. The Blackwell path is unchanged.
  • scripts/checkpoint_conversion/quantize_minimax_h3_text_encoder_fp8.py: writes the serialized checkpoint the loader already reads (E4M3 weight plus [N/128, K/128] weight_scale_inv, quantization_config in config.json, regenerated index). There was no producer for this format in-tree.
  • examples/inference/basic/basic_fasth3.py: --vsa-kernel tk (ThunderKittens sm_90a, FASTVIDEO_VSA_TK=1), --text-encoder-weights, --offload-text-encoder / --offload-vae (defaults unchanged), and an sm_90 extension check in validate_profile_dependencies.
  • examples/inference/basic/basic_fasth3_h100.yaml and a "Hopper (H100 80 GB)" README section with the measured points.
  • Tests: validate_runtime accepts (9, 0) without the Blackwell dependencies; the dequantizer is bit-exact against the reference broadcast and the FP8 round trip stays within E4M3 precision; the tk profile's environment, config and dependency check.

Test Plan

ruff check fastvideo/models/encoders/minimax_h3_checkpoint_fp8.py \
  fastvideo/tests/encoders/test_minimax_h3_qwen3_vl_checkpoint_fp8.py \
  fastvideo/tests/inference/test_basic_fasth3_profile.py \
  examples/inference/basic/basic_fasth3.py \
  scripts/checkpoint_conversion/quantize_minimax_h3_text_encoder_fp8.py
yapf --diff / codespell --toml pyproject.toml on the same files
python -m pytest fastvideo/tests/encoders/test_minimax_h3_qwen3_vl_checkpoint_fp8.py -q
python -m pytest fastvideo/tests/inference/test_basic_fasth3_profile.py -q

Test Results

Test output
ruff:      All checks passed! (the only report is the pre-existing SIM102 at minimax_h3_checkpoint_fp8.py:381, untouched)
yapf:      touched hunks clean (the file is outside pre-commit and was never yapf-formatted upstream)
codespell: clean
pytest fastvideo/tests/encoders/test_minimax_h3_qwen3_vl_checkpoint_fp8.py: 13 passed in 0.57s
pytest fastvideo/tests/inference/test_basic_fasth3_profile.py:                15 passed in 0.07s
(CPU, macOS arm64, torch 2.14.0)

Measured on FastVideo b2db0c0 with this encoder path (warm, loading and compilation excluded, 345 frames = 14.375 s of playback):

GPUs Resolution Generation Source
8x H100 80 GB 1344x768 13.506 s (13.503 / 13.506 / 13.554) hlander-ai/minimax-h3, Triton VSA
4x H100 80 GB, NVLink 960x544 12.98-14.02 s over 8 consecutive clips Windflow streaming worker: TK VSA, FSDP DiT, resident FP8 encoder

The 4xH100 row was measured through a downstream streaming worker rather than basic_fasth3.py; basic_fasth3.py numbers for that shape on a fresh 4xH100 box can be added to this PR on request.

Checklist

  • ruff, yapf and codespell run with the repository configuration on the touched files (pre-commit itself is not installed on the authoring machine)
  • I added or updated tests for my changes
  • I updated documentation if needed (README section, example YAML, scripts/checkpoint_conversion/AGENTS.md)
  • I considered GPU memory impact of my changes (sm_90 only: one dequantized BF16 weight is materialized per linear call, at most ~0.26 GB for the largest layer, and released after the matmul; the resident encoder drops from 66.7 GB to 35.5 GB)

For model/pipeline changes, also check:

  • SSIM regression not run here; the FP8 encoder is a precision change relative to the stock BF16 encoder on every architecture, and this PR does not change the Blackwell numerics
  • Support matrix unchanged (Hopper is a new runtime for an existing model)

…nd a 4xH100 FastH3 profile

FastVideo gates the serialized block-FP8 Qwen3-VL text encoder on
Blackwell, but only the FlashInfer groupwise GEMM is Blackwell-specific.
On sm_90 the weights now stay FP8-resident (35.5 GB instead of 66.7 GB)
and every language-model linear dequantizes its block-scaled weight to
BF16 right before a plain matmul. That is the arithmetic the FlashInfer
path performs internally, so FP8 buys residency here rather than speed:
the encoder can sit next to an FSDP-sharded DiT on 80 GB GPUs instead of
being offloaded on every request (text encode 1.82 s -> 0.04 s per clip
on 8xH200 in hlander-ai/minimax-h3, Apache-2.0, where this route comes
from).

Alongside the encoder path:

- scripts/checkpoint_conversion/quantize_minimax_h3_text_encoder_fp8.py
  writes the serialized checkpoint the loader already reads (E4M3
  weights, 128x128 block scales, quantization_config, regenerated index).
- examples/inference/basic/basic_fasth3.py learns --vsa-kernel tk
  (ThunderKittens sm_90a), --text-encoder-weights and
  --no-offload-text-encoder / --no-offload-vae, and checks for the sm_90
  extension before loading anything.
- examples/inference/basic/basic_fasth3_h100.yaml and a README section
  document the 4xH100 route: FSDP-sharded DiT, TK VSA, resident FP8
  encoder, 960x544 x 345 frames.

Measured on FastVideo b2db0c0 with these patches: 8xH100 1344x768 in
13.506 s for 14.375 s of playback (hlander-ai/minimax-h3), and 4xH100
960x544 in 12.98-14.02 s over eight consecutive clips (Windflow streaming
worker). basic_fasth3.py numbers for the 4xH100 shape will follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mergify mergify Bot added type: feat New feature or capability scope: inference Inference pipeline, serving, CLI scope: infra CI, tests, Docker, build scope: model Model architecture (DiTs, encoders, VAEs) labels Sep 7, 2026
@mergify

mergify Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews and 🤖 CI

Protection Waiting on
🔴 PR merge requirements 👀 reviews and 🤖 CI

🔴 PR merge requirements

Waiting for

  • #approved-reviews-by>=1
  • check-success=fastcheck-passed
  • check-success=full-suite-passed
This rule is failing.
  • #approved-reviews-by>=1
  • check-success=fastcheck-passed
  • check-success=full-suite-passed
  • check-success~=pre-commit
  • title~=(?i)^\[(feat|feature|bugfix|fix|refactor|perf|ci|doc|docs|misc|chore|kernel|new.?model|skill|skills|infra)\]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: inference Inference pipeline, serving, CLI scope: infra CI, tests, Docker, build scope: model Model architecture (DiTs, encoders, VAEs) type: feat New feature or capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant