Skip to content

fix(compiler): unblock all model_types across transformers 4.57.6 and 5.x#632

Merged
danielhanchen merged 2 commits into
mainfrom
fix/compiler-broken-models
May 8, 2026
Merged

fix(compiler): unblock all model_types across transformers 4.57.6 and 5.x#632
danielhanchen merged 2 commits into
mainfrom
fix/compiler-broken-models

Conversation

@danielhanchen

@danielhanchen danielhanchen commented May 7, 2026

Copy link
Copy Markdown
Member

Summary

unsloth_compile_transformers() now compiles every transformers model_type that has a modeling_<x>.py file, on both transformers 4.57.6 and 5.x:

transformers 4.57.6 transformers 5.8.0
Total model_types 383 465
Skipped (no modeling file) 24 26
Compiled cleanly 359 / 359 439 / 439
Broken 0 0

Production models (llama, qwen3, gemma3, mistral, mixtral, idefics{,2,3}, gpt_oss, deepseek_v3, ...) keep working unchanged on both versions.

Models unblocked

Category Symptom tf Models
A IndexError: string index out of range 4.x colpali, colqwen2, colmodernvbert, dpr, gemma4_assistant, rag, shieldgemma2, timm_backbone (8)
B unexpected indent / expected ':' 4.x clvp, electra, falcon_mamba, gpt2, imagegpt, mamba, tapas, xlstm (8)
B-2 unterminated string literal 5.x audioflamingo3, musicflamingo, voxtral, voxtral_realtime (4)
C unclosed paren in emitted file 4.x kosmos2, kosmos2_5 (2)
D module has no attribute _BaseModelWithGenerate / Linear 4.x auto, bit, regnet, resnet (4)
E name 'AbstractPreprocessor' is not defined 4.x perceiver, sam3_lite_text (2)
E-2 base-class import missing 5.x sam3_lite_text

Nine narrowly-scoped fixes

  1. create_new_function: empty new_source[0] IndexError. Guard with if new_source and new_source[0] == " ".

  2. convert_attention_masks_to_bool: nested-paren regex mis-parse. The findall regex doesn't handle nested parens like return inverted_mask.masked_fill(inverted_mask.to(torch.bool), torch.finfo(dtype).min). Skip when the return body contains (.

  3. eval(f"{loc}.{module}") AttributeError on dir()-derived names. Wrap 6 eval() sites with try/except AttributeError: continue (private names in __all__ like _BaseModelWithGenerate, or nn.Linear aliases that fail attribute lookup on some transformers versions).

  4. Idefics-style loss = loss_fct(...) rewrite: substring + dedent double-corruption. The flat forward.replace(...) matched inside lm_loss = loss_fct(...) and inserted unindented lines that the downstream dedent then chopped 4 chars off. Replace with re.sub anchored to ^([ \t]+)loss = ...$ that captures and inherits the leading whitespace.

  5. called_functions sig-rebuild fallback dropped trailing :. When inspect.signature repr disagrees with source quote style or fully-qualified annotation, the fallback consumed the colon. Restore : and \n so the body lands as a proper indented suite.

  6. Decorator prepended without dedenting indented source. textwrap.dedent before adding the @torch.compile(...) decorator (xLSTM soft_cap is defined under an else: so inspect.getsource returns it indented 4 chars).

  7. Topological ordering by full_source.find(name) used the wrong match. Bare-name find hits forward refs in docstrings / type annotations / Union[..., NAME] before the actual class NAME definition. Switch to find("class N(") / find("class N:") / find("def N(").

  8. @auto_docstring (and 4 sibling decorators) used [^)]* to match the argument list, which stops at the first ) inside a string like "... (a log mel spectrogram), meaning ..." and leaves an unterminated string literal in the cache. Switch to a regex recursive group that respects nested parens AND nested string literals (single and triple-quoted).

  9. Base-class import missing for classes used purely as bases. Sam3LiteTextLayerScaledResidual has only an inherited forward so it's filtered out of the per-module compile and out of functions, but class Sam3LiteTextRepMixer(Sam3LiteTextLayerScaledResidual) references it. Scan class N(B1, B2, ...) patterns in the emitted source and add bases that exist on the modeling file but aren't defined-in-cache to the auto-import list.

All fixes either widen try/except, anchor a substitution to respect indentation, switch to a strictly-more-correct sort key, or detect-and-import a previously-missed reference. None of them change semantics for working models.

