* tests: pinned-symbol canary for unsloth-zoo save_pretrained_merged guards (#5410)
unsloth#5410 was a class of silent-write bug in the
save_pretrained_merged path that the existing CI matrix could not
detect because the merge-helper tests were not wired through the
upstream-drift suite. The full fix lives in unslothai/unsloth-zoo#647
(layout-aware MoE merge helpers, authoritative num_experts resolver,
loud-fail counter, generation_config.json save). This PR adds the
unsloth-side canary that watches for the four guards staying in place
in unsloth-zoo so a future refactor cannot silently regress them.
tests/version_compat/test_unsloth_zoo_save_merged_pinned_symbols.py
fetches unsloth_zoo/saving_utils.py + tests/test_unsloth_zoo_lora_merge.py
from unslothai/unsloth-zoo:main and asserts:
- _MOE_MERGE_STATE / _reset_moe_merge_state / _record_moe_merge_fallback
are still defined and a `raise RuntimeError(...MoE...)` still fires
when fallback > 0.
- _detect_moe_lora_layout exists and both "swapped" / "standard" branch
labels are reachable in the source.
- _resolve_num_experts_from_lora_stats is present AND its base_layer
walk is bounded by `for _ in range(N):` (a cyclic ParamWrapper chain
must not hang the merge).
- merge_and_overwrite_lora still calls
model.generation_config.save_pretrained(...).
- tests/test_unsloth_zoo_lora_merge.py keeps the six PEFT 0.19+
standard-layout regression tests added in #647.
- Local unsloth/save.py still names save_pretrained_merged and
routes through merge_and_overwrite_lora (i.e. the entry point still
reaches the upstream fix).
While #647 is still open, the four symbol tests SKIP cleanly with a
message naming #647. When #647 merges into unsloth-zoo main, the same
tests automatically become hard gates and catch any future regression.
The sixth test (local entry-point grep) passes today.
CPU-only static fetch, ~0.1s. Wired into the existing peft-pinned-symbols
job in .github/workflows/version-compat-ci.yml so it runs on every PR
that touches unsloth/** and on the daily schedule.
Local run: 1 passed, 5 skipped (expected; #647 open).
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* tests/version_compat: relax MoE/generation_config regex to fit zoo#647
zoo#647 landed two layout changes that broke the pinned-symbol
canary's exact-string regex matches but kept the underlying
guarantees intact:
- The post-loop MoE LoRA fallback `raise RuntimeError(...)` wraps
the "MoE" wording onto a second line; the old `[^\n]*` did not
cross newlines. Switch to `.*?` + re.DOTALL.
- The generation_config save now binds the attr to a local var
`gen_cfg = getattr(model, "generation_config", ...)` and calls
`gen_cfg.save_pretrained(save_directory)`, so a literal
`generation_config.save_pretrained(` substring no longer matches.
Anchor on the conceptual operation: a `generation_config` mention
followed (within a small char window) by a `.save_pretrained(`
call. That is what the canary actually cares about.
Verified locally:
pytest tests/version_compat/test_unsloth_zoo_save_merged_pinned_symbols.py
-> 2 passed (4 deselected)
---------
Co-authored-by: Daniel Han-Chen <info@unsloth.ai>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>