Skip to content

[Diffusion][LoRA] Rework linear and LoRA linear common part - #37460

Open
vlserov wants to merge 19 commits into
sgl-project:mainfrom
vlserov:vlserov/linear
Open

vlserov wants to merge 19 commits into
sgl-project:mainfrom
vlserov:vlserov/linear

Conversation

@vlserov

@vlserov vlserov commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

The LoRA linear classes repeatedly implemented the same logic as the base linear classes. To avoid conflicts and simplify future maintenance, the linear class logic needs to be refactored.

Visualisation comparing LoRA linear classes and base linear classes: BaseLayerWithLoRA_ReplicatedLinear RowParallelLinearWithLoRA_RowParallelLinear ColumnParallelLinearWithLoRA_ColumnParallelLinear

Modifications

  • A base linear class ParallelLinear was created, which implements common behaviour for all linear classes.

  • Other linear classes inherit from ParallelLinear and only re‑implement parts that differ from the base implementation.

  • The LoRA base linear class BaseLayerWithLoRA implements a generic forward method. Each inheriting class specifies the behaviour of its respective base linear layer while leveraging the base LoRA implementation.

Accuracy Tests

Server launch and request
sglang serve --model-path Tongyi-MAI/Z-Image-Turbo --model-type diffusion --dit-cpu-offload false --num-gpus 1 --port 8080 --host localhost
curl -sS -X POST "http://localhost:8080/v1/images/generations" -H "Content-Type: application/json" -H "Authorization: Bearer sk-proj-1234567890" `
    -d '{ `
        "prompt": "At night, while their owner sleeps in a bedroom, three cats march in loudly playing tiny brass instruments, then abruptly file out.", `
        "size": "1024x1024", `
        }'
curl -sS -X POST "http://80.48.5.153:30040/v1/set_lora" -H "Content-Type: application/json"  `
    -d '{ `
        "lora_nickname": "lora_name", `
        "lora_path": "Ttio2/Z-Image-Turbo-pencil-sketch", `
        "target": "all", `
        }'
Output results: no_lora_image lora_image

Speed Tests and Profiling

platform linux -- Python 3.11.14, pytest-9.0.2, pluggy-1.6.0
configfile: pyproject.toml
plugins: cov-7.1.0, anyio-4.12.0
collected 40 items

python/sglang/multimodal_gen/test/unit/test_lora_peft.py ......... [ 22%]
python/sglang/multimodal_gen/test/unit/test_lora_pipeline.py ........ [ 42%]
python/sglang/multimodal_gen/test/unit/test_fused_lora_compose.py ........... [ 70%]
python/sglang/multimodal_gen/test/unit/test_lora_merge_cache.py ..... [ 82%]
python/sglang/multimodal_gen/test/unit/test_lora_format_adapter.py . [ 85%]
python/sglang/multimodal_gen/test/unit/test_lora_inference_mode.py .. [ 90%]
python/sglang/multimodal_gen/test/unit/test_lora_commit_as_base.py .... [100%]
=========================== 40 passed, 17 warnings in 22.64s ===========================

Checklist

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

CI States

Latest PR Test (Base): ❌ Run #40185619515
Latest PR Test (Extra): ❌ Run #40185619518
Latest PR Test (AMD ROCm 10): ❌ Run #40185619506

@ping1jing2

Copy link
Copy Markdown
Collaborator

please update the description, then click ready to review

@ping1jing2
ping1jing2 marked this pull request as draft September 7, 2026 16:40
@github-actions github-actions Bot added the lora label Sep 8, 2026
@vlserov
vlserov marked this pull request as ready for review September 8, 2026 21:22
@ping1jing2 ping1jing2 self-assigned this Sep 11, 2026
@mickqian

Copy link
Copy Markdown
Collaborator

/tag-and-rerun-ci

@github-actions github-actions Bot added the run-ci CI: run the baseline test suite on this PR label Sep 11, 2026
@vlserov

vlserov commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

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

CI follow-up on exact head 0362801: 2-GPU partition 0 fails wan2_2_t2v_a14b_lora_2gpu consistency and LoRA basic API validation (SSIM 0.7091 vs >=0.95; PSNR 17.0777 vs >=22.2). 1-GPU partition 3 fails LTX-2.3 HQ consistency as well as E2E latency (25.884s vs limit19.968s). Both suites report 1 failed/8 passed. These are not merely runner-timeout failures, so I have not rerun them or changed GT/thresholds.

One concrete regression candidate in the refactor: the shared LoRA forward computes input_parallel = base_layer.parallel_input(input), but then feeds the original input to _compute_lora_delta. For RowParallelLinear with input_is_parallel=False and TP>1, slice_lora_a_weights still returns a TP-sharded A, whereas the previous row-specific forward used input_parallel for this multiplication. Please use the same sharded input for the LoRA path and add a real row-parallel unmerged-LoRA regression covering input_is_parallel=False. This source-level mismatch is not yet proven to explain the observed CI image difference; also validate the merged and output-offset paths before updating any golden output. I have no push access to the head repository and have not modified the branch.

@vlserov

vlserov commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

@mickqian thank you for the review. Comment has been resolved.

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

CI follow-up on exact head 8ab1a64:

  • H100 single-GPU partition 3: fast_hunyuan_video and fastwan2_2_ti2v_5b recover on failed-item retry. The final remaining failure is ltx_2_3_hq_pipeline consistency (minimum SSIM 0.9507, PSNR 32.6642; maximum MAD 2.6987), against the existing per-case gates and pinned GT 252710158cd4c74b7604808a385e93f7bced6d28. This is not merely the earlier E2E failure; consistency must not be retried away or regenerated just to pass.
  • B200, runner b200-cirrascale1-4567: final retry still fails Flux1 NVFP4 E2E 1432.4429ms > 1045.8875ms and Qwen2512 NVFP4 E2E 18734.9121ms > 12062.5750ms. Earlier failures in other cases recover, so the accumulated failure lines are not the final failing set.
  • All three dual-GPU partitions are health-check cascades from these roots. The coverage check consequently reports all 28 dual-GPU cases missing; this does not establish a partition-registration bug or validate the new head's dual-GPU LoRA path.

The PR changes the shared linear/LoRA execution path; the logs alone do not distinguish a numerical/performance regression from environment effects. Please compare the parent and this exact head on the same runner/environment with unchanged configuration and gates. I have no push access to the head repository, have not modified the branch, and have not rerun the non-retryable consistency failure.

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

CI follow-up: GitHub currently reports a merge conflict with main on exact head 8ab1a64. Please merge the current target branch with a normal merge commit and resolve the conflicts, then validate the new head. I do not have push permission to this fork; I have not edited it or requested a CI rerun. This is a new mergeability blocker, separate from existing CI results.

detain added a commit to detain/sglang that referenced this pull request Oct 8, 2026
…ick of sgl-project#37460)

Squash-apply of the net diff (f385390..pr37460, base == our tree state
for these files, incl. the PR branch's own main merge); individual
cherry-picks conflicted against main-side reworks older than the tip.

- runtime/layers/linear.py: extract parallel_input/apply_quant_method/
  collect_output hooks as the common forward decomposition.
- runtime/layers/lora/linear.py: LoRA delta now computed on the parallel
  input with quant-method reuse (drops the duplicated base call),
  torch.compile removed for LoRA layers (perf regression), dynamic-LoRA
  forward input-tensor fix, per-class compile opt-in only; keeps
  can_merge_base_weight for pipelines_core/lora/pipeline.py.
- test/unit/test_fused_lora_compose.py adapted.

Co-authored PR commits: 29e85d9 b605c95 af55603 c3cf355 efbc3ff 58ce92b 0e7ecd5
detain added a commit to detain/sglang that referenced this pull request Oct 8, 2026
sgl-project#43028's base carries the old 4-site LoRA forwards plus the runtime scale
input from sgl-project#30487; this stack has the sgl-project#37460 rework and no runtime scale,
so the three commits land as their net final state: BaseLayerWithLoRA.
_scale_lora_delta folds alpha/rank and strength multiplies, skipping
elementwise muls entirely at unit scale (measured ~6%/multiply on LTX-2
refinement LoRA stages). Ported test counts aten.mul ops at 0/1 across
strength x {nn.Linear, ReplicatedLinear}.

PR commits: 051cf1c 6d9f811 17302ee
detain added a commit to detain/sglang that referenced this pull request Oct 8, 2026
… of sgl-project#43064)

Squash of 13 commits 48c3a28..da3d8c6 (PR rebased onto main tip
407f7f0; net diff via git apply -3). Removes rethrow-only wrappers,
obsolete channels_last_3d/sinc fallbacks, identity assignments; shares
BCG model unwrapping, component-name normalization, and LLM-encoder
weight loading (load_llm_encoder_weights).

Conflict resolutions:
- encoders/llama.py + qwen3.py: took theirs (shared helper call).
  Ported load_stacked_weight() def from merged upstream sgl-project#42988 (not in
  our tree) into weight_utils.py as the helper's dependency.
- layers/lora/linear.py: kept OURS in all 4 blocks — our sgl-project#37460 rework
  already removes the per-class inits/forwards this PR targets; the one
  clean hunk (redundant LinearWithLoRA.__init__) applied.
- disaggregation/orchestrator.py: kept OURS (file is our sgl-project#32550 DAG
  rewrite; the PR's receiver-dispatch dedupe targeted old main shape).
  Net file unchanged.
- Skipped two PR file-hunks outright: vaes/alias_free.py and
  model_specific_stages/kandinsky6/decoding.py exist only upstream of
  our merge-base (not in this tree).

Tests: py_compile all; unit -k lora/encoder/padding/breakable/vision/
transfer 347 passed; weight_utils runai-module failures pre-existing
(env lacks runai_model_streamer), file untouched by PR.
detain added a commit to detain/sglang that referenced this pull request Oct 15, 2026
…ject#4

Conflict resolutions (35 files), preserving stack behavior:

srt/utils/common.py: make_layers — folded our per-layer empty_cache body
  INTO upstream's stage-binding build() (layer_stack refactor sgl-project#43453-43460).
  Upstream removed is_building_neighbour_layer/building_neighbour_layer
  entirely (our earlier sgl-project#42481 pick); only user was qwen4_exp.
srt/models/qwen4_exp.py: import unions (cached_property/partial/TYPE_CHECKING,
  linear_attn_parallel_group); dropped dead is_building_neighbour_layer import;
  prefetch_stream keeps our _graph_prefetch_buffer line with upstream's
  ple_offload_embedding conditional; our PLE host-staging design kept
  (_offloads_ple guard moot under new stage facts).
srt/managers/cache_controller.py: upstream sgl-project#43550 absorbed our sgl-project#39830
  prefetch-degrade fixes; took upstream restructure, deleted auto-merge's
  stale duplicate tails (double-ack/deadlock hazard), kept our rationale
  comments. Fenced start_writing + load_fence_stream survive.
srt/mem_cache/hybrid_cache/hybrid_cache_controller.py: same pattern;
  sidecar degrade comment updated ('upstream adopted unconditionally').
model_executor/pool_configurator.py: UNION draft-cell sizing — our FP4
  _compute_eagle_fp4_draft_cell_size branch + upstream HiSparse
  draft_loc_space_scale else-branch. b05a7c1 None layer_info guard intact.
srt/layers/attention/qsa/sparse_attn.py: kept OUR FP8-descale chunk loop
  (superset; upstream sgl-project#41933 only re-commented); took upstream
  _pack_qsa_prefill_kv + pack_qsa_prefill_kv; our ck interface keeps
  k_scale/v_scale/max_q; __all__ union.
qsa/mqa.py: import functools + min_scored_len kwarg into our triton-first
  _resolve_mqa_backend dispatch; upstream ctas_per_row rework kept.
qsa/qsa_indexer.py: batch-invariance gate moved INTO upstream's shared
  use_fast_topk predicate.
qwen_sparse_attn_backend.py: imports union; gather path UNION — upstream
  pack_qsa_prefill_kv used when scales are unit (QSA stores unscaled K/V),
  our index_select fallback when checkpoint k/v descales != 1.0.
managers/schedule_policy.py: add_chunked_req UNION — upstream slot-reserve
  gate first, then our prefill_chunk_alignment truncation (monotone-safe);
  all 3 alignment sites (1177/1340/1608) intact.
managers/tokenizer_manager.py: import unions (PositionalEmbedsTransport +
  RequestPreprocessor + watermarking fns).
entrypoints/openai/serving_base.py: kept OUR request_preprocessor.run path.
model_executor/runner/eager_runner.py: took upstream TCPCG eager-fallback
  removal (sgl-project#42287); kept our shared cp_extend_forward call shape (our
  runner_utils/cp_extend.py is byte-equivalent to upstream's
  _execute_extend_cp — single source of truth).
runner/flashinfer_autotune.py: import union (can_run_dummy_draft_extend +
  cdiv/ceil_align + upstream get_eager_max_batch_size sgl-project#43789).
arg_groups/model_hook.py: Gemma4 accepted_backends union fa4 + flashinfer
  (NVFP4-KV enablement).
multimodal_gen/runtime/layers/lora/linear.py: KEPT OUR sgl-project#37460 pipeline
  structure; upstream runtime_lora_scale==0 early-return ordering adopted;
  _add_lora_delta/_add_lora_output_offset wired into pipeline; kept
  UPSTREAM's quality-tiered _scale_lora_delta (deleted our simple variant).
  DEBT: upstream RowParallel bias_in_gemm lossless opt not ported (perf-only).
multimodal_gen/runtime/layers/attention/layer.py: _gather_sharded_sequence
  took upstream sgl-project#43793 .contiguous() fix (subsumes our 55aaa02 guard);
  our _forward_with_kv_gather k/v guards kept (separate path, not covered).
configs/pipeline_configs/base.py + configs/sample/minimax_h3.py + envs.py:
  unions (supports_continuous_batching / teacache_params+fields /
  SGLANG_TEACACHE_CALIBRATE).
models/dits/ltx_2.py mask-fill: took upstream (identical to our fix).
ltx_2/denoising.py: took upstream pass_specs (sgl-project#43667) — subsumes our
  legacy one-stage branch; our _ltx2_model_forward_context helpers already
  upstreamed (entry-142 sgl-project#42928 CONFIRMED SUPERSEDED).
ltx_2/decoding_av.py: KEPT OUR split-stage AV decode (independent pool
  scaling) over upstream single-class restructure; adopted upstream
  chunked pinned-memory _postprocess_video; TODO(merge-4): upstream
  EncodeWhileDecoding streaming + rank-0 retain gating not ported.
kernels/ops/diffusion/activation/silu_mul_bitexact.py: our out= column-slice
  support + XPU guard UNION with upstream @replay_launches (sgl-project#43859).
kernels/ops/diffusion/README.md + docs performance-optimization.mdx: unions.
rust/sglang-renderer: ACCEPTED upstream crate removal (sampling.rs git rm);
  our sgl-project#42779 top_k vocab-bound port moves to sglang-server message/sampling.rs
  (follow-up).
Tests: anthropic test_serving — UNION fake-class exception param + our
  request_preprocessor stubs, took upstream's _FakeNonStreamingOpenAI removal
  (68 pass). test_server_args — union both new tests (pre-existing GPU-only
  collection ENV fail unchanged). test_lora_inference_mode — took upstream
  sgl-project#43028 runtime_scale+quality-tier test (supersedes our adapted variant;
  our kept quality-tiered _scale_lora_delta satisfies it).
  test_fused_moe_triton_config — kept our int4-filename + RTX-PRO-6000 JSON
  tests despite upstream sgl-project#43503 test-drop. test_hicache_file_lru_unit —
  union our corrupt-page-degrade class + upstream sgl-project#42077 uncached-batch test
  (40 pass). test_hicache_host_register — upstream alias host_common.
  test_cuda_graph_setup — took upstream rewrite of our override-decode-runner
  test into draft-local-capture-sizes test (sgl-project#43303 API).

Verified auto-merged stack markers survived: flashinfer_backend KVLocPlan/
prefill_wrappers_ragged/NVFP4-KV forwarding, memory_pool set_kv_buffer_prefix_valid,
fp8 grouped_moe kwarg, fp8_utils reshape fix 3642490, SGLANG_QWEN4_PLE_FILE_REUSE,
schedule_batch freed_rows fix, prefill_chunk_alignment sites, sgl-project#42736 autotune
default-on, PCIe-IPC vocab gathers, flashinfer 0.7.1 pin taken (NOT installed).

Co-Authored-By: Claude Opus 5.5 (1M context)
@github-actions

Copy link
Copy Markdown
Contributor

Required checks failed on f7a9523; this PR cannot be merged until they pass.

PR title (run)

PR title must use "[Type] scope: title" or "[Type] [Hardware] scope: title". Type must be one of: Feat, Fix, Refactor, Perf, Doc, Test, CI, Chore. Scope is required and may be any module or model name, with unrestricted letter case (letters, numbers, dots, underscores, slashes, or hyphens). Hardware is optional; use a single tag without spaces. Use uppercase hardware tags (AMD, NVIDIA, NPU, MI300X), except for exactly these four spellings: Intel, Apple, Huawei, Ascend. Reverts must use GitHub's default format: Revert "original title". Use one space between fields and after the colon, with a non-empty title and no leading or trailing whitespace. Examples: "[Fix] attention: Fix KV cache indexing", "[Feat] qwen3: Add model support", "[Perf] [AMD] flash-attn: Optimize decode performance".
Edit the title; the check reruns on its own.

This branch has not been deployed

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

Labels

diffusion SGLang Diffusion lora run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants