Skip to content

[Model] Support official SenseNova-U1 LoRA checkpoints - #5642

Open
EchoHayate wants to merge 7 commits into
vllm-project:mainfrom
EchoHayate:feat/sensenova-official-lora
Open

EchoHayate wants to merge 7 commits into
vllm-project:mainfrom
EchoHayate:feat/sensenova-official-lora

Conversation

@EchoHayate

@EchoHayate EchoHayate commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Closes #4705.

The released sensenova/SenseNova-U1-8B-MoT-LoRAs checkpoints use a
single-file layout with lora_down, lora_up, and per-layer alpha tensors,
while vLLM-Omni's diffusion LoRA runtime expects PEFT format. They also include
adapters for fm_modules.fm_head, which used native nn.Linear layers and
therefore was not visible to the existing vLLM LoRA wrappers.

This PR:

  • adds tools/sensenova_u1/convert_lora_to_peft.py to convert an official
    checkpoint into adapter_model.safetensors and adapter_config.json;
  • rejects malformed pairs, inconsistent rank/alpha, and zero-rank adapters
    before writing output;
  • changes the two non-pixel fm_head projections to replicated vLLM linear
    layers without changing their checkpoint parameter names;
  • exposes fm_modules to DiffusionLoRAManager, enabling request-scoped
    activation and deactivation of the official fm_head adapters;
  • documents the conversion workflow and adds converter, base-weight loading,
    and real manager round-trip tests.

The implementation is based on current main and preserves the typed
SenseNova configuration and request-local t_eps ownership introduced by
#5877.

Test Plan

python -m pytest -q \
  tests/tools/test_convert_sensenova_u1_lora.py \
  tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py \
  tests/diffusion/lora/test_lora_manager.py

pre-commit run --files \
  docs/user_guide/diffusion/lora.md \
  tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py \
  tests/tools/test_convert_sensenova_u1_lora.py \
  tools/sensenova_u1/convert_lora_to_peft.py \
  vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py

Current-head validation

Current base: b78c31eda6c75546be4d54c265d483977104a487

Current PR head: c326f201bbd79747804bcb8436eac3268f0b0002

  • Refreshed onto current main; the only textual conflict was in
    docs/user_guide/diffusion/lora.md. The resolution preserves both this PR's
    SenseNova conversion section and upstream's MiniMax-H3 adapter-schedule
    documentation.
  • The net PR diff remains limited to the same five SenseNova LoRA files.
  • Converter tests in an isolated Python 3.12 environment:
    13 passed.
  • Changed-file pre-commit: all applicable hooks passed, including Ruff,
    mypy, pytest marker policy, Markdown, SPDX, and forbidden-import checks.
  • Python 3.12 byte compilation and git diff --check: passed.
  • The full model/manager CPU suite and A100 E2E have not yet been rerun at
    c326f201; GitHub base CI is running.

Previously validated behavior

At head afc530532c783f17697c23775ef1004b4dd9b138:

  • converter + SenseNova runtime + generic LoRA manager:
    34 passed, 15 warnings;
  • all existing fm_head.{0,2}.{weight,bias} checkpoint names loaded through
    SenseNovaU1Pipeline.load_weights();
  • the real DiffusionLoRAManager wrapped and loaded both fm_head.0 and
    fm_head.2; activation changed output and deactivation restored the exact
    baseline.

These results are retained as historical functional evidence, not as
current-head verification.

Historical official-adapter validation

  • Source:
    SenseNova-U1-8B-MoT-LoRA-8step-V1.0.safetensors
  • Source size: 1,637,476,232 bytes
  • Source SHA-256:
    3fc8c94330ee16c210b294189ebf3b88f41693f2ddfc5db21239c8cc270024e0
  • Converted adapter SHA-256:
    c8aecd009cfa1696c18fbe495a62a77161d56922791ec10884f7f96638a8a307
  • Converted tensors: 592
  • Target modules: 296
  • Rank / alpha: 128 / 128
  • Includes fm_modules.fm_head.0 and fm_modules.fm_head.2

Historical full image-generation E2E (A100 80GB)

The E2E was produced at the previous exact base/head pair:

  • exact base:
    73b623f2f7db092053c1c86fe796bed89eb3dc71
  • exact head:
    afc530532c783f17697c23775ef1004b4dd9b138
  • model: sensenova/SenseNova-U1-8B-MoT
  • model revision:
    bfa9b436503cb8aed4f2bc60e3236710cc77468d
  • prompt: a photo of a cat sitting on a laptop keyboard
  • sampling: seed 42, 512x512, 8 steps, batch size 1, CFG scale 4.0
Comparison Result
Exact base baseline vs exact head baseline byte-identical, pixel MAE 0.0
Head baseline vs official LoRA different, pixel MAE 97.256365, max abs diff 255
Head baseline vs post-deactivation request byte-identical, pixel MAE 0.0

All four outputs were finite, non-constant RGB 512x512 images. The head run
reused one production Omni engine for baseline, official-LoRA activation,
and deactivation.

No compatibility overlay or source patch was applied to either checkout.
This is correctness validation, not a throughput claim. The first LoRA request
includes adapter loading, and the base/head initialization timings were
affected by cold versus warm filesystem cache.

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

@hsliuustc0106 hsliuustc0106 added new model add new model diffusion codes related to diffusion models labels Aug 2, 2026
@EchoHayate

Copy link
Copy Markdown
Contributor Author

Full SenseNova-U1 + official LoRA E2E validation

I completed the previously missing full image-generation validation on an NVIDIA A100 80GB using the production Omni path (not a converter-only or mocked harness).

Fixed inputs

  • Base commit: 0a51a18570d8df8e65e9d4f9be688ce686c67ce6
  • PR head: 62690e2b9e1d4d943601aa430e5648d753f18a7d
  • Base model: sensenova/SenseNova-U1-8B-MoT
  • Model revision: bfa9b436503cb8aed4f2bc60e3236710cc77468d
  • Model validation: 16/16 files checked, 35,110,117,492 bytes total; all eight safetensors shards matched their Hub SHA-256 metadata
  • Official source LoRA: SenseNova-U1-8B-MoT-LoRA-8step-V1.0.safetensors
    • 1,637,476,232 bytes
    • SHA-256 3fc8c94330ee16c210b294189ebf3b88f41693f2ddfc5db21239c8cc270024e0
  • Converted PEFT adapter:
    • 1,637,446,080 bytes
    • SHA-256 c8aecd009cfa1696c18fbe495a62a77161d56922791ec10884f7f96638a8a307
    • rank/alpha 128/128, 296 target modules
  • Prompt: a photo of a cat sitting on a laptop keyboard
  • Sampling: seed 42, 512x512, 8 steps, batch size 1, CFG scale 4.0

Correctness results

Comparison Result
Base commit baseline vs PR-head baseline byte-identical, pixel MAE 0.0
PR-head baseline vs official LoRA different, pixel MAE 97.256365, max abs diff 255
PR-head baseline vs request after LoRA deactivation byte-identical, pixel MAE 0.0

All generated PNGs were finite, non-constant RGB images. This verifies the complete sequence through the real diffusion engine: initialize SenseNova-U1, generate without an adapter, load and activate all 296 official adapter modules for one request, then deactivate the adapter and exactly restore baseline behavior.

Timing and memory

Run Init Request wall time Peak GPU memory
Base baseline 26.141 s 1.591 s 47,383 MiB
PR-head baseline 33.179 s 1.901 s 48,041 MiB
PR-head first official-LoRA request same engine 22.619 s included above
PR-head request after deactivation same engine 1.329 s included above

The first LoRA request includes adapter loading/conversion work and is not presented as steady-state throughput.

Compatibility boundary

The validation image uses vLLM 0.26, which requires _DiffusionVllmModelConfig.head_dtype. The one-property compatibility fix from #5766 (fbee7d3ac437387e957b33914342d152186ba24b, head_dtype -> dtype) was applied identically to both exact base and PR-head source snapshots. No #5642-specific code was changed in the base snapshot. Therefore, the A/B result isolates this PR while explicitly depending on that independent compatibility fix.

EchoHayate and others added 5 commits August 21, 2026 12:49
Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
@EchoHayate
EchoHayate force-pushed the feat/sensenova-official-lora branch from 62690e2 to afc5305 Compare August 21, 2026 08:15
@EchoHayate
EchoHayate requested a review from NickCao as a code owner August 21, 2026 08:15
@EchoHayate

Copy link
Copy Markdown
Contributor Author

Updated this PR onto current main and force-updated the existing branch to
afc530532c783f17697c23775ef1004b4dd9b138.

Key changes since the previous head:

Fresh verification:

  • converter + SenseNova runtime + generic manager:
    34 passed, 15 warnings;
  • changed-file pre-commit: all applicable hooks passed;
  • exact A100 80GB E2E, base 73b623f2 vs head afc53053:
    • main baseline == head baseline byte-for-byte;
    • official LoRA changes the image (pixel MAE 97.256365);
    • deactivation restores the baseline byte-for-byte.

Both E2E source trees were clean Git checkouts, imported directly from their
mounted source roots, and used the pinned model revision
bfa9b436503cb8aed4f2bc60e3236710cc77468d. No compatibility overlay or source
patch was applied. The PR body now contains the full current-main validation
contract and boundaries.

Fresh base CI is running. Review is welcome once it is green.

…a-official-lora

Co-authored-by: TRAE CLI <traecli@bytedance.com>

# Conflicts:
#	docs/user_guide/diffusion/lora.md
@EchoHayate

Copy link
Copy Markdown
Contributor Author

Current-head refresh / author self-review

  • Refreshed onto main@b78c31ed; current head is c326f201. The only textual conflict was docs/user_guide/diffusion/lora.md, resolved by preserving both this PR's SenseNova conversion instructions and upstream's MiniMax-H3 adapter-schedule section.
  • Rechecked the net diff: it remains limited to five SenseNova LoRA files and still closes one loop—official checkpoint conversion, fm_modules visibility to DiffusionLoRAManager, request-scoped activation/deactivation, and documentation.
  • Base checkpoint names remain unchanged; malformed pairs, inconsistent rank/alpha, zero-rank tensors, unknown tensors, and unsafe overwrite remain fail-closed.
  • Fresh current-head local evidence: converter suite 13 passed; changed-file pre-commit, Ruff check/format, Python 3.12 byte compilation, and git diff --check passed.
  • The prior 34 passed model/manager suite and A100 image-generation A/B were produced at afc53053 and are now explicitly labeled historical in the PR body. They are not presented as current-head GPU evidence. GitHub base CI is running for c326f201.

@xuechendi @SamitHuang, this refreshed PR is ready for review once the current checks finish.

Current head reviewed: c326f201bbd79747804bcb8436eac3268f0b0002.



def test_sensenova_fm_head_preserves_checkpoint_weight_names():
pipeline = torch.nn.Module()

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.

Build this stub as SenseNovaU1Pipeline so stacked_params_mapping is available.

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 9419599. The weight-name test now allocates a SenseNovaU1Pipeline without loading a checkpoint, initializes its nn.Module state, and calls the bound production load_weights method. It inherits stacked_params_mapping instead of using a plain Module. The original test reproduced the AttributeError before the change; all four tests in this file now pass, with the existing loaded-name and tensor-equality assertions unchanged.

world_size=1,
rank=0,
local_rank=0,
distributed_init_method="env://",

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.

Use backend="gloo" so these CPU tests do not require CUDA.

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 9419599 by explicitly passing backend="gloo" to init_distributed_environment. Model-parallel initialization inherits the initialized world-group backend. The CPU suite passes locally (4 tests); the combined converter, SenseNova LoRA, distilled-LoRA, and manager regression set passes 44 tests with 15 warnings. No production backend default was changed.

Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
@EchoHayate

Copy link
Copy Markdown
Contributor Author

Review follow-up / author self-review

Current head reviewed: 9419599d6560dbb914cc28efa367cc599b56a77b.

  • This update changes only tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py: an explicit Gloo backend for the CPU fixture, and a real SenseNovaU1Pipeline stub for the checkpoint-weight-name test. No production code, loading semantics, assertions, or tolerances changed.
  • Reproduced the original missing-stacked_params_mapping failure before the fix. The corrected test calls the bound production loader and retains exact loaded-key and tensor-equality checks.
  • Focused file: 4 passed. Combined converter, SenseNova LoRA, distilled-LoRA and generic manager tests: 44 passed, 15 warnings.
  • Changed-file pre-commit passed, including mypy, test marks and SPDX; Ruff check/format and git diff --check passed.
  • Tests ran on local macOS CPU with Python 3.12.13, PyTorch 2.11.0 and vLLM source 98dff2a81d747d1dba01a47f939f48c3526d4206. These are current-head CPU results, not new GPU or full-checkpoint image-generation evidence. The older A100 results remain historical.

Reproduction command, with the PR checkout and the above vLLM source on PYTHONPATH, and HF_HUB_OFFLINE=1:

python -m pytest \
  tests/tools/test_convert_sensenova_u1_lora.py \
  tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py \
  tests/diffusion/models/sensenova_u1/test_sensenova_u1_distill_lora.py \
  tests/diffusion/lora/test_lora_manager.py \
  -o addopts='' -q

@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: no human activity for 7 days

@EchoHayate this pull request has had no human commit, comment or review since 2026-09-16. Please confirm the current plan and next step. The author or a maintainer decides whether to change the PR state.

To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline.

@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: no human activity for 14 days

@EchoHayate this pull request has had no human commit, comment or review since 2026-09-16. Please consider marking this PR as draft until work can resume. The author or a maintainer decides whether to change the PR state.

To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline.

@EchoHayate

Copy link
Copy Markdown
Contributor Author

September 30 lifecycle follow-up

Rechecked against main 4af28f33bd9c07e7e4fc862ac6ccb72834512995: this PR
still addresses a distinct request-scoped LoRA gap. SenseNova exposes only
language_model to the LoRA manager, and fm_head still uses native
nn.Linear. The initialization-time fusion introduced by #6516/#6948 does
not provide adapter deactivation or baseline restoration.

Both September 16 inline requests were addressed in 9419599d and answered
in their original threads.

Remaining author work is to refresh the patch and PR description against
current main; revalidate conversion, complete adapter binding, unchanged
base-weight loading, and compatibility with distilled loading; and repeat the
pinned-checkpoint baseline → adapter → deactivation check before claiming
current-main E2E support.

No new tests or GPU runs were performed for this lifecycle check. Previously
reported results remain tied to their recorded revisions; this is not a
merge-readiness claim.

@vllm-omni-review-bot

Copy link
Copy Markdown
Omni ReviewBot routing record

Assigned Strict on zcode (GLM-5.3-Flash) under experiment fleet-strict-cursor-grok46-zcode-glm53flash-5050-c5-z10-20261002.

@vllm-omni-review-bot vllm-omni-review-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Omni ReviewBot review

6 actionable finding(s).

CI at 9419599d6560 (2026-10-09T07:25:06.379165+00:00): required check(s) blocking: buildkite/vllm-omni (missing).

Full review analysis

Scan:

Category Result
Tests / verification 5 finding(s) below
Security no finding reported
Docs / comments 1 finding(s) below
Behavior / compatibility no finding reported
Correctness no finding reported

