Fix final-layer LoRA target matching

This commit is contained in:
Xin Wang
2026-08-24 15:38:45 +08:00
parent 7fdda297d3
commit aa4278e13e
2 changed files with 37 additions and 17 deletions

View File

@@ -1083,8 +1083,10 @@ The first follow-up to the sparse-LoRA regression should remove sparse
supervision as a variable and constrain adaptation near the Thinker output. supervision as a variable and constrain adaptation near the Thinker output.
Use dense 5-Hz caches, reserve `Session_12` as development data, and train on Use dense 5-Hz caches, reserve `Session_12` as development data, and train on
`Session_4` through `Session_11`. `--lora-last-n-layers 8` discovers the `Session_4` through `Session_11`. `--lora-last-n-layers 8` discovers the
language-layer indices and stores PEFT `layers_to_transform=[28,...,35]` for language-layer indices and passes the 24 selected full module paths directly
the 36-layer Qwen2.5-Omni-3B Thinker. Combining it with to PEFT for the 36-layer Qwen2.5-Omni-3B Thinker. Explicit paths are required
because the extracted Thinker exposes names beginning with `layers.0...`,
which some PEFT `layers_pattern` versions fail to recognize. Combining it with
`--lora-target-modules q_proj,v_proj,o_proj` reduces the adapter from all 252 `--lora-target-modules q_proj,v_proj,o_proj` reduces the adapter from all 252
attention/MLP projections to 24 output-side attention projections. attention/MLP projections to 24 output-side attention projections.

View File

@@ -809,7 +809,7 @@ def _prepare_language_lora(
if (match := _TRANSFORMER_LAYER_PATTERN.search(name)) is not None if (match := _TRANSFORMER_LAYER_PATTERN.search(name)) is not None
} }
) )
layers_to_transform: list[int] | None = None selected_layer_indices: list[int] | None = None
matched = matched_all matched = matched_all
if last_n_layers: if last_n_layers:
if not layer_indices: if not layer_indices:
@@ -821,8 +821,8 @@ def _prepare_language_lora(
f"--lora-last-n-layers={last_n_layers} exceeds the " f"--lora-last-n-layers={last_n_layers} exceeds the "
f"{len(layer_indices)} matched Thinker layers" f"{len(layer_indices)} matched Thinker layers"
) )
layers_to_transform = layer_indices[-last_n_layers:] selected_layer_indices = layer_indices[-last_n_layers:]
selected = set(layers_to_transform) selected = set(selected_layer_indices)
matched = [ matched = [
name name
for name in matched_all for name in matched_all
@@ -835,21 +835,39 @@ def _prepare_language_lora(
raise RuntimeError( raise RuntimeError(
"Final-layer selection removed every matched LoRA module" "Final-layer selection removed every matched LoRA module"
) )
config_kwargs: dict[str, Any] = {} # PEFT's layers_pattern matcher requires additional nesting before the
if layers_to_transform is not None: # ModuleList name in some versions. The extracted Omni Thinker exposes
config_kwargs.update( # modules directly as ``layers.28...``, so layers_to_transform can reject
layers_to_transform=layers_to_transform, # every otherwise valid target. Explicit full module paths use PEFT's
layers_pattern="layers", # exact/suffix-list matching and are stable for this submodel hierarchy.
) peft_target_modules = matched if selected_layer_indices is not None else list(
target_modules
)
config = LoraConfig( config = LoraConfig(
r=rank, r=rank,
lora_alpha=alpha, lora_alpha=alpha,
lora_dropout=dropout, lora_dropout=dropout,
target_modules=list(target_modules), target_modules=peft_target_modules,
bias="none", bias="none",
**config_kwargs,
) )
language_model = get_peft_model(language_model, config) language_model = get_peft_model(language_model, config)
targeted_module_names = getattr(
getattr(language_model, "base_model", None), "targeted_module_names", None
)
if targeted_module_names is not None:
actual = list(targeted_module_names)
missing = [
expected
for expected in matched
if not any(
name == expected or name.endswith(f".{expected}") for name in actual
)
]
if missing:
raise RuntimeError(
"PEFT did not inject all selected LoRA modules; first missing="
f"{missing[0]}, missing={len(missing)}, targeted={len(actual)}"
)
language_model.config.use_cache = False language_model.config.use_cache = False
if gradient_checkpointing: if gradient_checkpointing:
try: try:
@@ -861,14 +879,14 @@ def _prepare_language_lora(
if verbose: if verbose:
language_model.print_trainable_parameters() language_model.print_trainable_parameters()
print( print(
f"LoRA matched {len(matched)} language layers; first={matched[0]}, " f"LoRA matched {len(matched)} language linear modules; first={matched[0]}, "
f"last={matched[-1]}" f"last={matched[-1]}"
) )
if layers_to_transform is not None: if selected_layer_indices is not None:
print( print(
"LoRA layer restriction: " "LoRA layer restriction: "
f"{layers_to_transform[0]}--{layers_to_transform[-1]} " f"{selected_layer_indices[0]}--{selected_layer_indices[-1]} "
f"({len(layers_to_transform)} of {len(layer_indices)} layers)" f"({len(selected_layer_indices)} of {len(layer_indices)} layers)"
) )
return language_model return language_model