Skip to content

[Model] Support SenseNova-U1.5-8B-MoT and its distilled 8-step LoRA - #6516

Merged
hsliuustc0106 merged 32 commits into
vllm-project:mainfrom
MrlixiangWE:sensenova-u15-distill-lora
Sep 1, 2026
Merged

hsliuustc0106 merged 32 commits into
vllm-project:mainfrom
MrlixiangWE:sensenova-u15-distill-lora

Conversation

@MrlixiangWE

@MrlixiangWE MrlixiangWE commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

FIX #6471

SenseNova-U1.5-8B-MoT runs on the existing pipeline, but its distilled 8-step LoRA loaded
without error and changed no weight. This fixes that, and cuts denoise latency.

Propose

  1. --lora-backend distill was accepted and then warned Pipeline does not support loading distilled LoRA weights for now, generating without the adapter. Implement load_lora_weights
    on SenseNovaU1Pipeline, and raise when nothing matches instead of loading a no-op.
  2. _prepare_lora_delta picked stacked mappings with if param_name not in base_key and did not
    stop at the first hit. .qkv_proj is a substring of .qkv_proj_mot_gen, so both rules fired
    and the delta came out twice the height of the parameter. Match on the tail and stop at the
    first hit. The fused names shipped by QwenImage and Wan2.2 do not overlap, so this selects the
    same shards for them; a test pins that.
  3. Fold the adapter in fp32, so the low-rank product rounds to the parameter dtype once.
  4. Qwen3RMSNorm.forward used an eager cast chain. Use F.rms_norm, which keeps the accumulation
    in fp32 through the weight multiply instead of rounding to bf16 first.
  5. Annotate the four attributes mypy flags in loader.py; the file is now clean.
  6. Add recipes/SenseNova/SenseNova-U1.5.md, and list U1.5 in supported-models and
    diffusion-features.

Test Plan

cd tests && pytest diffusion/models/sensenova_u1/ diffusion/lora/test_loader.py -q
cd tests && pytest -m "core_model and cpu" -q

# 1x A800-80G, seed 42, one engine per arm, 1 warmup + 5 timed, medians
examples/offline_inference/text_to_image/text_to_image.py --model <U1.5> \
  --width 1024 --height 1024 --num-inference-steps 50 --cfg-scale 4.0
# and --num-inference-steps 8 --cfg-scale 1.0 --lora-path <8step> --lora-backend distill

vllm serve <U1.5> --omni --trust-remote-code [--lora-path <8step> --lora-backend distill]

Test Result

Latency, 1x A800-80G, seed 42, n=5 medians

Case before after change
1024x1024, 50 steps, cfg 4.0 11726.9 ms 10973.6 ms -6.42%
1536x1536, 50 steps, cfg 4.0 25674.1 ms 23737.9 ms -7.54%
1024x1024, 8 steps, cfg 1.0, LoRA 1114.4 ms 1024.2 ms -8.09%

Peak VRAM unchanged, 34364 -> 34340 MiB. Five-run spread 0.3-3.7%.

Distilled LoRA, 1024x1024, 8 steps, cfg 1.0

Case p50 five-run range
main, --lora-path given 1114.4 ms 1108-1139
main, no --lora-path 1109.1 ms 1106-1117
this PR, --lora-path given 1024.2 ms 1023-1035
this PR, no --lora-path 1026.6 ms 1018-1056

On main the first two rows differ by +0.48%: the adapter is dropped, so both runs use the base
weights. With this PR it is applied, 588 LoRA keys into 168 parameters, and still costs nothing
against the same-step control, -0.23%, inside the 1.16% and 3.70% run spreads.

Accuracy of the RMSNorm change

Mean relative error against a float64 reference, same bf16/fp16 inputs:

shape dtype eager chain F.rms_norm
4096x3584 bf16 1.890e-3 1.409e-3
16384x4096 bf16 1.886e-3 1.409e-3
16384x8192 fp16 2.368e-4 1.761e-4

Lower in all 12 shape/dtype combinations tested: the eager path rounds to bf16 before the weight
multiply, F.rms_norm rounds once. CFG-parallel output is byte-identical to single GPU.

Tests

Suite this PR 3781214
tests/diffusion/models/ 5 failed, 1867 passed, 4 errors 5 failed, 1819 passed, 4 errors
tests/diffusion/lora/ 36 passed, 2 errors 31 passed, 2 errors
L1 core_model and cpu 137 failed, 9476 passed, 37 errors 137 failed, 9423 passed, 37 errors

Compared as failing-test-ID sets rather than counts: the two sides are identical in all three, at 7,
2 and 139 ids. The pass-count differences are the tests this PR adds. sensenova_u1/ alone is 86
passed, and restoring the pre-PR implementation under these tests turns 20 of them red.

Serving

Server text2text img2text text2img
full model ok ok ok
+ distilled LoRA ok ok ok

vLLM Version: 0.27.0
vLLM-Omni Commit: 3781214

The distill LoRA backend accepted --lora-path and then warned that the
pipeline could not load it, so generation ran without the adapter. Add
load_lora_weights to SenseNovaU1Pipeline, and raise when nothing matches.

The shared stacked-delta helper picked fused mappings by substring and did
not stop at the first hit, so .qkv_proj and .qkv_proj_mot_gen both fired and
produced a delta twice the height of the parameter. Match on the tail and
stop at the first hit. The fused names shipped by QwenImage and Wan2.2 do
not overlap, so this selects the same shards for them.

Replace the eager RMSNorm cast chain with F.rms_norm: -7.47% at 1024x1024
and -7.56% at 1536x1536 on one A800, and closer to a float64 reference in
every shape tested.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.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 belong to: docs/design/module/diffusion/diffusion_model_integration.md.

Module owners: @fhfuih @Bounty-hunter @wtomin @RuixiangMa

@MrlixiangWE, 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.

@MrlixiangWE

MrlixiangWE commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Same prompt and seed, 1024x1024, on this branch.

50 steps, cfg 4.0 8 steps, cfg 1.0, no LoRA 8 steps, cfg 1.0, distilled LoRA
cat cat_50step cat_8step cat_8step_lora
dog dog_50step dog_8step dog_8step_lora

10973.6 ms / 1026.6 ms / 1024.2 ms.

recipes/ sits outside the mkdocs docs tree, so the relative link warned and
fail_on_warning failed the Read the Docs build. Every other recipe in this
table is linked by absolute GitHub URL.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
@MrlixiangWE

Copy link
Copy Markdown
Contributor Author

@hsliuustc0106 this PR is ready for review.

@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.

Single-GPU validation passed (36 tests; full-model latency -11.7%), but TP=2 distilled LoRA startup crashes on both ranks.

state_dict = {k: v.to(torch.float32) for k, v in state_dict.items()}
state_dict = _apply_diffusers_lora_alpha_scaling(state_dict)

loaded_keys = self.load_lora_into_module(state_dict, self, prefix="")

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.

This breaks distilled LoRA with --tensor-parallel-size 2: both ranks load a 3,072-row local QKV weight, but _prepare_lora_delta builds a 6,144-row global delta and params.add_ raises. Please slice the delta through vLLM's parameter weight-loader/TP metadata and add a TP=2 regression test.

@MrlixiangWE MrlixiangWE Aug 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a312a94 — the delta is written through the parameter's own weight loader, so each rank takes only its shard. test_fused_delta_is_sharded_through_the_weight_loader pins it on CPU, and TP=2 with the distilled LoRA now runs on two A800s.

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.

Verified all five at be42af5 — the delta now routes through _prepare_lora_delta per stacked shard (with the loader test), the RMSNorm test asserts the fused path is closer to float64, only the adapter-name sentinel survives fusion, the cache-cleared cold-start table plus the 85acb30 warmup covers the readiness question, and 9e0561c documents the paged-cache scope and deletion condition. Thanks. Two things remain: my 08-27 A/B-parity comment is still open, and 0d68f32 adds ~460 lines of unreviewed 8B-MoT support to this PR — intentional, or should that land as a follow-up PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional. U1.5 shares SenseNovaU1Pipeline with U1 -- same
model_type: neo_chat, and SenseNovaU1Config already reads use_pixel_head
and noise_scale_max_value. Supporting it is making the distilled LoRA path
correct. The 460 lines:

File + What
tests (3 files) 258 tests
recipes/SenseNova/SenseNova-U1.5.md 114 recipe
pipeline_sensenova_u1.py 67 load_lora_weights, stacked_params_mapping
vllm_omni/diffusion/lora/loader.py 14 _prepare_lora_delta fix
sensenova_u1_transformer.py 5 F.rms_norm
two doc table rows 2 supported-models, diffusion-features

Tests on the latest commit are running here; I will reply on each thread when
they finish, about six hours if nothing breaks.

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.

Delta round at e32770a — reviewed the four commits since my last pass (3149b71 one-step resize publish, d7aae00 LoRA-wrapper capture gating, dcd1109 keyword-only flash-attn args, e32770a fusion-failure doc); all clean, and the fault-injected test_a_failed_grow_leaves_the_cache_on_its_old_buffers is a nice touch. The A/B-parity ask is resolved for me: the same-seed table above plus 8fb8683's whole-loop decode comparison is exactly the durable coverage I wanted — please push that commit into this PR so the test and the recipe index are actually in-tree. Still outstanding from my 08-28 review: is 0d68f32's ~460-line 8B-MoT support intentional for this PR, or should it land as a follow-up?

@MrlixiangWE MrlixiangWE Aug 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional. This PR closes #6471, which asks for SenseNova-U1.5-8B-MoT support; the model already runs on the existing pipeline, and 0d68f32, the PR's first commit, is what makes its distilled LoRA correct, so it belongs here rather than in a follow-up. Per-file breakdown in #6516 (comment).

8fb8683 is in the PR already, commit 17 of 25: recipes/README.md +1/-0 and the whole-loop decode test +53/-0.

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.

Conceded — my framing was wrong on both counts: 0d68f32 was this PR's first commit (the #6471 support itself, predating my review), and the growth I attributed to it was actually the nine 6194f41→be42af5 commits answering review threads. With the scope question resolved and the A/B coverage question reduced to pushing 8fb8683, the only remaining item from my side is that push. Thanks for the detailed accounting.

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.

One more ask before approval: an e2e stage-attribution table in the same format #6714 and #6607 already use, so the paged-decode and LoRA claims are isolated rather than blended. The VLLM_OMNI_SENSENOVA_PAGED_DECODE switch makes this a clean A/B on the same head:

Conditions: 1×A800 80GB, same prompt + seed + resolution across columns, 2 warmups then N≥10 measured, P50 (P100). Baseline = main <sha>, candidates = head <sha> with the switch as shown.

Metric main eager (50-step) PR paged=0 (50-step) PR paged=1 (50-step) PR paged=1 + LoRA (8-step)
Requested inference steps 50 50 50 8
e2e_total_ms
Stage 0 — AR prefill + decode (incl. think decode, tokens)
Stage handoff (e2e − stages)
Stage 1 — diffusion execution
Postprocess
MP4/PNG response encoding
Approx request → output
Sanity: output pixels / generated tokens identical identical identical identical

main vs paged=0 proves the loading/fusion changes cost nothing at equal steps; paged=0 vs paged=1 isolates the paged-KV + CUDA-graph decode gain (and doubles as the scheduler-overhead measurement for the model-local-cache question); the LoRA column separates the step-reduction claim. A small companion table — requests that hit a new decode bucket and the max capture stall — would keep the mid-serving re-capture tail honest, since P50s hide it.

@MrlixiangWE MrlixiangWE Aug 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stage attribution. One note on the shape first: stage_metrics reports a single stage named diffusion for this model, because SenseNovaU1Pipeline, which U1.5 shares with U1, runs its whole autoregressive loop inside one pipeline forward and the decode steps never reach the scheduler — the same fact behind the model-local cache. The AR/diffusion split below is therefore measured inside that stage, not across two of them.

Conditions: sensenova/SenseNova-U1.5-8B-MoT, 1×A800 80GB, TP=1, same prompt + seed 42 + 1024×1024 across columns, think on in every column, 2 warmups then N=10 measured, P50 (P100). Baseline = main 262ff14, candidates = head e32770a with the switch as shown.

Metric main eager (50-step) PR paged=0 (50-step) PR paged=1 (50-step) PR paged=1 + LoRA (8-step)
Requested inference steps 50 50 50 8
e2e_total_ms 27.747 s (28.159) 16.500 s (16.702) 13.930 s (13.944) 4.455 s (4.485)
Stage 0 — AR prefill + think decode 16.281 s (16.681) 6.283 s (6.461) 3.604 s (3.612) 3.593 s (3.603)
  · AR prefill 69.0 ms (78.0) 38.9 ms (39.1) 39.3 ms (39.5) 39.0 ms (41.9)
  · think decode 16.146 s (16.545) 6.213 s (6.391) 3.534 s (3.539) 3.522 s (3.533)
Stage 0 — diffusion execution 11.460 s (11.492) 10.251 s (10.271) 10.317 s (10.335) 0.854 s (0.877)
Stage handoff (e2e − stage_gen) 1.5 ms (2.4) 1.5 ms (2.1) 1.3 ms (1.5) 1.0 ms (1.3)
Postprocess 0.0 ms 0.0 ms 0.0 ms 0.0 ms
Sanity: output pixels 1048576 1048576 1048576 1048576
Sanity: think chars 1233 1233 1233 1233

Isolated that way:

Comparison e2e think decode diffusion
main → paged=0 (loading/fusion, RMSNorm, RoPE) -40.53% -61.52% -10.56%
paged=0 → paged=1 (paged KV + CUDA graph) -15.57% -43.11% +0.65%

The earlier same-seed A/B ran with think off, where the decode loop this switch serves barely executes — hence +0.02% there against -15.57% here.

Stage handoff stays at 1.0–1.5 ms across all four, which is also the scheduler-overhead number for the model-local-cache question.

There is no PNG encoding row and no separate request → output row: the offline path returns the image in-process, so nothing encodes a response and e2e_total_ms is that number.

Capture tail

Every capture timed individually, since P50s hide it. paged=1, 1 warmup + 5 measured per row.

workload requests captures bucket growths max capture stall
T2I + think 5 2 1 29.3 ms
edit, 2 images 5 1 0 28.9 ms
image-to-text 5 1 0 30.1 ms

Three prefill lengths against the same buckets (512, 1024, 2048, 4096, 8192), to reach the growth path rather than report a stall we never triggered:

prefill bucket growths captures max stall trail
237 chars 1 2 30.1 ms 512 → 1024 at length 513
2007 chars 1 1 34.0 ms 1024 → 2048 at length 1025
4367 chars 0 0 — starts in a bucket wide enough

One re-capture costs 30–34 ms against a 14 s request. Five edit requests captured once between them, which is the cross-request reuse.

The other dispatch entry points

forward dispatches three ways, and the table above covers _forward_t2i only. The other two, same head, switch as the only difference, 1 warmup + 5 measured:

entry point workload paged=1 paged=0 delta output identical
_forward_it2i edit, 2 images 24.050 s 30.158 s -20.25% image, not compared here
_forward_text image-to-text 7.263 s 12.532 s -42.04% no — 2031 vs 2190 chars
_forward_text text-to-text 7.014 s 12.640 s -44.51% no — 2750 vs 2789 chars

The two text divergences are the greedy-argmax ties already measured on the same-seed A/B at #6516 (comment): the first differing step has a top1-top2 margin of 0.000 and 0.125 against a |Δlogit| of 0.188 and 0.164.

want = _reference(x, norm.weight, norm.variance_epsilon)
assert got.dtype == dtype
assert got.shape == x.shape
torch.testing.assert_close(got.to(torch.float32), want.to(torch.float32), atol=atol, rtol=atol)

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.

Restoring the pre-PR eager RMSNorm implementation still leaves all 14 tests in this file green, so the claimed low-precision rounding improvement is not regression-protected. Please add a deterministic bf16/fp16 case that distinguishes F.rms_norm from the legacy path, or compare both errors against the float64 reference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You were right that the suite stayed green with the old implementation restored. test_rmsnorm_is_closer_to_float64_than_the_cast_chain now measures both implementations against the float64 reference and asserts the fused one is closer, so restoring the cast chain fails it.

loaded_keys = self.load_lora_into_module(state_dict, self, prefix="")
if not loaded_keys:
raise ValueError(f"Distilled LoRA {paths[0]} matched no parameter of this pipeline.")
self.lora_loaded[adapter_name] = state_dict

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.

This keeps the full fp32 adapter state alive even though SenseNova has no unload path. The 814.9 MB BF16 file expands to 1.52 GiB of tensors and raised steady process-tree RSS by 1.95 GiB locally; keep only an adapter-name sentinel after one-way fusion unless unload is added.

@MrlixiangWE MrlixiangWE Aug 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a312a94 — only an adapter-name sentinel survives the one-way fusion. test_fusion_does_not_retain_the_adapter_tensors asserts what is kept is neither a dict nor a tensor, and that a second load is still a no-op.

Under tensor parallelism the delta read from the checkpoint spans the whole
layer while the parameter holds only this rank's shard, so adding it directly
raised a shape error and startup failed on every rank. Route each delta through
the parameter's own weight loader, which is the same code that placed the base
weights, into a zeroed copy of the parameter, and add that.

Fusion is one-way and this pipeline has no unload path, so keep only a sentinel
instead of the fp32 state dict, which measured 1.52 GiB resident.

The RMSNorm tests passed with the previous implementation restored, so they did
not protect the accuracy claim. Compare both implementations against the float64
reference and assert the fused one is closer.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
@hsliuustc0106

Copy link
Copy Markdown
Collaborator

I profiled one 1024×1024 step with Nsight Systems 2026.1.3 on an L20X, capturing only the third worker-side forward after the engine warmup and one user warmup.

Path Captured forward Kernels Kernel time GPU busy Launch API Inter-launch gaps
Full, CFG 4 350.1 ms 8,248 89.9 ms 94.1 ms (26.9%) 42.0 ms 277.2 ms
No-LoRA control, CFG 1 254.3 ms 6,372 56.7 ms 60.3 ms (23.7%) 31.8 ms 194.7 ms
LoRA, CFG 1 (a312a949) 285.4 ms 6,372 56.9 ms 58.2 ms (20.4%) 34.6 ms 225.9 ms

Inter-launch gaps overlap GPU execution and are not additive to kernel time; they measure host intervals between consecutive launch APIs.

This is host-dispatch/kernel-launch limited, not device-compute limited:

  • The two prefix forwards launch 4,204 kernels for only ~21.7 ms of GPU work, but take 143-171 ms on the host.
  • CFG-1 denoise launches 1,877 kernels: ~34.1 ms GPU versus 71-76 ms host wall. CFG 4 nearly doubles this path to 3,753 kernels / 67.0 ms GPU.
  • CFG-1 averages an 8.9 µs kernel, ~5 µs inside each launch API, and 31-35 µs between launches. The GPU is idle for 170-203 ms inside its active window.
  • LoRA and no-LoRA CFG-1 have exactly the same kernel name/grid/block multiset, CUDA API counts, and memcpy signatures. The adapter adds no per-step kernel after fusion; the production gain comes from 8 steps + CFG 1.
  • In the LoRA path, 2,119 copy/cast kernels spend 10.6 ms launching and only 5.6 ms executing; 900 elementwise-multiply kernels spend 4.3 ms launching and 2.9 ms executing.

Can you optimize the launch path before merge? The runtime currently reports that regional compilation is skipped because SenseNovaU1ForCausalLM has no _repeated_blocks. The first experiment I suggest is:

_repeated_blocks = ["SenseNovaU1DecoderLayer"]

Please validate both UND/GEN specializations, resolutions, TP, and distilled LoRA, then provide the same-input A/B with:

  1. kernel count and GPU-busy percentage;
  2. latency mean ± stddev over at least three post-warmup runs;
  3. output-quality parity with the same seed.

A successful change should materially reduce the 6,372 launches, especially copy/cast and elementwise kernels. If regional compile still graph-breaks or leaves the count unchanged, the next target should be CUDA Graph replay for stable resolution/sequence/CFG buckets. GEMM tuning alone has a low ceiling here because GEMMs are only about 11-15% of end-to-end wall time.

Nsight collection overhead was ~6 ms. The 9-11 second application timing printed during capture is invalid because cudaProfilerStop blocks while Nsight writes the report; the table uses timestamps inside the NVTX capture range.

…che and a CUDA graph

Decode is about 80% of a think request, and almost all of the GPU idle in that
window sits inside no CUDA call at all -- it is Python/ATen dispatch. A CUDA
graph retires those dispatches, but capture needs static shapes and
DynamicCache grows K/V with torch.cat on every step. Padding to a bucket and
masking the tail does give static shapes, but the mask costs more than the
graph saves: 7.96-11.55 ms per step against 1.03 ms unmasked, measured on an
A800 at real decode shapes.

A paged cache gives both. flash_attn_varlen_func takes the used length as a
tensor (seqused_k) alongside a block_table, so the buffers stay bucket-sized
and capturable while the kernel reads only the valid prefix, and one capture
serves every length in the bucket.

Three per-layer costs go with it. The 3D RoPE tables were rebuilt inside all
84 modules -- 1,008 calls producing 17 distinct tables -- and are now built
once per forward. K/V are no longer expanded before the attention backend,
which was hiding the GQA shape from the backend's own fused-kernel check.
Single-token decode no longer builds an all-zeros mask whose only effect was
to keep SDPA off those kernels.

The path is gated by VLLM_OMNI_SENSENOVA_PAGED_DECODE and by a signature probe
on flash_attn_varlen_func, so a wheel without seqused_k/block_table falls back
to SDPA instead of raising at the first decode step.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
VLLM_OMNI_SENSENOVA_PAGED_DECODE=0 forces the ordinary cache; the automatic
fallback covers devices and wheels that cannot support the paged path.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
@MrlixiangWE

MrlixiangWE commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Both suggestions are in. Taking the buckets first: they are not where the idle time is.

Corrected GPU busy — kernel time from the profiled run, wall clock from a clean one — is 92.0% for text-to-image and 68.5% for understanding/think. Replay over stable resolution/sequence/CFG buckets is worth at most ~7.5% there; the idle sits in AR decode, at 31.5%. Its attribution is 8.7% cudaLaunchKernel, 0.2% cudaStreamSynchronize, and ~86% inside no CUDA call at all (Python/ATen dispatch), which rules out the .item() sync and selects CUDA Graphs.

The blocker was the cache. DynamicCache.update grows K/V with torch.cat on every step, so decode has no static shape. Padding to a bucket and masking the tail does give static shapes, but the mask costs more than the graph saves: 7.96-11.55 ms per step against 1.03 ms unmasked, at real decode shapes. A paged cache gives both — flash_attn_varlen_func takes the used length as a tensor (seqused_k) alongside a block_table, so the buffers stay bucket-sized and capturable while the kernel reads only the valid prefix, and one capture serves every length in the bucket.

Whole PR, main -> this branch (1x A800-80G, seed 42, n=5, one session)

Case main This branch Change
think 1024x1024 17710.2 +/- 62.8 ms 5424.2 +/- 10.1 ms -69.37%
T2I 1024x1024, 8 steps, cfg 1.0, distilled LoRA 1120.8 +/- 4.0 ms 924.4 +/- 2.4 ms -17.52%
T2I 1024x1024, 50 steps, cfg 4.0 11862.3 +/- 59.9 ms 10560.7 +/- 26.5 ms -10.97%
T2I 1536x1536, 50 steps, cfg 4.0 25779.6 +/- 48.3 ms 23131.7 +/- 49.6 ms -10.27%

On main the distilled adapter is silently dropped, so that row is the user-facing before/after rather than a like-for-like speed comparison.

Launch count, the criterion you set

Same input, one profiled generation of T2I 1024x1024, 8 steps, cfg 1.0, distilled LoRA.

Measure main This branch Change
cudaLaunchKernel 24,244 7,782 -67.90%
GPU kernels 25,924 8,232 -68.25%
Kernel time 962.0 ms 852.5 ms -11.38%
GPU busy 86.1% 93.1% +7.0 pt

cudaGraphLaunch is 0 on both legs: the graph covers autoregressive decode, which text-to-image never runs. The reduction here is entirely the RoPE hoist, the uncompressed K/V and the dropped decode mask. Kernel count falls 68% while kernel time falls 11%, which matches your reading that these are ~9 us kernels whose cost is the launch, not the execution.

Tensor parallelism (a312a94 -> this branch, 2x A800, SYS/PCIe, no NVLink)

Case Before After Change
think 1024x1024, TP=2 20678.3 +/- 28.8 ms 5221.0 +/- 4.6 ms -74.75%
T2I 1024x1024, 8 steps, cfg 1.0, distilled LoRA, TP=2 1234.7 +/- 25.9 ms 1127.8 +/- 11.2 ms -8.66%

The baseline is a312a94 rather than main because this configuration is the one that crashed at startup before that commit. The T2I leg is bit-identical; think with the adapter loaded also captures and replays cleanly.

Memory, same bytes with only VLLM_OMNI_SENSENOVA_PAGED_DECODE differing

Measure Off On Delta
Peak host RSS 7345 MiB 8146 MiB +801 MiB, during capture
Resident after the run 3589 MiB 3612 MiB +23 MiB
Peak VRAM, schedule pinned to the 8192 bucket 35107 MiB 35971 MiB +864 MiB (+2.46%)

Everything I tried

Change Verdict Evidence
Build the 3D RoPE tables once per forward kept 1,008 calls producing 17 distinct tables
Stop expanding K/V before the backend kept a +2.55% regression on its own; only pays once the mask is gone
Drop the all-zeros decode mask kept -5.66%, -8.19% together with the row above
Paged decode under a CUDA graph kept -29.93% / -31.27% on two prompts, same bytes, switch only, n=5
_repeated_blocks regional compile (your first suggestion) dropped launches 19,974 -> 19,974, zero triton kernels, hits recompile_limit (8) and falls back to eager permanently, +2.9%
Removing the forced sdpa_fallback dropped init dies: the other backends invert a boolean mask and SenseNova passes an additive float one, so the line is load-bearing
Removing the .item() sync dropped 0.2% of the idle
Bucketing plus a mask dropped 7.96-11.55 ms per step against 1.03 ms unmasked

think, main vs this branch (same prompt, same seed)

think_main_vs_branch

think output text does change: paged FA2 and SDPA are equally accurate against a float64 reference (mean error identical to four significant figures at n = 271/512/900/1500/3000), but they agree with each other only to 7.63e-06, and in a 382-step argmax loop that eventually flips a token. All four T2I cases are bit-identical between a312a94 and this branch.

Tests

tests/diffusion/models/sensenova_u1/ — 84 passed, 16 new. Verified by reverting: dropping the forward_und branch turns exactly one test red, weakening the flash_attn_varlen_func capability probe turns one red, and restoring the Python index in attend() turns one red. Bucket size does not affect results — pinning the schedule to 512, 2048 or 8192 produces the same png — and growth plus re-capture is covered end to end with a subdivided schedule that triggers two consecutive growths.

@hsliuustc0106

Copy link
Copy Markdown
Collaborator

The TP LoRA sharding, retained adapter state, and RMSNorm regression test are addressed at e96b5cba. Two issues remain from local re-verification:

  • pipeline_sensenova_u1.py:872: _generate_text is the T2T/I2T decode loop, but it calls _ar_step without the decode context that _generate_think creates. The recipe therefore claims paged/CUDA-graph acceleration for image-to-text even though that path never reaches it. Please wire the context into _generate_text and add a path-level regression test, or narrow the documentation.
  • test_sensenova_u1_attention_gqa.py:185: on one reserved L20X with PyTorch 2.13.0+cu129 and vLLM 0.27.0, the new CUDA suite reports 4 passed, 1 failed; at kv_len=512, the maskless GQA path is closer to float64 in only 7/8 fixed-seed trials. The current 8/8 assertion does not hold across this supported CUDA stack. Please use an explicit aggregate/no-regression tolerance (or document and enforce the intended hardware/kernel support) and rerun the accuracy gate.

I stopped the full-model A/B at that failed feasibility gate, so the reported switch-only paged-decode speedup remains unconfirmed locally.

_generate_text drives text-to-text and image-to-text, and it called _ar_step
without the decode context, so neither path ever reached the paged cache the
recipe documents for them. Build the context there as well. Unlike the think
loop there is no write-back, because the caller returns the decoded string and
never reads the cache again.

The maskless-GQA accuracy gate demanded that every one of eight fixed-seed
trials come out closer to the float64 reference, which a single tie breaks: on
an L20X with CUDA 12.9 it reported 7/8 at kv_len=512. Compare the aggregate
instead, which is the property the change actually claims, and print both means
and the per-trial count when it fails. Measured margins on an A800: 2.18% at
kv_len 271, 2.54% at 512, 5.72% at 2048.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
@hsliuustc0106 hsliuustc0106 added the high priority high priority issue, needs to be done asap label Aug 25, 2026
@hsliuustc0106

Copy link
Copy Markdown
Collaborator

Rechecked 9e5bf5b7 locally. Both issues from my previous comment are fixed.

Environment: NVIDIA L20X, PyTorch 2.13.0+cu129, vLLM 0.27.0. All runs used one excluded warmup followed by three measured requests.

Comparison Baseline Current Delta
Current head, graph off → on 13.900 ± 0.112 s 7.566 ± 0.131 s -45.57%
a312a949 → current, TP=1 36.835 ± 0.262 s 7.566 ± 0.131 s -79.46%
a312a949 → current, TP=2 55.171 ± 0.329 s 6.182 ± 0.293 s -88.79%

Timed NVML peak increased by 366 MiB (+1.01%) for TP=1 and 402 MiB/GPU (+2.02%) for TP=2. The real TP=2 distilled-LoRA path initialized and generated successfully on both ranks.

The updated suites also pass locally: 16 CPU tests and 5 CUDA tests. AR output diverges across implementations, so image hashes/SSIM are not parity metrics, but all compared images were visually sharp and prompt-faithful. This confirms the performance claims and keeps memory below the 5% regression gate.

paged_cache=self.cache,
).logits

def _capture(self, key):

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.

In a fresh-cache feasibility run, engine readiness completed before this path was exercised. The first graph-enabled think request took 76.9 s, with the first capture occurring about 69 s after request start; subsequent post-warmup requests were 7.566 ± 0.131 s.

Can we warm a representative AR decode/capture before reporting readiness, or document and measure this cold first-request penalty separately? The current dummy diffusion warmup does not appear to cover paged decode.

@MrlixiangWE MrlixiangWE Aug 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The tens of seconds is the language model's torch.compile, and where it lands depends on whether the startup warmup reaches that path. One session on an A800, inductor / triton / vLLM / CUDA JIT caches cleared for every arm, three requests each. "full" is diffusion_compile_granularity=full and "fixed" adds the warmup from 85acb30.

Warmup Compile Readiness First request Steady Above steady
active default 38.7 s 6.12 s 5.41 s 0.71 s
active full 68.8 s 13.13 s 5.35 s 7.77 s
active full, fixed 75.2 s 10.44 s 5.41 s 5.03 s
skipped full 35.2 s 45.67 s 5.48 s 40.19 s
skipped default 35.6 s 8.78 s 5.43 s 3.35 s

The fourth row is the shape you saw: readiness completes, then the first request pays tens of seconds. Your 76.9 s against a 7.566 s steady state is 69.3 s above steady, which matches the 69 s you measured to the first capture, and is 9.2x steady; that row is 40.19 s above steady, 7.3x. With the warmup active the same work is absorbed into readiness instead, so a run showing tens of seconds inside the request is one the warmup reached none of.

The default configuration compiles nothing at all: granularity is regional and regionally_compile returns early without _repeated_blocks, so the runner's "configured for lazy torch.compile" line is misleading.

85acb30 makes the dummy run warm the decode shape, which the text-to-image dummy request never touches: rows two and three, 7.77 s above steady before and 5.03 s after. It runs on both ranks under tensor parallelism.

A warmup cannot cover the capture half of your question: _decode_context builds the cache and the runner per request, so a startup capture is bound to buffers the first real request replaces — about 188 ms of every request rather than a one-off.

