Skip to content

[Diffusion][Performance] Optimize MiniMax-H3 video output transfer - #6824

Merged
hsliuustc0106 merged 1 commit into
vllm-project:mainfrom
lishunyang12:perf/minimax-h3-gpu-output
Aug 30, 2026
Merged

hsliuustc0106 merged 1 commit into
vllm-project:mainfrom
lishunyang12:perf/minimax-h3-gpu-output

Conversation

@lishunyang12

@lishunyang12 lishunyang12 commented Aug 30, 2026 •

Copy link
Copy Markdown
Collaborator

Purpose

MiniMax-H3 decoded video was returned from the diffusion worker as FP32 BCTHW. A 5-second 1344x768 output therefore moved 1,535,901,696 bytes through the worker-to-engine path before the parent process performed layout conversion and quantization for video encoding.

The worker-to-engine path already provides asynchronous pinned D2H transfer. This PR makes that path substantially cheaper by:

  • clamping, scaling, and rounding decoded frames on the GPU after VAE decode;
  • combining the dtype and BCTHW-to-BTHWC layout conversion into one contiguous uint8 allocation;
  • avoiding an additional torch.cat allocation for the common single-output case; and
  • letting the video encoder consume an already contiguous FHWC uint8 array without normalizing and copying it again.

There are no new Python or CLI options. The HTTP video response and encoded MP4 are unchanged. The raw MiniMax-H3 offline frame representation changes from float32 [0, 1] to contiguous uint8 [0, 255]; callers that consume OmniRequestOutput.images[0] directly should branch on dtype, as the generic offline video examples already do.

Test Plan

The before/after runs and SGLang reference used the same MiniMax-H3 FL2VA BF16 checkpoint, prompt, seed, workload, and top-level parallel degree on 8x NVIDIA B300 SXM6 AC:

  • 5.0 seconds, 124 frames, 1344x768, 24 FPS
  • 50 scheduler points / 49 denoise updates
  • DP1, TP1, SP8 (Ulysses 8, ring 1)
  • vLLM-Omni: text-encoder TP8 and VAE tile/patch parallel 8
  • SGLang: encoder_parallel=fold and its native SP8 decoder path
  • CUDNN attention, eager mode, no offload
  • vLLM-Omni: one warm-up followed by three measured generations in the same engine
  • SGLang reference: one warm-up followed by two measured generations in the same engine

vLLM Version: 0.28.0 (Python 3.12.3, torch 2.13.0+cu130)

vLLM-Omni Commit: baseline f7f48929db8dcd60c8ce538f4057bec5a789057c; candidate bdfd4847325ed70c88c7046c82acbbf7e7cf9459

SGLang Commit: fe694986a296787cb0ada3b8cd7b6dccd21de72a

Test Result

Metric vLLM-Omni baseline This PR SGLang reference This PR vs baseline
Steady end-to-end inference 22.578 s (n=3) 21.683 s (n=3) 18.830 s (n=2) -0.895 s / -3.96%
End-to-end standard deviation 0.082 s 0.018 s 0.019 s —
Worker video payload per output 1,535,901,696 bytes 383,975,424 bytes N/A -75%
Peak GPU memory 84,358 MiB 84,358 MiB 82,866 MiB unchanged

Measured generations were 22.586 / 22.656 / 22.492 s before and 21.703 / 21.676 / 21.669 s after. Both runs produced a 2,503,544-byte MP4 with the same SHA256:

9904a1ae9f0fb86d38cfc43d4398bdacd07c6ca4d289a37eaa87a9965378e6aa

The matching SGLang no-offload/SP8 steady inference times were 18.816 / 18.844 s; its corresponding wall times including MP4 materialization were 19.370 / 19.463 s (mean 19.417 s). The table compares vLLM-Omni valid_wave_s with SGLang scheduler total_duration_ms; both exclude engine shutdown and MP4 materialization. This PR closes 23.9% of the original vLLM-Omni-to-SGLang steady-inference gap, while the remaining vLLM-Omni latency is 2.853 s (15.15%) above SGLang.

Validation:

  • python -m pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py tests/entrypoints/openai_api/test_video_api_utils.py — 182 passed
  • Ruff, formatting, typos, SPDX, forbidden-import, CUDA-call, and test-mark hooks passed for all changed files
  • The changed-file mypy hook reports the same existing 3 library and 7 test errors on the clean baseline; this PR adds no mypy errors
  • Signed-off commit and static pre-submit check passed against current upstream/main

Signed-off-by: lishunyang12 <lishunyang12@163.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@vllm-omni-review-bot

Copy link
Copy Markdown

This PR appears to be related to model: MinimaxH3.

Model owners: @david6666666

@lishunyang12, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer.

Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment.

@hsliuustc0106 hsliuustc0106 added ready label to trigger buildkite CI cuda-test Used to trigger vllm-omni cuda CI separately. high priority high priority issue, needs to be done asap labels Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Reviewer A/B test for PR #6824

Environment

  • 4× NVIDIA L20X (143,771 MiB each), driver 570.133.20
  • PyTorch 2.11.0+cu130, vLLM 0.28.0
  • Baseline: f7f48929db8dcd60c8ce538f4057bec5a789057c
  • Candidate: bdfd4847325ed70c88c7046c82acbbf7e7cf9459
  • CUDNN attention, eager mode, no offload, SP/Ulysses=4, text-encoder TP=4, tiled VAE parallelism=4

Workload

  • MiniMax-H3 T2VA; 1344×768, 124 frames at 24 FPS
  • 4 denoising steps, seed 42, fixed prompt
  • One full-shape warm-up followed by two sequential measured requests
Metric Baseline PR #6824 Delta
E2E latency 9.738 ± 0.204 s 8.878 ± 0.064 s −8.84%
Stage-0 generation 8.601 s 6.832 s −20.56%
Throughput 0.1027 req/s 0.1126 req/s +9.69%
Peak memory 91,050 MB 91,050 MB unchanged

Both measured MP4 outputs were byte-identical:
d9a02c354293159da7790209eab9ed563af928febbe3847830380bc15be5d0

Both outputs decoded successfully as 124-frame H.264 video with stereo 32-kHz AAC audio.

One caveat: baseline used the direct_planar encoder path (~1.0 s), while the PR used legacy_fallback with reason=non_contiguous_rgb_planes (~1.91 s). The PR’s contiguous BTHWC layout and the current non-streaming _encode_video_bytes path do not preserve the existing direct-planar route.

Affected tests: 182 passed; Ruff passed.

Copy link
Copy Markdown
Collaborator

Follow-up on the interaction with PR #6776: this PR’s contiguous BTHWC output is interleaved, so the existing non-streaming encoder falls back unless the strided-plane routing from #6776 is also present.

Our 4-GPU MiniMax-H3 A/B (4 steps, 1344×768, 124 frames, seed 42) showed the net E2E gain, 9.738 s → 8.878 s (−8.84%), with byte-identical MP4s and unchanged peak memory. However, baseline used direct_planar encoding at about 1.0 s, while this PR alone used legacy_fallback at about 1.91 s. Please rebase/integrate with #6776, or add the equivalent frontend change, and rerun the combined A/B.

@hsliuustc0106 hsliuustc0106 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@hsliuustc0106
hsliuustc0106 merged commit 759aa4f into vllm-project:main Aug 30, 2026
6 checks passed
heyuanliu-intel added a commit to heyuanliu-intel/vllm-omni that referenced this pull request Sep 3, 2026
## Purpose

**Problem:** with model-level CPU offload, vllm-project#6072 evicts the DiT before VAE decode, but the decoded output stays on the device until the request returns. For `num_outputs_per_prompt > 1` the appended output of seed N is still resident when seed N+1 reloads the DiT, so the peak the eviction removed comes back; with a large output even the first request can OOM in decode.

**Fix:** on the executor reply rank, move each decoded output to the host inside the same seed iteration while active model-level offload is installed; non-reply ranks keep a metadata placeholder. Video is released after `_prepare_minimax_h3_video_output` quantizes to `uint8`, so vllm-project#6824's transfer optimisation is kept. Other offload modes and the no-offload path are unchanged; the design doc records the exception.

## Test Plan

**Reproduce:** 8 XPU cards, TP4xSP2, ref2va at 1344x768 / 124 frames / 50 steps, Cache-DiT and model-level CPU offload for encoders and VAE. The first request finishes denoising (49/49) and then fails in `klvae.py:621 write_part -> torch.empty` with `XPU out of memory. Tried to allocate 1.43 GiB ... 1.42 GiB is free`, HTTP 500.

```bash
pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_offload.py
```

**vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`.

## Test Result

CPU tests pass (release only under active model-level offload; reply rank gets a host tensor, non-reply ranks a placeholder; video copied after quantization; no-offload path unchanged). On the 8-card run with this change: 0 OOM, 3/3 requests HTTP 200, 24 `Evicted 1 DiT module(s) (15.43 GiB)` lines. The device run used one output per prompt; the multi-seed overlap is covered by the CPU tests.

Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
heyuanliu-intel added a commit to heyuanliu-intel/vllm-omni that referenced this pull request Sep 4, 2026
## Purpose

**Problem:** with model-level CPU offload, vllm-project#6072 evicts the DiT before VAE decode, but the decoded output stays on the device until the request returns. For `num_outputs_per_prompt > 1` the appended output of seed N is still resident when seed N+1 reloads the DiT, so the peak the eviction removed comes back; with a large output even the first request can OOM in decode.

**Fix:** on the executor reply rank, move each decoded output to the host inside the same seed iteration while active model-level offload is installed; non-reply ranks keep a metadata placeholder. Video is released after `_prepare_minimax_h3_video_output` quantizes to `uint8`, so vllm-project#6824's transfer optimisation is kept. Other offload modes and the no-offload path are unchanged; the design doc records the exception.

## Test Plan

**Reproduce:** 8 XPU cards, TP4xSP2, ref2va at 1344x768 / 124 frames / 50 steps, Cache-DiT and model-level CPU offload for encoders and VAE. The first request finishes denoising (49/49) and then fails in `klvae.py:621 write_part -> torch.empty` with `XPU out of memory. Tried to allocate 1.43 GiB ... 1.42 GiB is free`, HTTP 500.

```bash
pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_offload.py
```

**vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`.

## Test Result

CPU tests pass (release only under active model-level offload; reply rank gets a host tensor, non-reply ranks a placeholder; video copied after quantization; no-offload path unchanged). On the 8-card run with this change: 0 OOM, 3/3 requests HTTP 200, 24 `Evicted 1 DiT module(s) (15.43 GiB)` lines. The device run used one output per prompt; the multi-seed overlap is covered by the CPU tests.

Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
JoseCarlosGarcia95 pushed a commit to valendra-tech/vllm-omni that referenced this pull request Sep 5, 2026
heyuanliu-intel added a commit to heyuanliu-intel/vllm-omni that referenced this pull request Sep 14, 2026
## Purpose

**Problem:** with model-level CPU offload, vllm-project#6072 evicts the DiT before VAE decode, but the decoded output stays on the device until the request returns. For `num_outputs_per_prompt > 1` the appended output of seed N is still resident when seed N+1 reloads the DiT, so the peak the eviction removed comes back; with a large output even the first request can OOM in decode.

**Fix:** on the executor reply rank, move each decoded output to the host inside the same seed iteration while active model-level offload is installed; non-reply ranks keep a metadata placeholder. Video is released after `_prepare_minimax_h3_video_output` quantizes to `uint8`, so vllm-project#6824's transfer optimisation is kept. Other offload modes and the no-offload path are unchanged; the design doc records the exception.

## Test Plan

**Reproduce:** 8 XPU cards, TP4xSP2, ref2va at 1344x768 / 124 frames / 50 steps, Cache-DiT and model-level CPU offload for encoders and VAE. The first request finishes denoising (49/49) and then fails in `klvae.py:621 write_part -> torch.empty` with `XPU out of memory. Tried to allocate 1.43 GiB ... 1.42 GiB is free`, HTTP 500.

```bash
pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_offload.py
```

**vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`.

## Test Result

CPU tests pass (release only under active model-level offload; reply rank gets a host tensor, non-reply ranks a placeholder; video copied after quantization; no-offload path unchanged). On the 8-card run with this change: 0 OOM, 3/3 requests HTTP 200, 24 `Evicted 1 DiT module(s) (15.43 GiB)` lines. The device run used one output per prompt; the multi-seed overlap is covered by the CPU tests.

Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
(cherry picked from commit ba947d4)
khairulkabir1661 pushed a commit to khairulkabir1661/vllm-omni that referenced this pull request Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda-test Used to trigger vllm-omni cuda CI separately. high priority high priority issue, needs to be done asap ready label to trigger buildkite CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants