Skip to content

Filter adapter-name slots by tuner-prefix position - #3623

Open
Sravanjangam wants to merge 10 commits into
huggingface:mainfrom
Sravanjangam:fix/adapter-name-slot-filter
Open

Sravanjangam wants to merge 10 commits into
huggingface:mainfrom
Sravanjangam:fix/adapter-name-slot-filter

Conversation

@Sravanjangam

@Sravanjangam Sravanjangam commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3584

Problem

save_pretrained silently writes an empty/truncated adapter checkpoint when an adapter's name equals a base-model module path segment (e.g. an adapter called mlp while a submodule is literally named mlp): the negative name filter matched by raw string containment, so saving default dropped every key containing .mlp. — including default's own tensors.

Fix

_filter_state_dict_for_adapter_name now matches positionally using the existing _get_tuner_state_dict_key_prefixes(model): a key is dropped only when the segment immediately after a tuner prefix (lora_A, ia3_l, …) is an unwanted adapter name. Keys outside any tuner prefix are kept; modules_to_save copies are resolved per adapter downstream, so multi-adapter behavior there is unchanged.

Supersedes #3591 — that one only added a warning; the thread on #3584 pointed at fixing the filter itself, which is what's here.

Tests

  • TestAdapterNameCollisionFiltering (5 tests): partial + full collision key sets via get_peft_model_state_dict; reversed order (mlp first) with per-adapter value attribution; plain save_pretrained → from_pretrained round trip with init_lora_weights=False; non-colliding second adapter still filtered.
  • Bug proof: 4 of 5 fail on the base commit, the no-regression guard passes; all 5 pass with the fix.
  • Suites: full test_initialization.py 349 passed (3 FrodConfig failures reproduce identically on the base commit — pre-existing); test_shira.py 11 passed (call site updated); mixed modules_to_save selections match base (7 FrodConfig pre-existing failures, identical both sides).

Test environment: Python 3.12, torch 2.13.0+cpu, transformers 5.15.1, peft @ b147931, ruff 0.16.4 (check + format clean).

The negative filter in get_peft_model_state_dict matches by plain string
containment, so an adapter whose NAME equals a module path segment of the
saved adapter's tensors (e.g. an adapter called 'mlp' while a submodule is
named 'mlp') silently removed those tensors from the checkpoint — up to and
including writing a completely empty adapter_model.safetensors with no
warning at all.

Detect this collision before filtering and emit a RuntimeWarning naming the
affected tensors, so silent truncation becomes visible. A structural
(positional) filter is the cleaner long-term fix and is proposed in the
issue; this makes the failure mode observable without changing behavior.

Fixes huggingface#3584

@BenjaminBossan BenjaminBossan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for tackling this issue.

I found a couple of problems with the current PR and flagged them. Could you please share which LLM you are using, it feels rather weak to me.

Comment thread tests/test_initialization.py Outdated
self.mlp = nn.Linear(32, 32)
self.act = nn.ReLU()

def forward(self, x):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

forward is not used and can thus be removed.

Comment thread tests/test_initialization.py Outdated

return Net()

def _save_and_load(self, model, save_dir, selected_adapters):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need this? A simple save_pretrained -> from_pretrained should be enough. The returned config is not being used in the tests. To get the PEFT-specific weights from the model, use get_peft_model_state_dict.

Comment thread tests/test_initialization.py Outdated
tensors = load_file(str(save_dir / "adapter_model.safetensors"))
return config, tensors

def test_partial_collision_saves_all_default_tensors(self, mlp_net, tmp_path, recwarn):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The recwarn fixture here and below is not used.

Comment thread tests/test_initialization.py Outdated
torch.manual_seed(0)
model = get_peft_model(
mlp_net,
LoraConfig(r=4, lora_alpha=8, target_modules=["lin0", "mlp"], lora_dropout=0.0),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For a proper test, this should set init_lora_weights=False as B is otherwise always 0 and thus the values are always equal.

Comment thread tests/test_initialization.py Outdated
}
assert set(tensors.keys()) == expected_keys

def test_save_load_round_trip_with_colliding_adapter_name(self, mlp_net, tmp_path, recwarn):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This whole test doesn't contribute much. The other tests already establish that the key is missing from the checkpoint, so the fact that the weight is wrong trivially follows from that.

Comment thread tests/test_initialization.py
Comment thread tests/test_initialization.py Outdated
"base_model.model.mlp.lora_A.weight",
"base_model.model.mlp.lora_B.weight",
}
assert set(tensors.keys()) == expected_keys

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should also check the right keys when selecting the adapter name "mlp".

Comment thread src/peft/utils/save_and_load.py Outdated
using modules_to_save.
"""Filter the state dict to remove keys that correspond to the unwanted adapter.

A naive `f".{name}." in k` check is too permissive: if an unwanted adapter's NAME happens to equal a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This refers to the previous implementation, but when reading the code, the previous implementation is not visible. The explanation must make sense on its own and not require users to dig into the code history.

Comment thread src/peft/utils/save_and_load.py Outdated
state_dict: dict[str, torch.Tensor], unwanted_adapter_names: list[str]
state_dict: dict[str, torch.Tensor],
unwanted_adapter_names: list[str],
selected_adapter_name: str | None = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You changed the signature of the function but didn't update all the call sites. This function is also called in tuners/shira/model.py.

Comment thread src/peft/utils/save_and_load.py Outdated
# Keep the historical behaviour to avoid behaviour changes in that niche path.
return f".{name}." in key or key.endswith(f".{name}")

return {k: v for k, v in state_dict.items() if not any(_is_unwanted(k, n) for n in unwanted_adapter_names)}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This all seems too complicated to me. I tried a different version which takes advantage of the already existing _get_tuner_state_dict_key_prefixes and the tests pass for me:

def _filter_state_dict_for_adapter_name(
    state_dict: dict[str, torch.Tensor],
    unwanted_adapter_names: list[str],
    model,
) -> dict[str, torch.Tensor]:
    # avoid circular import
    from peft.tuners.tuners_utils import _get_tuner_state_dict_key_prefixes

    prefixes = _get_tuner_state_dict_key_prefixes(model)
    unwanted = set(unwanted_adapter_names)

    filtered_state_dict = {}
    for key, value in state_dict.items():
        keep = True
        for prefix in prefixes:
            prefix = prefix + "."
            if not key.startswith(prefix):
                continue

            suffix = key[len(prefix) :]
            adapter_name = suffix.partition(".")[0]
            if adapter_name in unwanted:
                keep = False
                break

        if keep:
            filtered_state_dict[key] = value

    return filtered_state_dict

…efixes

An adapter whose name equals a base-model module segment (e.g. adapter
'mlp') silently truncated the checkpoint: the containment filter dropped
the selected adapter's own tensors. Match positionally using
_get_tuner_state_dict_key_prefixes instead of mining prefixes from keys.

Per BenjaminBossan on huggingface#3623: adopt the simpler model-derived
implementation (both call sites incl. shira), standalone comments, tests
via get_peft_model_state_dict + plain save/load round trip, mlp-first
coverage, init_lora_weights=False where B=0 would trivialize.

Verification:
- 5 collision tests pass (2x); 4 fail on upstream 5d602fd, foo-guard passes
- full test_initialization 349 passed (3 FrodConfig fail identically on base)
- test_shira 11 passed; mixed modules_to_save same-as-base (7 Frod pre-existing)
- ruff check + format clean; doc-builder style --check_only exit 0
PR huggingface#3623.
@Sravanjangam

Sravanjangam commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @BenjaminBossan — all ten points addressed in b1479313 (no force-push), outputs checked before submitting:

Your implementation (save_and_load.py:113): adopted as proposed, using the existing _get_tuner_state_dict_key_prefixes(model) — the key-mining is gone, and I verified parity empirically rather than by inspection: the issue repro resolves both adapters' own pairs, and the mixed modules_to_save selections match base exactly (only the pre-existing FrodConfig failures, identical both sides via git stash compare). My read of why it's safe: modules_to_save copies are selected per adapter downstream, so keeping non-prefix keys can't leak across adapters.

Missed call site (save_and_load.py:79): shira/model.py:178 now passes model too; test_shira.py 11 passed on the committed tree.

Standalone comment (save_and_load.py:83): rewritten with no history references.

Tests (custom loader deleted; get_peft_model_state_dict + plain save→load instead):

  • forward removed (:6374); recwarn gone everywhere (:6397)
  • mlp-first ordering with per-adapter value attribution (:6406, :6418) — init_lora_weights=False keeps the two adapters' weights distinct so a mix-up fails
  • Round trip kept but made proper with init_lora_weights=False (:6441, :6447), since B=0 trivialized the old one
  • Bug proof: 4 of 5 new tests fail on the base commit (the no-regression guard passes); full test_initialization.py 349 passed with only the 3 pre-existing FrodConfig failures (verified identical on base). mergeable: true.

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

🤗 Serge says:

Verdict

The fix is correct: the old substring filter (f".{adapter_name}." in k) conflated adapter-name slots with base-model module path segments; the new positional check — drop only when the segment immediately after a tuner prefix (lora_A, lora_embedding_A, shira_indices, …) is an unwanted adapter name — resolves #3584 without weakening multi-adapter filtering.

Correctness

  • Verified _get_tuner_state_dict_key_prefixes (tuners_utils.py:1673): prefixes are derived structurally from tuner layers/BaseTuner attributes, exclude base_layer/model and auxiliary wrappers, and handle the FSDP prefix — so the positional match is robust and consistent with BaseTuner._get_adapter_state_dict's own key selection.
  • Keys outside any tuner prefix are now always kept. Safe: modules_to_save / AuxiliaryTrainingWrapper entries are re-selected per adapter downstream via adapter_state_dict(adapter_name, ...) (other.py:789, save_and_load.py:216-221), which only reads modules_to_save.{adapter_name}.*, so unwanted adapters' copies cannot leak into the checkpoint.
  • The len(...) > 0 guard at save_and_load.py:163 still behaves correctly for adaption_prompt: that tuner produces no structural prefixes, so the filter becomes a no-op and the guard logic is unchanged; its _get_adapter_state_dict selects adaption_* keys independently.
  • The SHIRA call site correctly passes model: shira_indices is a tuner-owned buffer dict keyed by adapter name, which the positional filter now handles properly.

Tests

TestAdapterNameCollisionFiltering covers partial and full collision, reversed adapter order with value attribution, an end-to-end save/load round trip, and a no-regression guard for a non-colliding second adapter; init_lora_weights=False making the adapters' weights distinguishable is the right call. Non-blocking gap: all tests exercise LoRA's lora_A/lora_B prefixes; a case with a different tuner prefix shape (e.g. IA³'s ia3_l, or an adapter named like a tuner attribute such as lora_A) would harden the positional matching further.

Style

  • The new model parameter lacks a type annotation while its siblings have one.

The description's claims (supersedes #3591, pre-existing FrodConfig failures, test counts) are consistent with the diff. Approving.

serge v0.1.0 · model: zai-org/GLM-5.3-Flash · 48 LLM turns · 53 tool calls · 379.0s · 1472048 in / 22456 out tokens

Comment thread src/peft/utils/save_and_load.py Outdated
state_dict: dict[str, torch.Tensor], unwanted_adapter_names: list[str]
state_dict: dict[str, torch.Tensor],
unwanted_adapter_names: list[str],
model,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: the other parameters are annotated; keep this consistent.

Suggested change
model,
model: nn.Module,

(nn.Module is already imported in this module.)

Comment thread tests/test_initialization.py

@BenjaminBossan BenjaminBossan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Sravanjangam Thanks for the update. I did my own review but also requested an LLM review from our Serge bot. I generally agree with that review (which is why I submitted it): The fix looks good, let's just extend the tests even more. This is a core functionality that affects all of PEFT, so I'd rather go the extra mile with unit tests to ensure it works as expected. Please add the test cases that the bot suggested.

@Sravanjangam

Sravanjangam commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @BenjaminBossan — both bot-suggested items are addressed (plain pushes, no force-push), all gates re-run on the pushed tip:

1. Annotation (save_and_load.py:79) — done in e12caf7c, with one deviation to flag: the bot's literal suggestion model: nn.Module would fail typechecking because nn is not imported in that module (it uses the torch.nn.Module spelling, e.g. line 311). So I annotated as model: torch.nn.Module, matching the file's existing convention. Happy to switch to a bare nn import instead if you prefer.

2. New hardening tests (test_initialization.py, TestAdapterNameCollisionFiltering) — exactly the gap Serge named:

  • test_ia3_prefix_shape_with_colliding_adapter_name: same "mlp" collision but through IA³'s single-vector ia3_l prefix instead of LoRA's lora_A/lora_B matrices. Both selection directions asserted.
  • test_adapter_named_like_tuner_attribute: an adapter literally named "lora_A", asserting both selection directions plus live-weight value attribution (init_lora_weights=False so a mix-up fails).

Verification (all on pushed tip 3c50473d):

  • New class: 7/7 passed, run twice (13–15s each, stable).
  • Baseline-fail proof in a clean worktree at 5d602fdb: 6 failed / 1 passed — both new tests fail on base and pass with the fix (the 1 pass is the no-regression guard, as expected).
  • Full test_initialization.py: 351 passed, 3 failed — the 3 failures are the pre-existing TestFrodInitialization cases, reproduced identically on the clean base worktree (3 failed / 1 passed there too), so zero new failures from this change.
  • ruff check and ruff format --check clean on both touched files (3c50473d fixes the one long line the formatter flagged).

Nothing else in the diff changed — the fix itself is untouched since your last review.

P.S. I did one final end-to-end verification on the latest tip after adding the additional test coverage.

Verification gates:

  • Gate 1: Targeted collision suite passed twice (7/7 both runs), including the new IA³ prefix-shape case and the adapter-named-lora_A case.
  • Gate 2: Reproduced both adapter orders (default → mlp and mlp → default) and verified the correct keys and live weights are selected for each adapter.
  • Gate 3: Baseline proof in a clean worktree (5d602fdb): 6/7 tests fail on the old implementation and all pass with this fix, confirming the new tests exercise the regression rather than just the happy path.

I also re-ran the broader verification: test_initialization.py (351 passed; only the same pre-existing TestFrodInitialization failures as base), test_shira.py passes, and ruff check + ruff format --check are clean.

Looks good to me from my side. just let me know any changes required

@BenjaminBossan BenjaminBossan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update. I found one test that I'm not sure if it really contributes to the coverage, please check.

Comment thread tests/test_initialization.py Outdated
model.add_adapter("foo", LoraConfig(r=4, lora_alpha=8, target_modules=["lin0"], lora_dropout=0.0))

tensors = get_peft_model_state_dict(model, adapter_name="default")
assert all("foo" not in k for k in tensors.keys())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm, isn't this assert trivially correct? "foo" is the adapter name, which is not part of the output of get_peft_model_state_dict. This makes the whole test somewhat moot.

Per BenjaminBossan on huggingface#3623 at tests/test_initialization.py:6469:
'foo' is not part of get_peft_model_state_dict output, so the
all('foo' not in k) assert alone is tautological.

Fix: prove filtering actually removed something — assert the raw
state_dict does contain 'foo', then assert the filtered view's keys
are exactly the expected default set, plus value attribution that the
returned tensor equals default's live weight (not foo's).

Verification:
- TestAdapterNameCollisionFiltering 7/7 pass twice
- 6/7 fail on base 5d602fd (1 pass is this guard, now non-trivial)
- full test_initialization 351 passed 3 failed (same Frod pre-existing)
- ruff check + format clean; doc-builder style exit 0
PR huggingface#3623.
@Sravanjangam

Copy link
Copy Markdown
Contributor Author

Hi @BenjaminBossan — addressed your tests/test_initialization.py:6469 point in 2200a809 (no force-push), outputs checked before submitting:

Trivial assert (test_real_second_adapter_is_still_filtered): you were right — all("foo" not in k for k in tensors.keys()) is tautological since get_peft_model_state_dict already filters. Fixed to prove filtering actually removed something:

  • Assert the raw model.state_dict() does contain "foo" (proves foo was present before filtering)
  • Assert the filtered get_peft_model_state_dict(..., adapter_name="default") keys are exactly the expected 4 default keys
  • Plus value attribution: tensors["...lin0.lora_A.weight"] equals model.base_model.model.lin0.lora_A["default"].weight (ensures it's default's weight, not foo's)

Verification: TestAdapterNameCollisionFiltering 7/7 passed twice; 6/7 fail on base 5d602fdb (the 1 pass is this guard, now non-trivial as intended); full test_initialization.py 351 passed with the same 3 pre-existing Frod failures; ruff + doc-builder clean. mergeable: true.

@BenjaminBossan BenjaminBossan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update. I still have a few issues around the tests, but with those out of the way, the PR should be ready.

Comment thread tests/test_initialization.py Outdated
"base_model.model.mlp.lora_B.weight",
}
# Value attribution: the returned tensor must be default's weight, not foo's.
assert torch.equal(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Okay, I see where this test is going now, but I think it's not necessary, please remove the test.

Comment thread tests/test_initialization.py Outdated
)
model.add_adapter("mlp", LoraConfig(r=4, lora_alpha=8, target_modules=["lin0"], lora_dropout=0.0))

tensors = get_peft_model_state_dict(model, adapter_name="default")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here and in the tests below, let's not call the variable tensors, as that sounds like a list of tensors. state_dict is a better fit.

Comment thread tests/test_initialization.py Outdated
"base_model.model.mlp.lora_A.weight",
"base_model.model.mlp.lora_B.weight",
}
live = model.base_model.model.mlp.lora_A[name].weight

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

live is a bit of an odd name, let's just go with weight. Also, how about comparing all the weights, not just this one example?

Same argument applies to the tests below.

Per BenjaminBossan on huggingface#3623 (09-17): remove unnecessary IA3 test,
rename tensors -> state_dict, live -> weight, compare all weights
instead of one example (same for the tests below).

- TestAdapterNameCollisionFiltering 6/6 pass (IA3 test removed per review)
@Sravanjangam

Copy link
Copy Markdown
Contributor Author

Hi @BenjaminBossan — all four points addressed in 0f6775bd (plain push):

  • test_ia3_prefix_shape_with_colliding_adapter_name removed (line 6478 note).
  • tensors → state_dict in the flagged test and the ones below it (default_tensors/lora_a_tensors renamed the same way).
  • live → weight, and the value check now compares all weights of the selected adapter, not just the one example — same expansion applied to test_real_second_adapter_is_still_filtered and test_adapter_named_like_tuner_attribute below it.

Verification: TestAdapterNameCollisionFiltering 6/6 pass (IA3 test dropped per your note), ruff + format clean.

@BenjaminBossan BenjaminBossan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update. Just a small suggestion left from my side, otherwise the PR is ready.

Comment thread tests/test_initialization.py Outdated
Comment on lines +6434 to +6435
module_name, param_name = key.split(".")[2], key.split(".")[3]
weight = getattr(getattr(model.base_model.model, module_name), param_name)[name].weight

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO this is simpler:

module_name = key.rpartition(".")[0]
weight = model.get_submodule(module_name)[name].weight

Same applies below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in ce8fd040 (plain push) — applied verbatim at all three sites, keeping each test's own index key ([name] / ["default"] / ["lora_A"]). Gates re-run on the pushed tip: TestAdapterNameCollisionFiltering 6/6 pass, ruff check + format clean.

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@BenjaminBossan

Copy link
Copy Markdown
Member

@Sravanjangam Please run make style.

@BenjaminBossan

Copy link
Copy Markdown
Member

There are failing SHiRA tests, could you please investigate?

The positional match alone missed keys that carry the adapter name as a suffix
rather than after a tuner prefix. SHiRA keeps its indices in a plain dict on the
layer, so they are neither parameters nor buffers and thus absent from the
structural prefixes; the other adapters' entries leaked into the checkpoint
(saving selected_adapters=["default"] wrote 6 keys instead of 2). Five SHiRA
cases of test_save_pretrained_adapter_name_substring failed on CI for this
reason and pass again with the suffix check restored, which was the previous
behaviour.

Also reflow the docstring per make style.
@Sravanjangam

Copy link
Copy Markdown
Contributor Author

Hi @BenjaminBossan — both points handled.

make style — that was ff37e98f, and make quality is now clean end to end: ruff check, ruff format --check (425 files), doc-builder style --check_only and check_doc_coverage.py (129/129, 100%). The new commit also reflows the docstring I rewrote, so it stays clean at the current head.

Failing SHiRA tests — root-caused and fixed in e138e585. The failures are the five SHiRA parameterizations of test_save_pretrained_adapter_name_substring in tests/test_custom_models.py; tests/test_shira.py itself passes 11/11 both before and after.

Cause: the positional match depends on _get_tuner_state_dict_key_prefixes, which derives its prefixes from parameters and buffers only. SHiRA keeps its indices in a plain dict on the layer (src/peft/tuners/shira/layer.py:62), so those keys have no structural prefix, and the other adapters' entries (…shira_indices.default2, …shira_indices.efaul, …) were kept: saving with selected_adapters=["default"] wrote 6 keys instead of 2.

The fix restores the suffix check next to the positional one (src/peft/utils/save_and_load.py:99) — a key is dropped if it ends with an unwanted adapter name or if the segment after a tuner prefix is one. The suffix form cannot reintroduce #3584: a key like …mlp.lora_A.weight does not end with .mlp, and the infix substring match that caused that bug stays removed.

Verification at e138e585:

  • SHiRA probe: 2 expected keys, 2 saved, no extras — the same as base db7d68a2; on ff37e98f the same probe wrote 6.
  • test_save_pretrained_adapter_name_substring: 325 passed, 6 skipped (was 5 failed, 320 passed).
  • tests/test_shira.py 11 passed; TestAdapterNameCollisionFiltering 6 passed.
  • Full tests/test_initialization.py: 350 passed, 3 failed — all in TestFrodInitialization, which fail identically on base db7d68a2.
  • make quality clean.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

save_pretrained writes an empty/truncated adapter checkpoint (silently) when another adapter name equals a base-model module path component

3 participants