fix(save): unsloth_push_to_hub_gguf(save_method="lora") raises NameError (#7193)

* fix(save): unsloth_push_to_hub_gguf(save_method="lora") raises NameError

unsloth_push_to_hub_gguf reads is_main_process at save.py:3181 but never
declares it. Its twin unsloth_save_pretrained_gguf declares it (2783) and
uses it the same way (2839) -- the LoRA branch was copied between the twins,
the parameter it depends on was not. There is no module-level global, so the
name resolves as a global load and the branch raises NameError 100% of the
time.

save_pretrained_gguf(save_method="lora", push_to_hub=True) raises a
ValueError that tells users to "use .push_to_hub_gguf(save_method='lora')
instead" -- the documented escape hatch is the broken call.

Add is_main_process to the signature, positioned as in the twin, and forward
it to unsloth_save_pretrained_gguf on the merged path so the parameter is not
silently ignored there. Default stays True, so nothing changes for existing
callers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(save): preserve GGUF push compatibility

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Etherll <61019402+Etherll@users.noreply.github.com>
This commit is contained in:
Andrew Chen 2026-07-17 16:55:34 +08:00 committed by GitHub
commit b508c8fe89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 80 additions and 2 deletions

View file

@ -8,6 +8,8 @@ regressions that pure AST checks cannot (e.g. wrong scheme/suffix/outtype passed
from __future__ import annotations
import inspect
import pytest
import unsloth.save as save_mod
@ -126,6 +128,80 @@ def test_gguf_lora_push_to_hub_is_rejected(tmp_path):
)
# The above rejection points users at push_to_hub_gguf(save_method='lora'), so that path
# has to work; it is only ever exercised here.
def test_push_to_hub_gguf_lora_dispatches(monkeypatch):
seen = {}
monkeypatch.setattr(
save_mod,
"_unsloth_save_lora_gguf",
lambda model, tok, sd, **kw: seen.update(kw),
)
save_mod.unsloth_push_to_hub_gguf(
_FakeModel(),
"repo/id",
tokenizer = object(),
save_method = "lora",
quantization_method = "q8_0",
)
assert seen.get("outtype") == "q8_0"
assert seen.get("push_to_hub") is True
def test_push_to_hub_gguf_lora_skips_non_main_process(monkeypatch):
calls = []
monkeypatch.setattr(
save_mod,
"_unsloth_save_lora_gguf",
lambda *a, **kw: calls.append(kw),
)
result = save_mod.unsloth_push_to_hub_gguf(
_FakeModel(),
"repo/id",
tokenizer = object(),
save_method = "lora",
is_main_process = False,
)
assert result is None
assert calls == []
def test_push_to_hub_gguf_skips_non_main_process_before_merged_conversion(monkeypatch):
calls = []
monkeypatch.setattr(
save_mod,
"unsloth_save_pretrained_gguf",
lambda **kw: calls.append(kw),
)
result = save_mod.unsloth_push_to_hub_gguf(
_FakeModel(),
"repo/id",
tokenizer = object(),
is_main_process = False,
)
assert result is None
assert calls == []
def test_push_to_hub_gguf_preserves_positional_max_shard_size():
bound = inspect.signature(save_mod.unsloth_push_to_hub_gguf).bind(
_FakeModel(),
"repo/id",
object(),
"q4_k_m",
None,
None,
None,
None,
"token",
"50GB",
)
assert bound.arguments["max_shard_size"] == "50GB"
assert "is_main_process" not in bound.arguments
# -- torchao PTQ / QAT dispatch ------------------------------------------------------------