Validated:

  • [resolved] concern 'does the new ReplicatedLinear fm_head still load unchanged checkpoint names?': load_weights (pipeline_sensenova_u1.py:1705-1716) routes non-stacked names via weight_loader, and test_sensenova_fm_head_preserves_checkpoint_weight_names (tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py:77-93) drives the bound production loader with fm_modules.fm_head.{0,2}.{weight,bias} asserting exact loaded-key set plus tensor equality; residual: CPU/fp32 only — the bf16 CUDA load path is covered solely by the historical A100 E2E (kept major comment).
  • [resolved] both Sep-16 inline review requests landed at head 9419599: tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py:44 passes backend="gloo" and :78-81 builds a real SenseNovaU1Pipeline stub calling the bound load_weights; residual checked — the stub still skips init (tokenizer/weights_sources unset), which is safe because load_weights reads only stacked_params_mapping and named_parameters
  • [claim-verified] 'preserves ... request-local t_eps ownership introduced by #5877' — _parse_request reads t_eps per request from extra_args (pipeline_sensenova_u1.py:1039) and threads it through _t2i_predict_v; no global state introduced
  • [claim-verified] 'rejects malformed pairs, inconsistent rank/alpha, and zero-rank adapters before writing output' — all validation branches are reachable and each has a dedicated failing-path test in tests/tools/test_convert_sensenova_u1_lora.py (12 functions, 13 cases with parametrization, matching the body's '13 passed')
  • [claim-verified] PR body claim 'net PR diff remains limited to the same five SenseNova LoRA files' — the diff touches exactly docs/user_guide/diffusion/lora.md, tools/sensenova_u1/convert_lora_to_peft.py, vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py, and the two new test files
  • [validated] pipeline_sensenova_u1.py:520-584 — with use_pixel_head=True fm_head is still a native ConvDecoder under fm_modules, but DiffusionLoRAManager._replace_layers_with_lora skips modules not matching the adapter targets (manager.py:432-445) and registers only actually-replaced wrappers (manager.py:458), so native TimestepEmbedder/ConvDecoder modules cannot crash the scan; residual: an official fm_head adapter on a pixel-head checkpoint silently binds only language_model (pipeline defines no _validate_diffusion_lora_binding) — identical silent behavior pre-PR, not a regression.

6 actionable finding(s).

Checked, no defect found:

  • tools/sensenova_u1/convert_lora_to_peft.py:28 — Checked and correct as written — no change requested. Second-round verification of the uncovered converter hunk: the isinstance union and PEP-604 annotations require Python 3.10+ and pyproject.toml:14 declares requires-python = ">=3.10,<3.14", so the floor admits them; all validation (missing pairs, non-matrix, zero rank, rank mismatch, inconsistent rank/alpha, unknown tensors) and the FileExistsError guard run before output_dir.mkdir/save_file, so the converter fails closed.

Verdict: REQUEST CHANGES

Findings

  • [P1] The production change — fm_head's two projections become ReplicatedLinear scann… — vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py
Evidence for The production change — fm_head's two projections become ReplicatedLinear scann…

The production change — fm_head's two projections become ReplicatedLinear scanned by the LoRA manager (pipeline_sensenova_u1.py:572 fm_head = _build_fm_head( via _build_fm_head at :128; _lora_components gains fm_modules at :523) — sits on the mandatory path of every non-pixel-head request: load_weights routes fm_modules.fm_head.* params through the vLLM weight_loader (:1709-1711) instead of the plain param.data.copy_ fallback (:1716), and every denoise step computes x_pred through self.fm_modules["fm_head"] (:749). Evidence is CPU-only: both new test files are pytest.mark.cpu, the PR body states the full model/manager CPU suite and A100 E2E were not rerun at c326f20, head has since moved to 9419599, and the official 1.6 GB-adapter A100 E2E is historical (base 73b623f / head afc5305). L2 CPU coverage proves plumbing, not the CUDA path (fp16/bf16 ReplicatedLinear + LoRA wrap inside the denoise loop). After the rebase, rerun the Test Plan's three pytest suites at the final head and state explicitly whether the official-adapter E2E evidence is being refreshed or stays historical.

Evidence: Tree-verified trigger/effect: this diff swaps nn.Linear for ReplicatedLinear in the released checkpoint's fm_head and makes the LoRA manager scan it, so every non-pixel-head request now runs the changed modules on CUDA with only CPU-marked tests as coverage. Decisive lines — pipeline_sensenova_u1.py:572 fm_head = _build_fm_head( (inside else of :564 if self.model_cfg.use_pixel_head:); pipeline_sensenova_u1.py:523 _lora_components: ClassVar[list[str]] = ["language_model", "fm_modules"] (changed by this diff); pipeline_sensenova_u1.py:749 x_pred = self.fm_modules["fm_head"](last_hidden_state[:, -image_token_num:].view(B, L, -1)).view(B, L, -1) (per denoise step); pipeline_sensenova_u1.py:1709-1711 weight_loader = getattr(param, "weight_loader", None) / weight_loader(param, loaded_weight) in load_weights (weight routing changed for these params); tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py:22 pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu] (CPU-only; unchanged-by-diff note: the run-status claim 'not rerun at c326f20' is from PR-body metadata quoted by the prior fetch step, not from this tree). Unmet requirement: repo hard gate that L2 CPU coverage cannot stand in for real device/precision evidence and modified tests must be run at the last-touched state.

- **[P2] The diff swaps fm_head's two nn.Linear projections for vLLM ReplicatedLinear la…** — `tests/diffusion/models/sensenova_u1/test_sensenova_u1_distill_lora.py`
Evidence for The diff swaps fm_head's two nn.Linear projections for vLLM ReplicatedLinear la…

The diff swaps fm_head's two nn.Linear projections for vLLM ReplicatedLinear layers (pipeline_sensenova_u1.py:572, built by _build_fm_head), so on the live distill path an fm_modules.fm_head.* delta now routes through the weight_loader branch of _add_lora_deltas (pipeline_sensenova_u1.py:1658-1674) instead of the direct-add branch it took as plain nn.Linear — and the distill backend dispatches this in production (diffusion_worker.py:572 calls pipeline.load_lora_weights under LoRABackend.DISTILL). Yet all eight tests in tests/diffusion/models/sensenova_u1/test_sensenova_u1_distill_lora.py stub only language_model (_prefix() = 'language_model.model.layers.{i}'; _Stub/_ShardedStub build no fm_modules), leaving the changed fm_head fusion path with zero coverage anywhere — the new test_sensenova_u1_lora.py covers fm_head only via the PEFT DiffusionLoRAManager and load_weights, never via load_lora_weights. Add one fm_head entry there (stub fm_modules with the real _build_fm_head, feed an fm_modules.fm_head.0 triple, assert the delta lands in the ReplicatedLinear weight) so the class swap is pinned on the distill path.

Evidence: In-diff: pipeline_sensenova_u1.py:572 fm_head = _build_fm_head( — the diff replaced nn.Linear(llm_hidden_size, fm_intermediate_dim, bias=True) with ReplicatedLinear layers (return_bias=False, disable_tp=True), changing the class of fm_head params. Routing: pipeline_sensenova_u1.py:1642 for name, param in self.named_parameters(): (no component filter — fm_head in scope on the distill path) and :1658 weight_loader = getattr(param, "weight_loader", None) with the loader branch at 1663-1674 (param.data = buffer ... weight_loader(param, delta)), which weight_loader-carrying vLLM layer params take while plain nn.Linear params took the direct-add branch at 1660-1662. Production trigger: diffusion_worker.py:572 pipeline.load_lora_weights(lora_path) under :567 elif lora_backend == LoRABackend.DISTILL: — unchanged by this diff, present in the PR-time tree. Unmet requirement: tests/diffusion/models/sensenova_u1/test_sensenova_u1_distill_lora.py:57-58 def _prefix(i: int = 0) -> str: / return f"language_model.model.layers.{i}" — all eight test functions (lines 61, 79, 95, 104, 113, 120, 159, 182) use only these language_model keys and _Stub.init (lines 35-39) builds only self.language_model, so the changed fm_head fusion path has no coverage (unchanged file; the diff's own new tests cover fm_head only through the PEFT manager path). weight_loader premise corroborated in-repo, unchanged by this diff: test_sensenova_u1_distill_lora.py:138 self.weight.weight_loader = self._weight_loader with docstring 'which is what vLLM does' (lines 127-132), and test_fused_delta_is_sharded_through_the_weight_loader proves such params route through the loader branch.

- **[P2] The module docstring (lines 13-14) still says 'Vision model (NEOVisionModel) an…** — `vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py`
Evidence for The module docstring (lines 13-14) still says 'Vision model (NEOVisionModel) an…

The module docstring (lines 13-14) still says 'Vision model (NEOVisionModel) and FM modules kept as standard nn.Module since they are lightweight (no transformer blocks)', but this diff replaces fm_head's two nn.Linear projections with vLLM ReplicatedLinear layers built by _build_fm_head (called at pipeline_sensenova_u1.py:572; layers constructed at 128-152) precisely so DiffusionLoRAManager can wrap fm_modules. Update the sentence, e.g. 'Vision model (NEOVisionModel) kept as standard nn.Module; the FM head uses vLLM ReplicatedLinear layers so the diffusion LoRA manager can wrap it.', so the docstring no longer tells readers fm_head is off-limits to vLLM layer machinery.

Evidence: Trigger: on this PR head, every SenseNovaU1Pipeline without a pixel head builds fm_head from vLLM layers — vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py:572 fm_head = _build_fm_head( feeding :582 "fm_head": fm_head, inside self.fm_modules, where diff-added :134-135 defines return nn.Sequential( ReplicatedLinear( (two ReplicatedLinear layers, lines 128-152, added by this diff along with the import at :34 from vllm.model_executor.layers.linear import ReplicatedLinear). Adverse effect: the unchanged, pre-existing module docstring contradicts the changed code — :13-14 - Vision model (NEOVisionModel) and FM modules kept as standard nn.Module / since they are lightweight (no transformer blocks). — so readers are told FM modules stay on plain torch layers, while the diff's stated purpose (test test_sensenova_lora_manager_scans_fm_modules asserting _lora_components == ["language_model", "fm_modules"]) is to let the LoRA manager wrap the new ReplicatedLinear layers. Lines 34 and 128-152 are diff-added; lines 13-14 are in the PR-time tree unchanged by this diff, made stale by it.

Suggestion: - Vision model (NEOVisionModel) kept as standard nn.Module; the FM head uses
vLLM ReplicatedLinear layers so the diffusion LoRA manager can wrap it.

- **[P2] The new round-trip test (tests/diffusion/models/sensenova_u1/test_sensenova_u1_…** — `tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py`
Evidence for The new round-trip test (tests/diffusion/models/sensenova_u1/test_sensenova_u1_…

The new round-trip test (tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py:111-144) is this PR's core semantic evidence for the LoRA path, but it proves only wrap/activate/restore, not value math: _write_official_fm_head_lora (lines 97-108) writes all-ones lora_down/lora_up with alpha == rank, so the PEFT scaling alpha/r is exactly 1.0 and a loader that dropped or misapplied the scaling factor would still pass the only value assertion, assert not torch.equal(adapted, baseline) (line 140). Scaling is real in this load path (loader.py:200 folds alpha.item() / rank into lora_B; manager.py:343 depends on 'internal scaling'), so it deserves coverage. Since the base weights are zeroed and inputs are ones, the adapted output is exactly computable: pin the expected tensor computed in the test from the fixture weights, or use rank=2 with alpha=4 so a dropped or inverted scaling flips the assertion.

Evidence: tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py:97 def _write_official_fm_head_lora(source_path: Path, rank: int = 2) -> None: with :102 "fm_modules.fm_head.0.alpha": torch.tensor(rank, dtype=torch.int32), and :105 the same for fm_head.2 — alpha/r == 1.0 exactly, so any scaling behavior (dropped, inverted, squared) is a numerical fixed point; :122-123 torch.nn.init.zeros_(parameter) zeroes all base weights and :140 assert not torch.equal(adapted, baseline) is the only value assertion on the adapted output, hence blind to scaling. Scaling is a live dimension of this path — unchanged by this diff, present in the PR-time tree: vllm_omni/diffusion/lora/loader.py:200 scaled_state_dict[lora_b_key] = state_dict[lora_b_key] * (alpha.item() / rank) (diffusers format) and vllm_omni/diffusion/lora/manager.py:343 lora.optimize() # ref: _create_merged_loras_inplace, internal scaling (PEFT path the test exercises). Trigger on this head: any regression that misapplies alpha/r in the LoRA load path ships green through this PR's only semantic test; unmet requirement: the PR's core semantic evidence does not exercise its own scaling semantics.


🤖 This review was generated by InferMatrix Copilot, an open-source repo-maintenance agent for PR review, CI debugging and issue triage. Try it on your own repo, and ⭐ star it if it helped!

- `lora_alpha`: LoRA alpha scaling factor
- `target_modules`: List of module names to apply LoRA to

### SenseNova-U1 Official LoRAs

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P0] The merge gate reports this branch as DIRTY/CONFLICTING — it both conflicts wit…

Evidence and suggested fix

The merge gate reports this branch as DIRTY/CONFLICTING — it both conflicts with and trails the base, so the PR cannot merge as-is (gate: 'files may have moved/renamed on main'). The PR body pins base b78c31e while main has since moved (4af28f3+), so every head-relative validation number in the body predates the required rebase. Rebase onto current main and re-resolve the repeat conflict in docs/user_guide/diffusion/lora.md preserving BOTH blocks: this PR's '### SenseNova-U1 Official LoRAs' (line 30) and upstream's '## MiniMax-H3 adapter-declared schedules' (line 266) — the working tree at head 9419599 already contains both. Confirm the five-file scope survived (docs/user_guide/diffusion/lora.md, tools/sensenova_u1/convert_lora_to_peft.py, tests/tools/test_convert_sensenova_u1_lora.py, tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py, vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py), force-update the branch, and let base CI go green on the refreshed head before merge.

Evidence: Tree-checked (head 9419599): docs/user_guide/diffusion/lora.md:30 ### SenseNova-U1 Official LoRAs — the cited anchor exists at the exact cited line; docs/user_guide/diffusion/lora.md:266 ## MiniMax-H3 adapter-declared schedules — upstream section present in the same working tree, unchanged by this diff, so the rebase must preserve both blocks; all four remaining diff files confirmed present (tools/sensenova_u1/convert_lora_to_peft.py, tests/tools/test_convert_sensenova_u1_lora.py, tests/diffusion/models/sensenova_u1/test_sensenova_u1_lora.py, vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py). Decisive merge-state fact lies OUTSIDE the diff and outside file reads — git-level repo state from this pipeline's prior gate step, quoted in gate_report: 'MERGE STATE: DIRTY / CONFLICTING — the branch conflicts with or trails the base; files may have moved/renamed on main. Flag this as a blocking issue.'; current effect: PR #5642 cannot merge until rebased, and head-relative numbers in the PR body (pinned base b78c31e vs main 4af28f3) are stale.

SenseNova-U1-8B-MoT-LoRA-8step-V1.0.safetensors \
--local-dir /path/to/sensenova-lora

python tools/sensenova_u1/convert_lora_to_peft.py \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P3] The converter refuses to write into an output directory that already holds adap…

Evidence and suggested fix

The converter refuses to write into an output directory that already holds adapter files (tools/sensenova_u1/convert_lora_to_peft.py:105-106 raises FileExistsError), and its hint is worded in Python-API terms ('pass overwrite=True') while the CLI flag is --overwrite (line 129). The two-command flow documented in docs/user_guide/diffusion/lora.md:41-43 never mentions --overwrite, so re-running the snippet — fresh hf download, retried, or source-swapped conversion — fails with no in-doc remedy. One sentence (or an inline comment on the snippet) closes it.

Evidence: tools/sensenova_u1/convert_lora_to_peft.py:105-106 (added by this diff, verified at head 9419599): if not overwrite and (weights_path.exists() or config_path.exists()): -> raise FileExistsError(f"PEFT adapter already exists in {output_dir}; pass overwrite=True to replace it") — so a second run of the documented flow fails once /path/to/sensenova-lora/peft holds adapter_model.safetensors or adapter_config.json (written at :111-120). The CLI flag the doc user can actually pass is --overwrite (:129 parser.add_argument("--overwrite", action="store_true", ...)). docs/user_guide/diffusion/lora.md:41-43 (section added by this diff) shows python tools/sensenova_u1/convert_lora_to_peft.py \ with no --overwrite, and the file's only --overwrite mention is the unrelated Wan2.2 section (lora.md:235). Anchor line is 41, not 42.

Suggestion: python tools/sensenova_u1/convert_lora_to_peft.py
/path/to/sensenova-lora/SenseNova-U1-8B-MoT-LoRA-8step-V1.0.safetensors
/path/to/sensenova-lora/peft # add --overwrite to replace a previous conversion

@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: finding feedback

[p0] The merge gate reports this branch as DIRTY/CONFLICTING — it both conflicts wit… — docs/user_guide/diffusion/lora.md:30

See the review for details. If you are the PR author and disagree, react 👎 here; the maintainer will see your disagreement.

@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: finding feedback

[p1] The production change — fm_head's two projections become ReplicatedLinear scann… — vllm_omni/diffusion/models/sensenova_u1/pipeline_sensenova_u1.py

See the review for details. If you are the PR author and disagree, react 👎 here; the maintainer will see your disagreement.

@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: no human activity for 15 days

@EchoHayate this pull request has had no human commit, comment or review since 2026-09-30. Please consider marking this PR as draft until work can resume. The author or a maintainer decides whether to change the PR state.

To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline.

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

Labels

diffusion codes related to diffusion models new model add new model

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[New Model]: SenseNova-U1-8B-MoT-LoRAs

4 participants