unsloth/tests/test_uninitialized_position_ids.py
Daniel Han 18f8869829
Load DeepSeek-OCR and other VLMs that register AutoModel in auto_map (#6421)
* Load repo-code VLMs that register AutoModel in auto_map

FastModel.from_pretrained already falls back from the VLM auto class to
AutoModelForCausalLM for repo-code VL models that register only that class
in their auto_map (e.g. Nemotron-VL). Models like DeepSeek-OCR and
DeepSeek-OCR-2 instead register their architecture under AutoModel, so they
fell through to AutoModelForImageTextToText and raised "Unrecognized
configuration class ... for AutoModelForImageTextToText".

Generalize the guard: when neither vision auto class is registered, fall
back to whichever generic auto class the repo actually registered
(AutoModelForCausalLM, else AutoModel).

* Do not hard-error on a newly initialized position_ids buffer

RaiseUninitialized turns transformers' "some weights of ... were not
initialized" warning into a hard error. position_ids is a deterministic
arange buffer that transformers itself lists in
_keys_to_ignore_on_load_missing, so re-initializing it is correct rather
than a sign of a corrupt checkpoint. Some VLMs (e.g. DeepSeek-OCR) ship it
non-persistently, which tripped the guard. Allowlist position_ids alongside
the existing classifier/predictions head weights.

* Only ignore missing-weight records that are exclusively position_ids

The previous substring check skipped the whole "Some weights of ..." record
whenever position_ids appeared anywhere in it. Transformers reports every
missing key in one record, so a corrupt or incompatible checkpoint missing a
real parameter could load with randomly initialized weights as long as one
missing key contained position_ids. Parse the "newly initialized: [...]" list
and suppress only when every listed key is a position_ids buffer; otherwise
raise as before.

* Match the concrete VLM auto class name when checking auto_map

Transformers resolves remote code by the exact auto class name being called,
and AutoModelForVision2Seq aliases to AutoModelForImageTextToText on
transformers >= 5. Checking for both spellings treated a config that only
registers the legacy AutoModelForVision2Seq key as having a supported VLM
class, skipping the AutoModelForCausalLM fallback that used to load it and
failing as an unrecognized config under AutoModelForImageTextToText. Match
only the concrete class name we would actually pass, keeping the AutoModel
and AutoModelForCausalLM fallbacks.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Preserve VLM mode on the vLLM path when falling back to AutoModel

A repo-code VLM that registers only AutoModel or AutoModelForCausalLM (DeepSeek-OCR, Nemotron-VL) routes to that generic class, so is_vlm, derived from the resolved auto class, is False. That is correct for processor selection (these repos ship no AutoProcessor) but wrong for the vLLM path, where is_vision_model=is_vlm made vLLM treat a vision_config model as text-only and skip the VLM guard and conversion.

Add is_vlm_config, derived from the config vision_config (and gated on not text_only so a text-only resolve still wins), and use it for the fast_inference VLM guard and the is_vision_model flags passed to load_vllm, get_vllm_state_dict and convert_vllm_to_huggingface. Processor selection still uses is_vlm, so DeepSeek-OCR keeps loading via its tokenizer. DeepSeek-OCR with fast_inference now raises the clear 'Fast inference is only supported for ...' error instead of being mishandled as text-only.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-18 07:03:43 -07:00

76 lines
2.5 KiB
Python

"""RaiseUninitialized must ignore a checkpoint that only re-initializes deterministic
position_ids buffers, but still raise when a real weight is missing -- even if the same
HF record also lists a benign position_ids buffer.
"""
from __future__ import annotations
import logging
import pytest
from unsloth.models._utils import (
_all_missing_keys_are_position_ids,
_RaiseUninitialized,
)
_TEMPLATE = (
"Some weights of DeepseekOCRForCausalLM were not initialized from the model "
"checkpoint at unsloth/DeepSeek-OCR and are newly initialized: {keys}\n"
"You should probably TRAIN this model on a down-stream task."
)
def _record(keys_repr: str) -> logging.LogRecord:
return logging.LogRecord(
name = "transformers.modeling_utils",
level = logging.WARNING,
pathname = "modeling_utils.py",
lineno = 1,
msg = _TEMPLATE.format(keys = keys_repr),
args = None,
exc_info = None,
)
@pytest.mark.parametrize(
"keys_repr, expected",
[
("['model.vision_model.embeddings.position_ids']", True),
(
"['model.vision_model.embeddings.position_ids', "
"'vision_model.encoder.layers.0.position_ids']",
True,
),
# A real missing weight alongside position_ids must NOT be suppressed.
(
"['model.vision_model.embeddings.position_ids', 'model.layers.5.mlp.weight']",
False,
),
("['model.layers.5.mlp.weight']", False),
("[]", False),
],
)
def test_all_missing_keys_are_position_ids(keys_repr, expected):
assert _all_missing_keys_are_position_ids(_TEMPLATE.format(keys = keys_repr)) is expected
def test_emit_suppresses_position_ids_only_record():
# A record listing only position_ids buffers loads cleanly (no raise).
handler = _RaiseUninitialized()
handler.emit(_record("['model.vision_model.embeddings.position_ids']"))
def test_emit_raises_when_real_weight_missing_alongside_position_ids():
# The core fix: one benign position_ids key must not mask a real missing weight.
handler = _RaiseUninitialized()
with pytest.raises(Exception, match = "some weights are not initialized"):
handler.emit(
_record("['model.vision_model.embeddings.position_ids', 'model.layers.5.mlp.weight']")
)
def test_emit_raises_on_real_missing_weight():
handler = _RaiseUninitialized()
with pytest.raises(Exception, match = "some weights are not initialized"):
handler.emit(_record("['model.layers.5.mlp.weight']"))