Repository navigation
[Model][MiniMax-H3] Multi-GPU serving fixes and layer-wise offload component selection - #7047
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
This PR appears to be related to model: MinimaxH3. Model owners: @david6666666 @heyuanliu-intel, 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. |
Omni ReviewBot triage noteResolved as of |
|
please help review PR #5929, we will land that PR for layerwise offload component selection first |
|
I also suggest to split this PR into serverl independent PRs |
Oh, I see, I actually asked @heyuanliu-intel to create a merged PR because splits are quite many , here is his previous list #6990 [Bugfix][MiniMax-H3] Respect trust_remote_code when loading checkpoints |
ed463d7 to
6f2a58c
Compare
|
Thanks both. Since #5929 landed, I have re-based this PR on top of it and dropped everything it supersedes: |
| key = (tuple(stacked.shape), stacked.dtype, str(stacked.device)) | ||
| buffer = self._tile_gather_buffers.get(key) | ||
| if buffer is None: | ||
| buffer = stacked.new_empty((sp_size, *stacked.shape)) |
There was a problem hiding this comment.
[P2] Bound persistent gather memory across request geometries
Each new tile shape permanently retains another full gather buffer on every XPU rank. VAE offload and empty_cache() cannot release these dictionary-held tensors, so varying request geometries accumulates device memory and can eventually OOM. Stable-address reuse fixes repeated identical shapes, but needs a bounded workspace or an enforced, memory-budgeted set of geometry buckets.
|
intel ci failed |
Thanks for flagging. The Intel CI build for 6f2a58c (#8014) failed after 5 minutes, well short of the 30–50 minutes the same pipeline took on the neighbouring passing builds (#8013, #8015, #8016), and two unrelated builds in the same window failed the same short way (#8012 on main, #8020 on #7336). The AMD failure on that head (#11615) also coincides with failures on two unrelated PRs in the same hour (#11614 on #7155, #11618 on #6956). None of the tests the Intel job selects (-m "xpu and B60" over tests/diffusion, tests/dfx and tests/e2e) touch MiniMax-H3, and the commits here only change the MiniMax-H3 model directory, the Ring-attention padding path and one model_extras registry entry. I cannot see the Buildkite job log from outside the org. I have just pushed 93f0323 (addressing the P2 comment above), which re-triggers both pipelines; if the failure reproduces on this head, could someone with access paste the failing step? I will fix it right away if it is real. |
|
@heyuanliu-intel , please resolve the conflict. BTW, we have upgrade vllm-omni xpu version to 0.29 last friday, please help to test locally on XPU, thanks |
## 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)
…er across requests on XPU ## Purpose **Problem:** the tiled video VAE gathers every rank's decoded tiles with a variable-shape all-gather whose output is allocated afresh each call. After the per-request `empty_cache()` the next gather lands at a new device address. On XPU the collective backend registers a device-side resource per new receive address and does not reclaim it, so device memory outside `torch.xpu.memory_allocated()` grows every request until the card is full. (Mechanism inferred from intervention experiments, not observed directly.) **Fix:** keep one landing buffer per (shape, dtype, device) on the adapter and gather into it with `all_gather_into_tensor`; tiles are cloned out because the buffer is reused. Installed only when the adapter's device is XPU, bound on the checkpoint instance; other backends keep the checkpoint's own method. The buffer cache is uncapped on purpose: the key is the tile geometry, and evicting would re-register a new address. ## Test Plan **Reproduce:** 4 XPU cards, TP4/SP1, model-level offload on, send three identical t2va requests (1344x768, 209 frames, 2 steps) and sample each rank's non-PyTorch device memory between requests: it climbs monotonically while `memory_allocated()` stays flat. ```bash pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_vae_gather_reuse.py ``` **vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`. ## Test Result CPU tests pass (XPU-only install, tile order for even and uneven shares, buffer reuse at same geometry, tiles survive the next gather). On the 4-card A/B, non-PyTorch peak growth from request 1 to 2 dropped from 6617.69 MiB to 4008.76 MiB per rank (-39.4%); worst-rank free memory at request 2 rose from 2.93 MiB to 1662.50 MiB. Growth is reduced, not eliminated. Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> (cherry picked from commit afde5b4)
## Purpose **Problem:** MiniMax-H3 pads its packed sequence to an aligned length and passes `valid_kv_length` to the local attention backends. On the Ring path it instead builds an `attn_mask` at the global padded length (`minimax_h3_transformer.py:514`), which the Ring backends reject, so any padded request fails under Ring (the failure recorded in `recipes/MiniMaxAI/MiniMax-H3.md`). **Fix:** both Ring backends accept an optional `valid_kv_length` and trim each K/V block to the valid prefix of its source rank (`ring_utils.ring_kv_block_valid_length`); a fully padded block launches no attention. H3 publishes `valid_kv_length` on the Ring path instead of the mask. `causal=True` together with `valid_kv_length` raises `ValueError`: on a rectangular tile the causal diagonal would silently shift. With `valid_kv_length=None` every path is byte-for-byte the previous behaviour. ## Test Plan **Reproduce:** serve MiniMax-H3 with Ring sequence parallel and send a request whose packed length is not a multiple of the alignment. ```bash pytest -q tests/diffusion/attention/test_ring_padding.py pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py -k ring ``` **vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`. ## Test Result CPU tests pass (ring communicator and kernel stubbed, rank-2-of-3 block ordering covered). Reverting the K/V trim makes the ordering test observe `[4, 4]` instead of `[1, 4]`; removing the causal guard makes the two refusal tests fail with `DID NOT RAISE`. Not run on multi-rank hardware. Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> (cherry picked from commit ec68727)
## Purpose **Problem:** `MiniMaxH3Attention._run_packed_attention` reads the static `use_ring` flag in three places to decide between the Ring and local packed paths. Whether Ring actually runs is a runtime decision (`Attention._get_active_parallel_strategy` also honours `skip_sequence_parallel` and `forward_context.sp_active`), so a layer that declares Ring but is not running it this step takes the Ring branch with the wrong inputs. **Fix:** decide from the active strategy through one local helper; a layer not running Ring this step takes the local packed path (including NPU `npu_attn_varlen`). A layer genuinely running Ring behaves as before. Depends on the previous commit for the Ring branch. ## Test Plan **Reproduce:** configure Ring, then run a step with `sp_active=False` (or a layer with `skip_sequence_parallel`): the packed attention still takes the Ring branch. ```bash pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py -k ring ``` **vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`. ## Test Result CPU tests pass for the three combinations (Ring active, Ring declared but inactive, no Ring). Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> (cherry picked from commit 6f50ed4)
## Purpose **Problem:** `serving_video` rescales every reference image to the request canvas. MiniMax-H3 expects reference images at their native geometry, so ref2va silently replaces the reference aspect ratio with the canvas one. **Fix:** register an H3-specific `reference_image_size_resolver` through the existing `model_extras/registry.py` extension point that keeps the source geometry. Pipelines without a resolver keep the serving-side resize. ## Test Plan **Reproduce:** send a ref2va request with a 48x32 reference image and a 96x64 canvas; the engine receives a 96x64 image. ```bash pytest -q tests/model_extras/test_model_extras.py -k minimax_h3_preserves_reference_image_size pytest -q tests/entrypoints/openai_api/test_video_server.py -k minimax_h3_preserves_reference_geometry ``` **vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`. ## Test Result CPU tests pass: the engine receives the 48x32 image while sampling params still carry (96, 64). Dropping the registry entry makes the serving test observe 96x64 again. Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> (cherry picked from commit aac988a)
…t VAEs ## Purpose **Problem:** the pipeline never passes `trust_remote_code` to the two checkpoint-supplied VAE constructors in `pipeline_minimax_h3.py`, so a checkpoint that ships its own VAE class either fails to load or executes remote code the user did not opt into (the only existing use is `encoder.py:1118`, hard-coded `False`). **Fix:** thread `od_config.trust_remote_code` into both VAE adapters in `vae.py`; they forward it and default to refusing, with an error naming the component path and class. ## Test Plan **Reproduce:** serve MiniMax-H3 with `--trust-remote-code` and a checkpoint whose VAE is a dynamic module: the VAE is constructed without the flag. ```bash pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py -k trust_remote_code ``` **vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`. ## Test Result CPU tests pass. Deleting either `trust_remote_code=od_config.trust_remote_code` argument in the pipeline turns the six parametrized wiring cases red. Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> (cherry picked from commit 27e2baf)
## Purpose **Problem:** compiled regions are keyed by input shape. The t2va/fl2va packer rounds the used rows up to the next 64-row boundary, so requests whose prompt length, frame count or frame size differ land on different packed lengths, and a fixed-shape deployment recompiles per length. The ref2va packer already accepts an explicit `seq_len`; the t2va/fl2va packer has no such contract. **Fix:** share one resolver (`_resolve_padded_seq_len`) between the two packers and thread `extra_args["pad_seq_len"]` into both, validated at the request boundary (integer, multiple of 64, at most `MINIMAX_H3_MAX_PAD_SEQ_LEN`, not below the used rows). Without the pin both packers round up as before. ## Test Plan **Reproduce:** on a fixed-shape deployment send two requests whose used rows round to different 64-row buckets; the second recompiles. With `pad_seq_len` pinned (for example 30464 on a TP4xSP2 FL2VA service, log `used=30429 seq_len=30464`) both use one bucket. ```bash pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_packing.py pytest -q tests/diffusion/models/minimax_h3/test_minimax_h3_contract.py -k pad_seq_len ``` **vLLM Version:** as pinned by `requirements/`. **vLLM-Omni Commit:** rebased onto current `main`. ## Test Result CPU tests pass: rounding without a pin, explicit pin honoured, pin below used rows rejected by both packers, invalid values rejected at validation. No performance number is claimed. Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> (cherry picked from commit 277c2af)
…pace on XPU Addresses review feedback on vllm-project#7047 (P2): the per-geometry buffer dictionary never evicted, so every new tile shape permanently retained another full gather buffer on every XPU rank. VAE offload and empty_cache() cannot release dictionary-held tensors, so varying request geometries accumulated device memory. The adapter now keeps a single grow-only uint8 workspace and carves each gather out of its front. Resident memory is bounded by one buffer for the largest geometry seen instead of one buffer per geometry; a larger geometry replaces the block and the previous allocation is dropped with the last reference to it. Addresses stay stable for repeated and smaller geometries, which is what the XPU collective needs, and a byte workspace is dtype agnostic so mixed-precision requests share it as well. The hook is still installed only when the target device is XPU, so non-XPU devices keep the checkpoint's own gather and are unchanged. Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
|
@xuechendi Conflict resolved (rebased onto current main, merges clean). |
93f0323 to
f8589e2
Compare
|
@heyuanliu-intel , can you add a before / after comparison (perf / memory footprint ) with e2e test on BMG as well; Also put output for quick exam |
Import the CTRL-01 delta from 769a3cf relative to its main parent 2cbfd3e. Retain both CTRL-01 control arguments and main vllm-project#7047 pad_seq_len when resolving the three pipeline signature/call conflicts. Signed-off-by: Hu <hu.zhu@connect.polyu.hk> Signed-off-by: Alicia <115451386+congw729@users.noreply.github.com>
Resolve conflicts with vllm-project#7047 (pad_seq_len pin, model-CPU-offload output release). Both features are additive on the same call sites; keep guide_blocks and pad_seq_len side by side in the denoise-input keys, _build_denoise_inputs/diffuse signatures, the ref2va packer call, and the request context. Packing tests: keep the guide suites and append the upstream seq_len pin tests.
Merge upstream main at 72cc012. Preserve the indexed_modulation import introduced by vllm-project#7535 and the H3 serving changes from vllm-project#7047, while keeping QK RMSNorm/RoPE on the shared layers.ops entry instead of the legacy import. The canonical operator, compatibility aliases and registration stay unchanged. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: bugkeep <1921817430@qq.com>
…mponent selection (vllm-project#7047) Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com> Signed-off-by: Matthieu Laneuville <matthieu.laneuville@surf.nl>
…mponent selection (vllm-project#7047) Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
Purpose
Bringing MiniMax-H3 up on multi-card XPU serving surfaced a series of modeling-layer
defects: VAE residency fought between the pipeline and the offload backends, a padded
packed sequence that Ring attention rejects, reference images silently rescaled,
trust_remote_codenever reaching the checkpoint VAEs, and a gather buffer that grewdevice memory once per request. Fixing them also needed two capabilities the offloader
did not have: paging a declared encoder block stack in the single-process layer-wise
backend, and choosing which component families layer-wise offload manages at all.
1. [Bugfix] Keep MiniMax-H3 pipeline-staged VAEs out of offloader discovery
Problem: The MiniMax-H3 pipeline stages its video and audio VAEs itself: it
calls
load_to_device()/offload_to_cpu()around every encode and decode sothat only one VAE is resident at a time. The pipeline nevertheless declared both
VAEs in
_vae_modules, soModuleDiscoveryreported them as regular VAEcomponents.
SupportsComponentDiscoverydocuments_vae_modulesas "VAE(s)(always on GPU)", and consumers act on that contract: the layer-wise offload
backend moves every discovered VAE to the device when offloading is enabled, and
the HSDP loader path moves them after sharding. Both fight the pipeline's own
staging and pull the VAEs back onto the accelerator, costing memory that
layer-wise offloading is supposed to save.
Fix: Declare no VAE components for MiniMax-H3. The pipeline-staged VAEs are
simply not visible to component discovery, so no offload backend and no model
loader relocates them, and the discovery contract holds again for every other
pipeline. Because the VAEs are no longer discovered, the on-demand component set
in the offload plan drops them as well (only the text encoder stages on demand),
and the model-level CPU offload entry point now lists its stages explicitly
instead of deriving them from discovery, which keeps the text encoder and both
VAEs in model-level offload exactly as before. The
pipeline.vaealias is kept:registry-side VAE patch parallelism reads it, and it is not a discovery
attribute for a pipeline that declares components explicitly.
The user guide sentence about resident VAE modules is reduced to one sentence
naming pipeline-staged components rather than describing a backend special case.
2. [Bugfix][MiniMax-H3] Release decoded outputs before the next seed
Problem: with model-level CPU offload, #6072 evicts the DiT before VAE decode, but the decoded output stays on the device until the request returns. For
num_outputs_per_prompt > 1the 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_outputquantizes touint8, so #6824's transfer optimisation is kept. Other offload modes and the no-offload path are unchanged.3. [Bugfix][Hardware][Intel] MiniMax-H3: reuse the tiled-VAE gather buffer across requests on XPU
Problem: the tiled video VAE gathers every rank's decoded tiles with a variable-shape all-gather whose output is allocated afresh each call. After the per-request
empty_cache()the next gather lands at a new device address. On XPU the collective backend registers a device-side resource per new receive address and does not reclaim it, so device memory outsidetorch.xpu.memory_allocated()grows every request until the card is full. (Mechanism inferred from intervention experiments, not observed directly.)Fix: keep one landing buffer per (shape, dtype, device) on the adapter and gather into it with
all_gather_into_tensor; tiles are cloned out because the buffer is reused. Installed only when the adapter's device is XPU, bound on the checkpoint instance; other backends keep the checkpoint's own method. The buffer cache is uncapped on purpose: the key is the tile geometry, and evicting would re-register a new address.4. [Bugfix] Fix MiniMax-H3 packed padding with Ring attention
Problem: MiniMax-H3 pads its packed sequence to an aligned length and passes
valid_kv_lengthto the local attention backends. On the Ring path it instead builds anattn_maskat the global padded length (minimax_h3_transformer.py:514), which the Ring backends reject, so any padded request fails under Ring (the failure recorded inrecipes/MiniMaxAI/MiniMax-H3.md).Fix: both Ring backends accept an optional
valid_kv_lengthand trim each K/V block to the valid prefix of its source rank (ring_utils.ring_kv_block_valid_length); a fully padded block launches no attention. H3 publishesvalid_kv_lengthon the Ring path instead of the mask.causal=Truetogether withvalid_kv_lengthraisesValueError: on a rectangular tile the causal diagonal would silently shift. Withvalid_kv_length=Noneevery path is byte-for-byte the previous behaviour.5. [Bugfix] Honor runtime SP state in H3 Ring padding
Problem:
MiniMaxH3Attention._run_packed_attentionreads the staticuse_ringflag in three places to decide between the Ring and local packed paths. Whether Ring actually runs is a runtime decision (Attention._get_active_parallel_strategyalso honoursskip_sequence_parallelandforward_context.sp_active), so a layer that declares Ring but is not running it this step takes the Ring branch with the wrong inputs.Fix: decide from the active strategy through one local helper; a layer not running Ring this step takes the local packed path (including NPU
npu_attn_varlen). A layer genuinely running Ring behaves as before. Depends on the previous commit for the Ring branch.6. [Bugfix][MiniMax-H3] Preserve reference image geometry
Problem:
serving_videorescales every reference image to the request canvas. MiniMax-H3 expects reference images at their native geometry, so ref2va silently replaces the reference aspect ratio with the canvas one.Fix: register an H3-specific
reference_image_size_resolverthrough the existingmodel_extras/registry.pyextension point that keeps the source geometry. Pipelines without a resolver keep the serving-side resize.7. [Bugfix][MiniMax-H3] Respect trust_remote_code when loading checkpoint VAEs
Problem: the pipeline never passes
trust_remote_codeto the two checkpoint-supplied VAE constructors inpipeline_minimax_h3.py, so a checkpoint that ships its own VAE class either fails to load or executes remote code the user did not opt into (the only existing use isencoder.py:1118, hard-codedFalse).Fix: thread
od_config.trust_remote_codeinto both VAE adapters invae.py; they forward it and default to refusing, with an error naming the component path and class.8. [Model][MiniMax-H3] Let a request pin the packed-sequence length
Problem: compiled regions are keyed by input shape. The t2va/fl2va packer rounds the used rows up to the next 64-row boundary, so requests whose prompt length, frame count or frame size differ land on different packed lengths, and a fixed-shape deployment recompiles per length. The ref2va packer already accepts an explicit
seq_len; the t2va/fl2va packer has no such contract.Fix: share one resolver (
_resolve_padded_seq_len) between the two packers and threadextra_args["pad_seq_len"]into both, validated at the request boundary (integer, multiple of 64, at mostMINIMAX_H3_MAX_PAD_SEQ_LEN, not below the used rows). Without the pin both packers round up as before.9. [Bugfix] Honor declared encoder block stacks in layer-wise offload
Problem:
LayerWiseOffloadBackend.enable()pins every encoder on the device for the whole request. A pipeline can declare which encoder submodules are streamable block stacks (OffloadPlan.encoder_block_attrs) and the distributed backend honours that, but the single-process backend never reads it, so a large vision-language encoder stays resident, which is most of the memory layer-wise offload exists to free.disable()also left encoder hooks installed.Fix: read the declaration in the single-process backend and page the declared block stacks like DiT blocks; non-block state (norms, embeddings, projections) is still placed on the device.
disable()tears the encoder hooks down and restores each block's weights to the host throughLayerwiseOffloadHook.restore_next_block_to_host(), rebuilding them withtorch.as_stridedfrom the storedstrideso transposed (non-contiguous) weights keep their element order;prefetch_layer()is not used because it would put every block on the accelerator at once.10. [Core] Let layer-wise offload select which component families it manages
Problem: layer-wise offload is all-or-nothing. There is no way to say "manage the encoders and VAEs, keep the DiT whole", which is the topology to run whenever the DiT fits on the device but the encoders and VAEs do not.
Fix:
layerwise_offload_components, a comma list from{dit, text_encoder, vae}. A family left out stays fully device-resident: excludedditis placed whole with no block hooks; excludedtext_encoderis placed with no streaming and no parking even if the pipeline declares block stacks or on-demand; excludedvaeis placed even if declared on-demand. Omitting the option selects every family (byte-for-byte today's behaviour). Unknown or empty selections fail at config validation.11. [Model][MiniMax-H3] Honour the layer-wise component selection when staging encoders and VAEs
Problem:
layerwise_offload_componentspromises that an excluded family stays device-resident, and the backend keeps that promise. The MiniMax-H3 pipeline manages encoder and VAE residency itself and never reads the selection:_component_on_device()stages purely fromenable_layerwise_offload, and the construction site builds both VAEs on the host. With--layerwise-offload-components dit,text_encoderthe backend places the VAEs on the device and the pipeline pulls them back to the host after every decode, a full round trip per request for weights the operator asked to keep resident.Fix: route the three sites through
_stages_component_family(), which reads the selection under plain layer-wise offload and keeps returningTrueunder distributed layer-wise offload (that backend shards every component).12. [Bugfix][MiniMax-H3] Always stage the pipeline-owned VAEs
Problem: Two changes in this series meet on the MiniMax-H3 VAEs and cancel
each other out. Keeping the pipeline-staged VAEs out of component discovery
means no offload backend ever places them: the pipeline is the only owner of
their residency. Routing the pipeline's staging sites through
_stages_component_family()then makes that ownership conditional on--layerwise-offload-componentsnaming thevaefamily. With--layerwise-offload-components dit,text_encoderthe VAEs are consequentlyneither placed by the backend nor staged by the pipeline: they stay wherever
they were loaded and run from there, which is neither the "fully
device-resident" contract the selection publishes for an excluded family nor
the low-residency behaviour the operator enabled layer-wise offload for.
Fix: Honour the selection only for the families component discovery
actually reports.
_stages_component_family("vae")now returnsTruewheneverlayer-wise offloading is active, so the pipeline keeps staging its own VAEs
around every encode and decode and keeps building them in host memory; the
text_encoderfamily is unchanged and still follows the selection. The userguide and the
--layerwise-offload-componentshelp text say so: thevaefamily does not change what the backend does with discovered VAEs, and
pipeline-staged VAEs are always staged by the pipeline.
Changes outside the MiniMax-H3 directory
Every file below is outside
vllm_omni/diffusion/models/minimax_h3/. For each one:what changed, whether the default behaviour of any other model changes, and the
evidence for that answer.
Attention (Ring path, shared by every model that runs Ring sequence parallelism)
vllm_omni/diffusion/attention/backends/abstract.pyvalid_kv_lengthmetadata key is documented as usable by Ring backends, not only by backends advertisingsupports_prefix_kv_slicing.vllm_omni/diffusion/attention/backends/ring/ring_utils.pyring_kv_block_valid_length(), a pure function mapping a circulated block index to its valid prefix length.tests/diffusion/attention/test_ring_padding.pycovers the helper directly.vllm_omni/diffusion/attention/backends/ring_flash_attn.py,ring_pytorch_attn.pyvalid_kv_length; each circulated K/V block is trimmed to the valid prefix of its source rank, and a fully padded block launches no attention.causal=Truecombined withvalid_kv_lengthraisesValueErrorinstead of silently shifting the causal diagonal.valid_kv_length is None, which is the value for every caller that does not pass it.Nonepath is the pre-existing code path;tests/diffusion/attention/test_ring_padding.pyasserts the unpadded results are identical to the previous behaviour and that the causal combination raises.vllm_omni/diffusion/attention/parallel/ring.pyvalid_kv_lengthfrom attention metadata to the two backends.None.Layer-wise offloader
vllm_omni/diffusion/offloader/layerwise_backend.pyOffloadPlan.encoder_block_attrs(previously honoured only by the distributed backend), tears those hooks down indisable(), and gates the DiT / encoder / VAE handling on the new component selection. Extractsstream_declared_encoder_blocks()as the shared helper.encoder_block_attrs, and unchanged for every pipeline when the selection is left at its default (all families).tests/diffusion/offloader/test_layerwise_backend.pyandtests/diffusion/offloader/test_layerwise_component_selection.py; the declaration is opt-in per pipeline, and the default selection is the full family set (OffloadConfig.layerwise_components).vllm_omni/diffusion/offloader/distributed_layerwise_backend.pyvllm_omni/diffusion/offloader/base.pyOffloadConfig.layerwise_components, defaulting to all three families, populated from the diffusion config.frozenset({"dit", "text_encoder", "vae"}), which reproduces the previous all-or-nothing behaviour.Configuration and entrypoints (plumbing for one new option)
vllm_omni/diffusion/data.pylayerwise_offload_componentspluslayerwise_component_selection()and validation that rejects unknown or empty selections.Noneselects every family.layerwise_component_selection()returns the full family set when the option is unset; covered bytests/diffusion/offloader/test_layerwise_component_selection.py.vllm_omni/config/omni_config.py,vllm_omni/config/stage_config.py,vllm_omni/engine/arg_utils.py,vllm_omni/engine/async_omni_engine.py,vllm_omni/entrypoints/cli/serve.pyNone, carrying--layerwise-offload-componentsto the diffusion config.None-defaulted declaration plus the argument definition.Serving-side model extras
vllm_omni/model_extras/registry.pyreference_image_size_resolverfor the two MiniMax-H3 pipeline classes through the existing extras mechanism.MiniMaxH3PipelineandMiniMaxH3ModularPipeline;tests/model_extras/test_model_extras.pyandtests/entrypoints/openai_api/test_video_server.pyassert that a pipeline without the resolver still rescales.Tests and documentation outside the model directory
tests/diffusion/attention/test_ring_padding.py(new),tests/diffusion/offloader/test_layerwise_backend.py,tests/diffusion/offloader/test_layerwise_component_selection.py,tests/entrypoints/openai_api/test_video_server.pyand
tests/model_extras/test_model_extras.pycover the changes above.docs/design/feature/offloader/layerwise_offload.md,docs/user_guide/diffusion/offloader/layerwise_offload.md,docs/design/feature/sequence_parallel.md,docs/user_guide/diffusion/regional_compilation.mdandrecipes/MiniMaxAI/MiniMax-H3.mddocument the new option,the Ring padding contract and the packed-length pin.