Skip to content

[Fix] lora: Load fused-module members into their own buffer slots - #44332

Merged
yueming-yuan merged 4 commits into
sglang-milesfrom
yueming/lora-fill-absent-fused-members
Oct 15, 2026
Merged

yueming-yuan merged 4 commits into
sglang-milesfrom
yueming/lora-fill-absent-fused-members

Conversation

@yueming-yuan

@yueming-yuan yueming-yuan commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

An adapter may target any subset of the members SGLang packs into one fused LoRA module: q_proj/k_proj/v_proj -> qkv_proj, gate_proj/up_proj -> gate_up_proj, q_a_proj/kv_a_proj_with_mqa -> fused_qkv_a_proj_with_mqa, Inkling wq_du/wk_dv/wv_dv/wr_du -> qkvr. The loader concatenated the members into the fused key at normalization time, assuming specific members are present. On Qwen3-0.6B with sglang-miles:

adapter targets before
q, o KeyError: ...self_attn.v_proj.lora_A.weight
k, v ValueError: Cannot find target module name
up, down ValueError: Cannot find target module name

A trainer could only work around this by exporting zero A/B pairs for untrained members, with widths computed from its own copy of the engine's layout.

Modifications

Members are no longer concatenated in LoRAAdapter. The memory pool writes each member into its own slot of the fused buffer:

  • A: member i goes to rows [i*r, (i+1)*r); B: member i goes to the member's row range of the local output. Absent members are left zero, so the fused module computes exactly the partial adapter.
  • The per-member geometry (full rows, TP-local offsets, TP slice incl. KV-head replication and MLA first_output_dim) comes from the *WithLoRA wrapper that already owns the fused layout: lora_b_member_full_rows(), lora_b_member_local_offsets(), slice_lora_b_member(). slice_lora_b_weights is rebuilt from the same methods, so there is one definition per wrapper. packed_modules_mapping is not used; it is absent on Llama/Qwen2/Qwen3 and does not match the LoRA modules on Qwen3.5 / NemotronH.
  • LORA_FUSED_MEMBERS in lora/utils.py is the single member table; params_mapping and stacked_rank derive from it.
  • An adapter that already carries the fused key (qkv_proj, fused_qkv_a_proj_with_mqa, ...) is split by the same geometry; carrying both the fused and the per-member form is rejected.
  • MoE (routed / shared expert) gate/up keep the existing stacked path.

Behavior: an adapter covering all members loads byte-identical buffers to the old concatenating loader (unit test, all TP ranks, max_lora_rank = r and r+1). Partial adapters that raised before now load. An MLA adapter with a fused fused_qkv_a_proj_with_mqa key is newly accepted.

Tests

test/registered/unit/lora/test_lora_fused_member_load.py (CPU, 37 cases) on fake QKV / MergedColumn / Inkling qkvr / MLA wrappers:

  • complete adapter == old concatenating loader (oracle copied from sglang-miles@4076a68579), every TP rank, max_lora_rank in {r, r+1}
  • every member subset loads, absent members are zero
  • fused-key adapters (shared and stacked A) match their per-member form; fused + per-member is rejected
  • LoRAAdapter keeps dense members separate and still stacks expert gate/up

E2E on H200, Qwen3-0.6B, r=8: each adapter is compared against the same SGLang running the PEFT-merged weights (merge_and_unload). Mean |logprob diff|, rows = LoRA run, columns = merged model:

full q_o k_v up_down
full 0.046 0.610 0.545 0.650
q_o 0.613 0.038 0.212 0.243
k_v 0.543 0.226 0.045 0.236
up_down 0.649 0.258 0.241 0.037

The diagonal is bf16 rounding of the merged weights; every LoRA run matches only its own merged model. The full adapter's logprobs are identical (==) to sglang-miles before this change.

Not in this PR

Fused modules whose members are not plain row blocks of B, and MoE gate/up, keep the current path: GDN in_proj_qkvz / in_proj_ba, Mamba in_proj, routed/shared-expert gate_up_proj.

Consumer

radixark/miles#4101 drops the trainer-side zero-fill and exports only the trained members.


CI States

Latest PR Test (Base): ❌ Run #38660217031
Latest PR Test (Extra): ❌ Run #38660216994
Latest PR Test (AMD ROCm 10): ❌ Run #38660217049

@github-actions github-actions Bot added the lora label Oct 14, 2026
@yueming-yuan yueming-yuan changed the title [sglang-miles] [LoRA] Zero-fill absent members of fused qkv / MLA qkv_a adapters [Fix] lora: Zero-fill absent members of fused qkv / MLA qkv_a adapters Oct 14, 2026
An adapter may target any subset of q/k/v, gate/up, q_a/kv_a_with_mqa or
Inkling wq/wk/wv/wr. The loader used to concatenate members into the fused
key and failed (KeyError / ValueError) when one was absent. Each member now
writes its own A rows and B rows of the fused buffer; absent members stay
zero. The LoRA wrappers own the per-member TP geometry. MoE gate/up keeps
the stacked path.
@yueming-yuan
yueming-yuan force-pushed the yueming/lora-fill-absent-fused-members branch from e9b7cec to 4b86318 Compare October 14, 2026 22:06
@yueming-yuan yueming-yuan changed the title [Fix] lora: Zero-fill absent members of fused qkv / MLA qkv_a adapters [LoRA] Load fused-module members into their own buffer slots Oct 14, 2026
PEFT can ship an untrained up_proj as zero-element tensors. The dense
gate/up normalization used to zero-fill them; per-member loading sliced
them instead and raised IndexError.
CI collection requires an executable __main__ block in every registered
test file. Also correct the subset test's docstring: k/v-only adapters
raised ValueError rather than being dropped.
@yueming-yuan yueming-yuan changed the title [LoRA] Load fused-module members into their own buffer slots [Fix] lora: Load fused-module members into their own buffer slots Oct 15, 2026

@jybsuper jybsuper left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@yueming-yuan
yueming-yuan merged commit 7b6993a into sglang-miles Oct 15, 2026
70 of 80 checks passed
@yueming-yuan
yueming-yuan deleted the yueming/lora-fill-absent-fused-members branch October 15, 2026 05:25
nv-hwoo pushed a commit to nv-hwoo/sglang that referenced this pull request Oct 20, 2026
Rockdu pushed a commit that referenced this pull request Oct 30, 2026
Rockdu pushed a commit that referenced this pull request Oct 30, 2026
Rockdu pushed a commit that referenced this pull request Oct 31, 2026
Rockdu pushed a commit that referenced this pull request Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants