From dc4618ce475554d99fab76daae9746baa62bf090 Mon Sep 17 00:00:00 2001 From: Vineeth Sai Date: Wed, 8 Jul 2026 13:40:39 -0700 Subject: [PATCH] Fix duplicate unsloth/gemma-2b-bnb-4bit mapper key routing the base 4bit repo to the instruct model (#6891) --- .github/workflows/consolidated-tests-ci.yml | 1 + tests/test_gemma_2b_mapper_key.py | 46 +++++++++++++++++++++ unsloth/models/mapper.py | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/test_gemma_2b_mapper_key.py diff --git a/.github/workflows/consolidated-tests-ci.yml b/.github/workflows/consolidated-tests-ci.yml index 6ff3d19ba..1bb4c2bb5 100644 --- a/.github/workflows/consolidated-tests-ci.yml +++ b/.github/workflows/consolidated-tests-ci.yml @@ -366,6 +366,7 @@ jobs: tests/python/test_fast_language_model_text_only.py \ tests/test_bad_mappings_redirect.py \ tests/test_prefetch_snapshot_scope.py \ + tests/test_gemma_2b_mapper_key.py \ --deselect 'tests/utils/test_attention_masks.py::test_run_attention_flash_varlen_receives_window_and_softcap' # The deselected test monkeypatches flash_attn_varlen_func, which is # only bound on the module when `flash_attn` is importable. flash_attn diff --git a/tests/test_gemma_2b_mapper_key.py b/tests/test_gemma_2b_mapper_key.py new file mode 100644 index 000000000..31edacfce --- /dev/null +++ b/tests/test_gemma_2b_mapper_key.py @@ -0,0 +1,46 @@ +"""Regression test for the duplicate ``unsloth/gemma-2b-bnb-4bit`` key in +``unsloth/models/mapper.py``. + +The 4bit instruction-tuned Gemma 2B entry was accidentally keyed with the base +model's repo name, so ``__INT_TO_FLOAT_MAPPER`` held two identical +``unsloth/gemma-2b-bnb-4bit`` keys. Python keeps only the last value for a +duplicate literal key, so the base 4bit repo resolved to the *instruct* model, +the base model lost its reverse (4x-faster) mapping, and +``unsloth/gemma-2b-it-bnb-4bit`` was never registered at all. + +``mapper.py`` has no imports, so we exec it directly and inspect the built +mappers without importing ``unsloth`` (which requires a GPU). +""" + +import os + +MAPPER_PATH = os.path.join(os.path.dirname(__file__), os.pardir, "unsloth", "models", "mapper.py") + + +def _load_mappers(): + with open(MAPPER_PATH) as f: + source = f.read() + namespace = {} + exec(compile(source, MAPPER_PATH, "exec"), namespace) + return namespace + + +def test_gemma_2b_base_and_instruct_4bit_are_distinct(): + namespace = _load_mappers() + int_to_float = namespace["INT_TO_FLOAT_MAPPER"] + float_to_int = namespace["FLOAT_TO_INT_MAPPER"] + + # The base 4bit repo must resolve to the base model, not the instruct one. + assert int_to_float["unsloth/gemma-2b-bnb-4bit"] == "unsloth/gemma-2b" + + # The instruct 4bit repo must be registered and resolve to the instruct model. + assert "unsloth/gemma-2b-it-bnb-4bit" in int_to_float + assert int_to_float["unsloth/gemma-2b-it-bnb-4bit"] == "unsloth/gemma-2b-it" + + # The base model must reverse-map back to the base 4bit repo. + assert float_to_int["unsloth/gemma-2b"] == "unsloth/gemma-2b-bnb-4bit" + assert float_to_int["google/gemma-2b"] == "unsloth/gemma-2b-bnb-4bit" + + # The instruct model must reverse-map to the instruct 4bit repo. + assert float_to_int["unsloth/gemma-2b-it"] == "unsloth/gemma-2b-it-bnb-4bit" + assert float_to_int["google/gemma-2b-it"] == "unsloth/gemma-2b-it-bnb-4bit" diff --git a/unsloth/models/mapper.py b/unsloth/models/mapper.py index 57c1e292c..f3a0e1f9b 100644 --- a/unsloth/models/mapper.py +++ b/unsloth/models/mapper.py @@ -134,7 +134,7 @@ __INT_TO_FLOAT_MAPPER = \ "unsloth/gemma-7b-it", "google/gemma-7b-it", ), - "unsloth/gemma-2b-bnb-4bit" : ( + "unsloth/gemma-2b-it-bnb-4bit" : ( "unsloth/gemma-2b-it", "google/gemma-2b-it", ),