Skip to content

fix: prevent silent double-application of LoRA weights - #473

Open
WangXukang-cypher wants to merge 2 commits into
AI-Hypercomputer:mainfrom
WangXukang-cypher:fix/lora-double-merge-guard
Open

fix: prevent silent double-application of LoRA weights#473
WangXukang-cypher wants to merge 2 commits into
AI-Hypercomputer:mainfrom
WangXukang-cypher:fix/lora-double-merge-guard

Conversation

@WangXukang-cypher

Copy link
Copy Markdown

Summary

  • Wire up the previously dead num_fused_loras counter in LoRABaseMixin to track merged LoRA identities
  • Before each merge, the loader checks whether the same (path, weight_name) was already applied and skips with a warning
  • Covers all three NNX loaders: Wan2_1NNXLoraLoader, Wan2_2NNXLoraLoader, LTX2NNXLoraLoader

Problem

merge_lora unconditionally adds delta to model weights (kernel += delta). Calling load_lora_weights twice with the same LoRA — via duplicate config entries or notebook cell re-execution — silently doubles the LoRA effect. The existing num_fused_loras = 0 counter in LoRABaseMixin was never incremented or checked.

Test plan

  • Pre-commit passes (ruff, pyink, pylint ≥ 7)
  • Verified: first merge allowed, duplicate blocked, different LoRA allowed, different weight name allowed, independent loader instances isolated, class-level counter unchanged
  • Existing CI tests (require TPU environment)

Wire up the previously dead `num_fused_loras` counter in LoRABaseMixin
to track merged LoRA identities. Before each merge, the loader checks
whether the same (path, weight_name) was already applied and skips with
a warning if so. This prevents accidental weight corruption when users
list duplicate LoRA paths in config or re-run merge calls in notebooks.
@google-cla

google-cla Bot commented Sep 4, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a mechanism to prevent duplicate LoRA weight applications by tracking merged LoRA keys within the LoRABaseMixin class and checking them in the LTX2 and Wan LoRA loaders. The review feedback highlights two important issues: first, defining __init__ in a mixin class can cause runtime issues, so lazy initialization of the tracking set is recommended; second, checking and recording the LoRA key before verifying if the pipeline actually has the target transformer attributes can lead to incorrect tracking state, so the check should be deferred until after those attributes are verified.

Comment on lines +27 to +36
def __init__(self):
self._fused_lora_keys = set()

def _check_and_record_lora(self, lora_key):
"""Return True if this LoRA was already merged (duplicate). Records it otherwise."""
if lora_key in self._fused_lora_keys:
return True
self._fused_lora_keys.add(lora_key)
self.num_fused_loras += 1
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Defining __init__ in a mixin class like LoRABaseMixin can easily lead to AttributeError at runtime if subclasses or multiple inheritance hierarchies do not properly call super().__init__().\n\nA much safer and more idiomatic approach for Python mixins is to use lazy initialization inside the helper method (e.g., checking hasattr(self, '_fused_lora_keys')). This completely avoids the need for __init__ in the mixin.

Suggested change
def __init__(self):
self._fused_lora_keys = set()
def _check_and_record_lora(self, lora_key):
"""Return True if this LoRA was already merged (duplicate). Records it otherwise."""
if lora_key in self._fused_lora_keys:
return True
self._fused_lora_keys.add(lora_key)
self.num_fused_loras += 1
return False
def _check_and_record_lora(self, lora_key):
"""Return True if this LoRA was already merged (duplicate). Records it otherwise."""
if not hasattr(self, "_fused_lora_keys"):
self._fused_lora_keys = set()
if lora_key in self._fused_lora_keys:
return True
self._fused_lora_keys.add(lora_key)
self.num_fused_loras += 1
return False

Comment on lines +53 to 60
lora_key = (lora_model_path, transformer_weight_name)
if self._check_and_record_lora(lora_key):
max_logging.log(f"WARNING: LoRA '{lora_model_path}' already merged — skipping to avoid double-application.")
return pipeline

if hasattr(pipeline, "transformer") and transformer_weight_name:
max_logging.log(f"Merging LoRA into transformer with rank={rank}")
h_state_dict, _ = lora_loader.lora_state_dict(lora_model_path, weight_name=transformer_weight_name, **kwargs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Checking and recording the LoRA key before verifying if transformer_weight_name is provided and if the pipeline has the transformer attribute causes num_fused_loras to be incorrectly incremented and a dummy key to be recorded even when no LoRA is actually merged.\n\nWe should only check and record the LoRA key if we are actually going to attempt to merge it.

Suggested change
lora_key = (lora_model_path, transformer_weight_name)
if self._check_and_record_lora(lora_key):
max_logging.log(f"WARNING: LoRA '{lora_model_path}' already merged — skipping to avoid double-application.")
return pipeline
if hasattr(pipeline, "transformer") and transformer_weight_name:
max_logging.log(f"Merging LoRA into transformer with rank={rank}")
h_state_dict, _ = lora_loader.lora_state_dict(lora_model_path, weight_name=transformer_weight_name, **kwargs)
if hasattr(pipeline, "transformer") and transformer_weight_name:
lora_key = (lora_model_path, transformer_weight_name)
if self._check_and_record_lora(lora_key):
max_logging.log(f"WARNING: LoRA '{lora_model_path}' already merged — skipping to avoid double-application.")
return pipeline
max_logging.log(f"Merging LoRA into transformer with rank={rank}")
h_state_dict, _ = lora_loader.lora_state_dict(lora_model_path, weight_name=transformer_weight_name, **kwargs)

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.

1 participant