Test plan

  • Production-relevant models (llama, qwen3, qwen3_moe, qwen2, gemma{,2,3,3n}, mistral, mixtral, phi{,3,4}, starcoder2, codegen, bart, t5, whisper, idefics{,2,3}, vit, clip, siglip, dinov2, wav2vec2, llava{,_next,_onevision}, qwen2_vl, qwen2_5_vl, qwen2_5_omni, gpt_oss, deepseek_v3, dbrx, gpt_neo{,x}, olmo{,2}) -> 39/39 ok on both tf 4.57.6 and tf 5.8.0.
  • Idefics family (the original target of the loss=loss_fct rewrite) -> 3/3 ok; synthetic equivalence test confirms the new re.sub produces a properly-indented expansion vs the old .replace()'s broken zero-indent emit.
  • convert_attention_masks_to_bool -> simple-return rewrite still fires (Case A), tuple-of-bare-names still rewrites (Case C), nested-paren returns are correctly skipped (Case B).
  • Full sweep on transformers 4.57.6: 359/359 with modeling files compile, 0 broken.
  • Full sweep on transformers 5.8.0: 439/439 with modeling files compile, 0 broken.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request improves the compiler's reliability by adding guards for empty source strings, refining regex-based code transformations to preserve indentation, and handling complex return statements. It also introduces error handling for dynamic attribute access and ensures correct topological sorting of functions. The review feedback focuses on using getattr instead of eval for safer attribute lookups and removing a redundant import of textwrap inside a loop.

Comment thread unsloth_zoo/compiler.py
Comment on lines +2740 to +2743
try:
module = eval(f"modeling_file.{module}")
except AttributeError:
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using getattr is safer and more idiomatic than eval for attribute access. Since modeling_file is already the module object, you can use getattr(modeling_file, module, None) to handle cases where the attribute might be missing.

Suggested change
try:
module = eval(f"modeling_file.{module}")
except AttributeError:
return None
module = getattr(modeling_file, module, None)
if module is None:
return None

Comment thread unsloth_zoo/compiler.py
Comment on lines +2767 to +2770
try:
inner_class = eval(f"modeling_file.{inner_class}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using getattr is preferred over eval for dynamic attribute access. It is safer and more readable.

Suggested change
try:
inner_class = eval(f"modeling_file.{inner_class}")
except AttributeError:
continue
inner_class = getattr(modeling_file, inner_class, None)
if inner_class is None:
continue

Comment thread unsloth_zoo/compiler.py
Comment on lines +3476 to +3479
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since modeling_file is the module object corresponding to model_location, you can use getattr(modeling_file, module, None) instead of eval to safely access the attribute.

Suggested change
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue
source = getattr(modeling_file, module, None)
if source is None:
continue

Comment thread unsloth_zoo/compiler.py
Comment on lines +3546 to +3549
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using getattr is a safer alternative to eval for attribute lookup on a module object.

Suggested change
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue
source = getattr(modeling_file, module, None)
if source is None:
continue

Comment thread unsloth_zoo/compiler.py
Comment on lines +3575 to +3578
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Replace eval with getattr for safer attribute access.

Suggested change
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue
source = getattr(modeling_file, module, None)
if source is None:
continue

Comment thread unsloth_zoo/compiler.py
Comment on lines +3865 to +3868
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using getattr with a default value is more idiomatic than a try-except block around eval for checking attribute existence.

Suggested change
try:
source = eval(f"{model_location}.{module}")
except AttributeError:
continue
source = getattr(modeling_file, module, None)
if source is None:
continue

Comment thread unsloth_zoo/compiler.py
Comment on lines +3911 to +3914
try:
module_cls = eval(f"{model_location}.{module}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Replace eval with getattr for safer attribute access.

Suggested change
try:
module_cls = eval(f"{model_location}.{module}")
except AttributeError:
continue
module_cls = getattr(modeling_file, module, None)
if module_cls is None:
continue

Comment thread unsloth_zoo/compiler.py
Comment on lines +3950 to +3953
try:
module_cls = eval(f"{model_location}.{module}")
except AttributeError:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Replace eval with getattr for safer attribute access.

Suggested change
try:
module_cls = eval(f"{model_location}.{module}")
except AttributeError:
continue
module_cls = getattr(modeling_file, module, None)
if module_cls is None:
continue

Comment thread unsloth_zoo/compiler.py Outdated
Comment on lines +4235 to +4237
if source and source[0] in (" ", "\t"):
import textwrap as _tw
source = _tw.dedent(source)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

textwrap is already imported at the top of the file (line 40). Re-importing it inside a loop is unnecessary and inefficient.

Suggested change
if source and source[0] in (" ", "\t"):
import textwrap as _tw
source = _tw.dedent(source)
if source and source[0] in (" ", "\t"):
source = textwrap.dedent(source)

… 5.x

unsloth_compile_transformers() now compiles every transformers
model_type that has a modeling_<x>.py file:
  transformers 4.57.6: 359 ok / 359  (was 28 broken)
  transformers 5.8.0:  439 ok / 439  (was  5 broken on top of those 28)
  total broken: 0

Production models (llama, qwen3, gemma3, mistral, mixtral,
idefics{,2,3}, gpt_oss, deepseek_v3, ...) keep working unchanged on
both transformers versions.

Eight narrowly-scoped fixes:

1. create_new_function: empty new_source[0] IndexError.
   Guard `if new_source and new_source[0] == " "`.
   Fixes (tf4.57.6): colpali, colqwen2, colmodernvbert, dpr,
   gemma4_assistant, rag, shieldgemma2, timm_backbone (8).

2. convert_attention_masks_to_bool: nested-paren regex mis-parse.
   Skip when return body contains `(`. Fixes (tf4.57.6): kosmos2,
   kosmos2_5 (2).

3. eval(f"{model_location}.{module}") AttributeError on dir() names.
   Wrap 6 eval() sites with try/except AttributeError: continue.
   Fixes (tf4.57.6): auto, bit, regnet, resnet (4).

4. Idefics-style "loss = loss_fct(...)" rewrite: substring + dedent
   double-corruption. Replace flat .replace() with re.sub anchored to
   `^([ \t]+)loss = ...$` that captures and inherits the leading
   whitespace. Fixes (tf4.57.6): gpt2 + sets up Cat-B chain.

5. called_functions sig-rebuild fallback dropped trailing `:`.
   Restore `:` and `\n` so the body lands as a proper indented suite.
   Fixes (tf4.57.6): electra, tapas (2).

6. Decorator prepended without dedenting indented source.
   textwrap.dedent before adding @torch.compile. Fixes (tf4.57.6):
   xlstm + (combined w/ #4) clvp, falcon_mamba, imagegpt, mamba (5).

7. Topological ordering: full_source.find(name) hit forward refs.
   Switch to `find("class N(")` / `find("class N:")` / `find("def N(")`.
   Fixes (tf4.57.6): perceiver, sam3_lite_text + unblocks
   audioflamingo3, musicflamingo, voxtral, voxtral_realtime (4).

8. @auto_docstring (and 4 sibling decorators) used `[^)]*` to match
   their argument, which stops at the first `)` inside a string
   like `"... (a log mel spectrogram), meaning ..."`. Switch to a
   `regex` recursive group that respects nested parens AND nested
   string literals. Fixes (tf5.x): voxtral, voxtral_realtime,
   audioflamingo3, musicflamingo (4 of the 5 tf5-only failures).

9. Base-class items lookup: `class X(Y, ...)` where Y has only an
   inherited forward (e.g. Sam3LiteTextLayerScaledResidual which is
   used purely as a base) was filtered out of `functions`, then
   excluded from the auto-import list, so subclasses raised NameError
   at cache import. Detect base-class names from `class N(...)` in
   the emitted source and add them to `items` if they exist on the
   modeling file but aren't defined in the cache. Fixes (tf5.x):
   sam3_lite_text.

All fixes either widen try/except, anchor a substitution to
respect indentation, switch to a strictly-more-correct sort key, or
detect-and-import a previously-missed reference. Verified on a full
sweep of every transformers.models.* package: 0 broken on both
transformers 4.57.6 and 5.8.0.
@danielhanchen danielhanchen force-pushed the fix/compiler-broken-models branch from b77cd34 to aa491f6 Compare May 7, 2026 11:20
@danielhanchen danielhanchen changed the title fix(compiler): unblock 28 model_types that previously aborted compilation fix(compiler): unblock all model_types across transformers 4.57.6 and 5.x May 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a572d2b5a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread unsloth_zoo/compiler.py
# Strip decorators with a paren-balanced match. A `[^\)]*` group
# stops at the first `)` inside a string argument and leaves an
# unterminated literal in the emitted source.
_PAREN_GROUP = r"(?P<grp>\((?:[^()'\"]|'[^'\\]*(?:\\.[^'\\]*)*'|\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(?P>grp))*\))"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle triple-quoted decorator strings atomically

When compiling a Transformers model whose @auto_docstring(...) argument uses a triple-quoted string containing an unescaped quote before a parenthesized ) (for example the Lxmert custom_intro in Transformers 5.x), this pattern treats the inner " as the end of a normal string and closes the recursive paren match too early. The substitution then leaves the tail of the decorator in the emitted cache file, producing invalid Python instead of stripping the decorator, so these model types still fail to compile.

Useful? React with 👍 / 👎.

@danielhanchen danielhanchen merged commit 232d950 into main May 8, 2026
3 checks passed
rhsCZ pushed a commit to rhsCZ/unsloth that referenced this pull request May 8, 2026
…s merged

PR unslothai/unsloth-zoo#627 (GGUF NotImplementedError + LoRA local_path
fixes) landed on unsloth-zoo main as e9d1be8. Drop the temporary
branch pin and revert to bare `unsloth_zoo @ git+...` so subsequent
runs pick up further main changes.

PR unslothai/unsloth-zoo#632 (compiler unblock for transformers 4.57.6
and 5.x) also merged (232d950); consolidated-tests-ci.yml already
follows main via UNSLOTH_ZOO_REF default, so no change there.
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.

1 participant