return ((length + BLOCK_SIZE - 1) // BLOCK_SIZE) * BLOCK_SIZE


class PagedDecodeCache:

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.

Can you document why this allocation remains model-local instead of using DiffusionKVCacheManager or ARDiffusionModelRunner?

PagedDecodeCache owns per-request tensors and an identity block table, so it bypasses scheduler admission, pooling, eviction, prefix reuse, and continuous-batching lifecycle. That is reasonable for the current max_num_seqs=1 path, but please state the limitation and link a follow-up/deletion condition before this is treated as general vLLM paged-KV support.

@MrlixiangWE MrlixiangWE Aug 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Documented in the module docstring at 9e0561c, with the deletion condition: once the loop is driven by a scheduler-visible runner, or the pipeline serves more than one sequence per forward, this file goes and the manager takes over.

@hsliuustc0106

Copy link
Copy Markdown
Collaborator
image

…rst request

PagedDecodeCache is model-local because DiffusionKVCacheManager reserves once
per scheduler request and ARDiffusionModelRunner lives under experimental, while
SenseNova runs its whole autoregressive loop inside one pipeline forward, where
the decode steps never surface to the scheduler at all. Say so in the module
docstring, together with the condition under which this file should be deleted
in favour of the manager, so it is not mistaken for general paged-KV support.

Also record what a cold first request costs. Measured on one A800 with cold
inductor and triton caches, the first request sits 1.43 s above steady state
with the paged path on and 1.67 s above it with the path off, so the graph
capture is not what dominates a cold start.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
…cache cleared

The earlier note cleared only the inductor and triton caches, which left vLLM's
own compile cache warm and made the number unrepresentative of a cold start.
With VLLM_CACHE_ROOT cleared as well, one A800 gives 1.43 s above steady state
with the paged path on and 1.28 s with it off, so the capture accounts for about
150 ms and the rest is what a first request costs anyway.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
…st capture

The previous note put the first-request delta down to the graph capture. It is
not: the capture is per request, twice for a think request that grows past the
512 bucket, at 150 ms for the first one in a process and about 94 ms after that,
so about 188 ms of every request including steady state. The 1.3 s a first
request costs above steady state appears with the paged path off as well.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
The engine warms the model with a dummy text-to-image request, and think is off
for it, so that request exercises the prefill shape and never the single-token
decode one. Whatever a deploy config compiles for decode therefore lands on the
first real request rather than on readiness.

Detect the dummy request the way the other pipelines do and drive a two-token
prefill followed by one decode step through the language model. Measured on an
A800 with the inductor, triton, vLLM and CUDA JIT caches cleared and
diffusion_compile_granularity=full, the first request drops from 7992.4 ms above
steady state to 4846.0 ms while readiness absorbs 5.7 s of it. With the default
granularity nothing compiles and the warmup costs a two-token forward.

The warmup is best effort: a failure logs and leaves startup alone.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
@hsliuustc0106

Copy link
Copy Markdown
Collaborator

Ci failed

@hsliuustc0106

Copy link
Copy Markdown
Collaborator

is there any CPU overhead for client e2e time? for the kernels, what's the optimization opportunity?

@MrlixiangWE

MrlixiangWE commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor Author

Ci failed

I checked the buildkite log. The error is [2026-08-30T23:07:03Z] nvidia-container-cli: initialization error: driver rpc error: timed out: unknown. It looks like the CI container failed to start rather than a problem with my code. Could you retry it?

@MrlixiangWE

MrlixiangWE commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor Author

is there any CPU overhead for client e2e time? for the kernels, what's the optimization opportunity?

All numbers below are re-measured, on 1x A800-80G with an Intel Xeon Gold 6336Y, TP=1, 1024x1024, seed 42, head e32770a.

CPU overhead

2.31% of the AR decode window and 3.97% of the diffusion window at this head. Before the graph it was 37.29%.

Three arms, one session, each differing from the previous by one thing. B is not a switch, only the arm that separates the cache from the graph.

Arm KV cache attention CUDA graph
A paged=0 exact length SDPA, flash backend no
B paged flash_attn_varlen no
C paged=1 paged flash_attn_varlen yes

Profiled: think on, 8 steps. Kernel time from the profiled run, wall clock from a clean one; idle is the difference.

Arm clean wall kernel time idle GPU busy launches
A 8259.5 ms 5179.2 ms 3080.3 ms 62.71% 206,149
B 8323.3 ms 5136.6 ms 3186.7 ms 61.71% 185,245
C 5196.9 ms 5076.9 ms 120.0 ms 97.69% 185,245

Kernel time is flat across the three arms, so the idle that disappears is host time and not device work. think_chars is 1233 in every run, and B and C issue the same launches.

Timing, think on, 50 steps, 2 warmups then n=10, P50:

Arm e2e think decode diffusion
A 17106.0 ms 6500.4 ms 10566.1 ms
B 17142.5 ms 6428.7 ms 10639.9 ms
C 14209.6 ms 3485.1 ms 10639.5 ms

On the decode loop A -> B is -1.10% and B -> C is -45.79%: the paged cache buys the static shape, the graph buys the time.

Outside the model, wall - stage is 2.8-3.3 ms, 0.02% of e2e.

Kernels

GEMM. Both windows are above 96% busy, so there is little left to take on the host side.

window GPU busy GEMM elementwise attention norm
AR decode, think on, 8 steps 97.69% 82.4% 6.4% 5.5% 5.4%
diffusion, think off, 50 steps 96.03% 78.9% 9.2% 6.8% 4.9%

ampere_bf16_s16816gemm_bf16_256x128_ldg8_f2f_stages_64x3_tn is 7754.9 ms over 16,926 launches, 77.7% of diffusion kernel time.

Thanks for the review on this PR.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
# A pipeline may hold captures of its own across requests. Level 2
# discards the memory those were recorded against, so they have to
# go with `graph_runners`.
release = getattr(model, "release_captured_graphs", None)

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.

The cleanup is necessary, but this establishes a second graph-lifecycle protocol in the infrastructure worker: it clears model_runner.graph_runners, then duck-types an individual pipeline for a hidden method. DiffusionModelRunner owns model compilation, caches, and execution resources, so please expose one model_runner.release_captured_graphs() method that clears its runners and delegates internally, or register the paged runner with its graph registry. Otherwise every future pipeline that retains captures must discover this unwritten hook, and an omission will silently survive level-2 sleep.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 519c9b2, with the single runner method.

hsliuustc0106 commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

Tested PR head 29c090d0c3c3ac609dff3ca50472b2beeff8c782 locally.

Environment

  • 1x NVIDIA H200 (139 GiB, compute capability 9.0); physical GPU 1 was reserved for the run.
  • Python 3.12.13; vLLM 0.28.0; torch 2.13.0+cu130; CUDA 13.0; diffusers 0.40.0; transformers 5.14.1; flashinfer 0.6.16.post3; BF16; TP=1.
  • Model: sensenova/SenseNova-U1.5-8B-MoT; LoRA: SenseNova-U1.5-8B-MoT-LoRA-8step.safetensors; seed 42; 1024x1024. CUDA runs were executed through gpu run --gpu-ids 1 with the PR worktree pinned in PYTHONPATH.

Focused tests

  • pytest -m "cpu and not cuda" tests/diffusion/models/sensenova_u1/ tests/diffusion/lora/test_loader.py -q: 71 passed, 41 deselected, 14 warnings, 5.95 s.
  • pytest -m "cpu and not cuda" tests/config/test_environment_variables.py tests/diffusion/test_diffusion_worker.py -q: 13 passed, 8 deselected, 14 warnings, 24.47 s.
  • pytest -m cuda tests/diffusion/models/sensenova_u1/test_sensenova_u1_attention_gqa.py tests/diffusion/models/sensenova_u1/test_sensenova_u1_paged_decode.py -q --tb=short: 9 passed, 23 deselected, 14 warnings, 102.13 s.

Real-model smoke

Case Result
Base, 50 steps, cfg 4.0, think=false image generated; 1024x1024 RGB; stage 9,017 ms; peak 34,384 MiB
PR, 8 steps, cfg 1.0, no LoRA control image generated; stage 624.00 ms; 77.85 ms/step; peak 34,376 MiB
PR, 8 steps, cfg 1.0, distilled LoRA image generated; LoRA fused into 168 parameters; stage 623.85 ms; 77.98 ms/step; peak 34,364 MiB
PR, 50 steps, cfg 4.0, think=true, VLLM_OMNI_SENSENOVA_PAGED_DECODE=1 image generated; stage 81,622 ms; peak 34,594 MiB

The same-seed 8-step LoRA output differs from the no-LoRA control in 1,048,574/1,048,576 pixels (MAE 58.62), so the adapter is not a no-op. All generated PNGs were nonblank and had shape (1024, 1024, 3).

Online smoke

The PR-source server reached /health; img2text returned a 1,921-character description for the generated image, and text2text returned the expected “Two plus two is four.” response. The server was shut down cleanly afterward.

The expected startup messages about the checkpoint lacking configuration_neo_chat.py appeared; generation was unaffected, consistent with the recipe note. This is a functional smoke, not a full multi-run image-quality benchmark.

@MrlixiangWE Could you add the H200 validation to recipes/SenseNova/SenseNova-U1.5.md, including the exact environment/commands, peak memory, and the 50-step and distilled-8-step timings? The recipe currently documents only the A800 measurements.

MrlixiangWE and others added 5 commits September 1, 2026 10:41
The worker cleared `model_runner.graph_runners` and then looked for a
`release_captured_graphs` method on the pipeline, so a pipeline that keeps
captures across requests had to be found through an unwritten hook, and an
omission would have survived level-2 sleep silently. `DiffusionModelRunner`
owns compilation and execution resources, so it owns the release: one
`release_captured_graphs()` clears its own runners and delegates to the
pipeline, and the worker asks it once.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
Environment, commands, peak memory and the 50-step and 8-step timings measured
on 1x NVIDIA H200. The recipe documented only the A800 numbers before.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
The tolerance case only called the method, so it would have passed against an
implementation that created `graph_runners` on the way past.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
The A800 block listed only the OS and the vLLM version, so its numbers could not
be placed against the H200 ones. Both blocks now carry the same fields, and the
software stack turns out to be identical.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
The table listed the cases but not how to reproduce them.

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
@MrlixiangWE

Copy link
Copy Markdown
Contributor Author

Could you add the H200 validation to recipes/SenseNova/SenseNova-U1.5.md, including the exact environment/commands, peak memory, and the 50-step and distilled-8-step timings?

Added in be4c6b8 and dd52292.

@hsliuustc0106 hsliuustc0106 added ready label to trigger buildkite CI cuda-test Used to trigger vllm-omni cuda CI separately. and removed ready label to trigger buildkite CI cuda-test Used to trigger vllm-omni cuda CI separately. labels Sep 1, 2026
@hsliuustc0106
hsliuustc0106 merged commit 0288c3f into vllm-project:main Sep 1, 2026
6 checks passed
princepride added a commit to princepride/vllm-omni that referenced this pull request Sep 2, 2026
…ntory

The VSA backend's native Blackwell opt-in reads FASTVIDEO_VSA_SM100A, but
the name was never classified, so the inventory drift check failed:

    FAILED tests/config/test_environment_variables.py::
    test_statically_resolvable_environment_accesses_are_classified
    AssertionError: assert {'FASTVIDEO_VSA_SM100A'} == set()

Classify it as model-specific with a "promote" disposition, matching the
other per-model kernel toggles (MING_CFM_CUDAGRAPH, OMNIVOICE_CUDA_GRAPH,
VLLM_OMNI_SENSENOVA_PAGED_DECODE): it selects a kernel route for the
MiniMax-H3 FastH3 path and belongs in typed diffusion attention config
rather than an environment switch.

Bump the reviewed snapshot counts accordingly. The reference page's
model-specific counts were already one behind main after vllm-project#6516, so refresh
them to the real values (58 model-specific, 35 promote) instead of leaving
a second inconsistency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: princepride <wangzhipeng628@gmail.com>
JoseCarlosGarcia95 pushed a commit to valendra-tech/vllm-omni that referenced this pull request Sep 5, 2026
…llm-project#6516)

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
ZhengWG pushed a commit to ZhengWG/vllm-omni that referenced this pull request Sep 8, 2026
…llm-project#6516)

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
Signed-off-by: ZhengWG <zwg0606@gmail.com>
86MaxCao added a commit to 86MaxCao/vllm-omni that referenced this pull request Sep 8, 2026
Resolve conflict in pipeline_sensenova_u1.py by combining
SupportsStepExecution with new LoraLoaderMixin from vllm-project#6516.

Signed-off-by: MaxCao <86maxcao@gmail.com>
ljy19911228 added a commit to ljy19911228/vllm-omni that referenced this pull request Sep 23, 2026
Re-apply only the optimizations that measured a real win on the v0.24.0
work; experiments that showed no gain are not carried over (dual-stream
CFG Plan A, row-chunk pipeline, batched CFG), and the MC2 MatmulAllReduce
platform registration is dropped because the vllm-ascend snapshot in the
v0.28.0 container removed the whole feature.

Kept:
- Phase 1: gate the CUDA-only Triton qk-norm+rope kernel to non-NPU
  platforms (pristine v0.28.0 verified to segfault on NPU model load)
- Phase 2: npu_fused_rmsnorm_rope.py - npu_rms_norm x4 (fp32) +
  npu_rotary_mul x3 replacing ~34 unfused kernels
- Phase 3: forward_fa_npu bool-mask conversion (True=attend) + explicit
  softmax scale for the mindiesd fused attention call
- SDPA fallback restricted to non-NPU platforms
- Fix A/B/C host-sync removal: exist_und/exist_gen kwargs, timesteps_py
  python mirror, grid_pairs python ints
- FIA attention via npu_fused_infer_attention_score (opt-out with
  VLLM_OMNI_SENSENOVA_FIA=0), kept on top of the build-once
  position_embeddings threading from vllm-project#6516 (which supersedes the earlier
  id()-keyed RoPE cache approach)

Verified E2E in quay.io/ascend/vllm-omni:v0.28.0 (CANN 9.1.0),
cards 0/1 only, 2048x2048 / 50 steps / cfg 7.0 / seed 42, clean-protocol
run (proc+port verified before each round, APIServer pid re-checked after):

  TP2 FIA=0     50.33s  md5 72e1638c  (= v0.24 baseline, bit-exact)
  TP2 FIA=on    49.00s  md5 9beb7c94  (= v0.24 FIA, bit-exact, -2.7%)
  TP1           54.00s  md5 03d629b8
  TP1+cfg-par2  30.13s  md5 03d629b8  (cfg-parallel, -40% vs TP2)

Per-config md5s match the v0.24.0 stack exactly, i.e. the port is
output-identical across versions. NPU sampling during the FIA=on run:
cards 0/1 average ~69% AICore / ~225W across the whole 49s window.
No FIA fallback / rope-fallback / segfault lines in any serve log.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ljy19911228 <ljy19911228@163.com>
khairulkabir1661 pushed a commit to khairulkabir1661/vllm-omni that referenced this pull request Sep 25, 2026
…llm-project#6516)

Signed-off-by: MrlixiangWE <mrdanaer@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
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. diffusion codes related to diffusion models high priority high priority issue, needs to be done asap new model add new model ready label to trigger buildkite CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[New Model]: SenseNova-U1.5-8B-MoT

4 participants