Skip to content

fix: correct shard_ids for GDN in_proj_qkv under LoRA (Qwen3.5/3.6)#1711

Merged
AlpinDale merged 1 commit into
dphnAI:mainfrom
Nakanokensetsu:fix-exl3-in-proj-qkv-shard
Jul 16, 2026
Merged

fix: correct shard_ids for GDN in_proj_qkv under LoRA (Qwen3.5/3.6)#1711
AlpinDale merged 1 commit into
dphnAI:mainfrom
Nakanokensetsu:fix-exl3-in-proj-qkv-shard

Conversation

@Nakanokensetsu

Copy link
Copy Markdown
Contributor

Summary

When --enable-lora is set for a Qwen3.5/3.6 (GDN hybrid Mamba/linear-attention) model quantized with EXL3, weight loading fails with:

ValueError: Missing EXL3 tensors for language_model.model.layers.0.linear_attn.in_proj_qkv:
suh[0], suh[1], suh[2], svh[0], svh[1], svh[2], trellis[0], trellis[1], trellis[2]

Root cause

aphrodite/model_executor/models/qwen3_5.py's load_weights() has two branches depending on whether LoRA is enabled:

if self.enable_lora:
    stacked_params_mapping.extend([
        ("in_proj_qkv", "in_proj_qkv", (0, 1, 2)),   # <- loaded with a single tuple shard_id
        ("in_proj_z", "in_proj_z", 0),
    ])
else:
    stacked_params_mapping.extend([
        ("in_proj_qkvz", "in_proj_qkv", (0, 1, 2)),
        ("in_proj_qkvz", "in_proj_z", 3),
    ])

exllamav3 quantizes this GDN block's q+k+v projection as a single fused trellis matrix (confirmed against exllamav3/architecture/qwen3_5.py, which always defines separate in_proj_qkv / in_proj_z keys, independent of any downstream LoRA split). So in both branches, the weight loader is called with shard_id=(0, 1, 2) — a single tuple — populating param.exl3_tensors[(0, 1, 2)].

exl3.py's Exl3LinearMethod._shard_ids_for_layer() only special-cases the combined in_proj_qkvz prefix (LoRA-disabled path):

if prefix.endswith("in_proj_qkvz"):
    return [(0, 1, 2), 3]

return list(range(len(output_partition_sizes)))

For the standalone in_proj_qkv prefix (LoRA-enabled path), no branch matches, so it falls through to the generic fallback and returns [0, 1, 2] — three separate integer shard ids. process_weights_after_loading() then checks for exl3_tensors[0], exl3_tensors[1], exl3_tensors[2], none of which exist (only the tuple key (0, 1, 2) was ever populated), hence the "Missing EXL3 tensors" error.

Fix

Add the missing branch, mirroring the tuple-shard convention already used for the in_proj_qkvz case:

if prefix.endswith("in_proj_qkv"):
    return [(0, 1, 2)]

Test plan

Verified end-to-end on Qwen3.6-27B-exl3-3.08bpw (TP=2, RTX 5070 ×2) with --enable-lora --max-lora-rank 64: weight loading now succeeds past this point (a separate, unrelated LoRA-device-detection issue was also found and fixed — see companion PR). Without this fix, loading fails deterministically at layer 0 as shown above.

I also confirmed via dphnAI/sonar issue/PR search that this specific combination (EXL3 + Qwen3.5/3.6 GDN hybrid + LoRA) had no prior reports.

When --enable-lora is set, qwen3_5.py loads the GDN block's in_proj_qkv
weight via stacked_params_mapping with shard_id=(0, 1, 2) (a single
tuple), since exllamav3 quantizes q+k+v as one fused trellis for this
layer regardless of the LoRA-enabled / combined-in_proj_qkvz split.

_shard_ids_for_layer() only special-cased the LoRA-disabled combined
'in_proj_qkvz' prefix. For the standalone 'in_proj_qkv' prefix used in
the LoRA-enabled path, it fell through to the generic fallback and
returned [0, 1, 2] (three separate integer shard ids), which never
matches the tuple key (0, 1, 2) actually populated by the weight
loader, causing:

  ValueError: Missing EXL3 tensors for ...in_proj_qkv: suh[0], suh[1],
  suh[2], svh[0], svh[1], svh[2], trellis[0], trellis[1], trellis[2]

This adds the missing branch, mirroring the tuple-shard convention
already used for the qkvz case.
@AlpinDale
AlpinDale merged commit 9cb097c into dphnAI:main Jul 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants