Skip to content

Studio: keep training from failing when a namespace-package shadows unsloth#6269

Merged
danielhanchen merged 4 commits into
mainfrom
studio-training-namespace-shadow-guard
Jun 15, 2026
Merged

Studio: keep training from failing when a namespace-package shadows unsloth#6269
danielhanchen merged 4 commits into
mainfrom
studio-training-namespace-shadow-guard

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Problem

Starting a training run can fail before it begins with:

Failed to import ML libraries: cannot import name 'FastLanguageModel' from 'unsloth' (unknown location)

The traceback ends at the worker's from unsloth import FastLanguageModel in core/training/trainer.py.

Root cause

(unknown location) means import unsloth resolved to a namespace package: a directory named unsloth that has no top-level __init__.py (a stray checkout, a partial clone, or a PYTHONPATH entry that happens to contain one). The path finder returns that directory as a namespace package, which of course has no FastLanguageModel.

A normal pip install unsloth (a regular package in site-packages) always wins this race: the path finder keeps scanning and returns the real package even when a namespace directory appears earlier on the path. The failure only happens when unsloth is reachable solely through a meta path finder that runs after the standard path finder, i.e. a source / editable (PEP 660) install, combined with a stray unsloth directory on sys.path. The training worker is spawned with the parent's sys.path and PYTHONPATH, so it inherits any such directory.

Fix

Before the worker imports unsloth, run a small guard that:

  • checks whether unsloth / unsloth_zoo currently resolve to a namespace shadow,
  • if so, drops only the sys.path entries that hold a shadow directory (one without __init__.py),
  • imports the real packages (so they are bound and cached),
  • restores sys.path so any other modules living on those entries keep importing.

It is a no-op when unsloth already resolves to a real package, so normal installs are unaffected.

Testing

  • Reproduced the failure with a source/editable install plus a stray namespace unsloth directory on PYTHONPATH; confirmed from unsloth import FastLanguageModel fails before the guard and succeeds after it, resolving to the real package, with sys.path restored.
  • Unit-style checks for: recovery when the real package is only reachable via a post-path-finder meta path finder (the editable case), a pure no-op for a healthy install, sys.path restoration on both success and failure, and a clear ModuleNotFoundError when no real package exists at all.
  • py_compile on the changed file.

…of unsloth

A directory named unsloth without an __init__.py on sys.path (a stray checkout,
a partial clone, or a polluted PYTHONPATH) makes the path finder return a
namespace package, so the worker's 'from unsloth import FastLanguageModel' fails
with 'cannot import name ... (unknown location)'. A normal site-packages install
always wins this race, so only source/editable installs are exposed. Before the
import, drop the offending sys.path entries, bind the real packages, then
restore sys.path so other modules on those entries keep importing. It is a
no-op when unsloth already resolves to a real package.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@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: 82bd9f5adc

ℹ️ 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".

sys.path[:] = saved


_ensure_real_packages("unsloth_zoo", "unsloth")

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 Avoid pre-importing unsloth_zoo before Unsloth's guards

When the namespace shadow is for unsloth_zoo (for example an editable/source install plus a stray unsloth_zoo directory), this call imports unsloth_zoo directly inside _ensure_real_packages before unsloth.__init__ reaches _gpu_init. _gpu_init deliberately runs fixes before its first import unsloth_zoo (see unsloth/_gpu_init.py lines 69-79 and 103-139), including ROCm bitsandbytes and cache setup, so the recovery path can import zoo with those guards skipped and fail on the same ROCm/Windows cases those fixes handle. Let the guard unshadow paths and import unsloth, allowing unsloth to import zoo after its setup.

Useful? React with 👍 / 👎.

danielhanchen and others added 2 commits June 15, 2026 11:56
_ensure_real_packages imported the requested names in argument order, so a
unsloth_zoo namespace shadow made it import unsloth_zoo directly before
unsloth. That skips unsloth.__init__ -> _gpu_init, which runs its ROCm and
Windows bitsandbytes fixes before its own import unsloth_zoo, so the recovery
path could import zoo with those guards skipped and fail on the ROCm/Windows
cases those fixes handle.

Import parent-first via reversed(names) so unsloth is imported first and pulls
in the real unsloth_zoo after _gpu_init has run; the later cached import is a
no-op. This also covers the case where only unsloth_zoo is shadowed: the bad
sys.path entry is still dropped and unsloth owns the zoo import. Detection,
sys.path pruning, shadow-cache clearing, and restoration are unchanged.

Add tests/test_namespace_shadow_guard_pr6269.py: CPU-only subprocess scenarios
(only zoo shadowed, both shadowed, only unsloth shadowed, healthy no-op, real
package absent, multiple shadow entries) that assert unsloth imports before
unsloth_zoo and that sys.path is restored.

@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: 8e5c3e5cc8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if not bad:
return
saved = list(sys.path)
sys.path[:] = [e for e in sys.path if e not in bad]

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 Avoid removing the real unsloth path with the zoo shadow

When a single sys.path entry contains a valid source checkout for unsloth and a namespace-only unsloth_zoo directory (for example running Studio from an Unsloth checkout while unsloth_zoo is supplied by an editable/meta-path install), this removes that whole entry to hide the zoo namespace. The later importlib.import_module("unsloth") then runs while the only path to the real unsloth package is gone, so the guard turns that source/editable setup into ModuleNotFoundError instead of recovering from the zoo shadow. Consider only hiding the shadowed package path or deferring the unsloth import until its source path is available.

Useful? React with 👍 / 👎.

…w guard

Move importlib.invalidate_caches() inside the try/finally so a failure there
still restores sys.path, and tighten the import-order comment. Add a test that
forces invalidate_caches to raise and asserts sys.path is restored.
@danielhanchen danielhanchen merged commit f297593 into main Jun 15, 2026
28 of 31 checks passed
@danielhanchen danielhanchen deleted the studio-training-namespace-shadow-guard branch June 15, 2026 13:06
rhsCZ pushed a commit to rhsCZ/unsloth that referenced this pull request Jun 21, 2026
… workers

A directory named `unsloth` (or `unsloth_zoo`) without an __init__.py on
PYTHONPATH/sys.path, a stray source checkout or a polluted PYTHONPATH, makes
`import unsloth` resolve to an empty namespace package, so a worker's
`from unsloth import FastLanguageModel` dies with a cryptic
"cannot import name ... (unknown location)".

The LLM training path already recovered from this via `_ensure_real_packages`
in trainer.py (PR unslothai#6269), but the inference, export, and embedding-training
subprocesses imported Unsloth directly with no guard. Extract that helper into
a shared, dependency-free core/import_guards.py and call it before the Unsloth
import in every subprocess: it drops the offending sys.path entries, imports
the real packages (unsloth before unsloth_zoo so the pre-zoo GPU fixes run),
then restores sys.path. trainer.py now imports the shared helper instead of its
local copy.

Covers both unsloth and unsloth_zoo and both namespace origin forms (None and
"namespace"). The existing PR unslothai#6269 test now exercises the shared helper.
danielhanchen added a commit that referenced this pull request Jun 22, 2026
…ages (#6532)

* Studio: self-heal unsloth namespace-package shadows in all subprocess workers

A directory named `unsloth` (or `unsloth_zoo`) without an __init__.py on
PYTHONPATH/sys.path, a stray source checkout or a polluted PYTHONPATH, makes
`import unsloth` resolve to an empty namespace package, so a worker's
`from unsloth import FastLanguageModel` dies with a cryptic
"cannot import name ... (unknown location)".

The LLM training path already recovered from this via `_ensure_real_packages`
in trainer.py (PR #6269), but the inference, export, and embedding-training
subprocesses imported Unsloth directly with no guard. Extract that helper into
a shared, dependency-free core/import_guards.py and call it before the Unsloth
import in every subprocess: it drops the offending sys.path entries, imports
the real packages (unsloth before unsloth_zoo so the pre-zoo GPU fixes run),
then restores sys.path. trainer.py now imports the shared helper instead of its
local copy.

Covers both unsloth and unsloth_zoo and both namespace origin forms (None and
"namespace"). The existing PR #6269 test now exercises the shared helper.

* Studio: distinguish a failed model load from no model in the attach gates

A failed load never sets the checkpoint, so the image and audio attach gates
fell through to "Load a model before adding images/audio", which reads as if
the user simply forgot to pick a model rather than that the load errored. Add a
dedicated lastModelLoadError to the chat runtime store, set only when an actual
load attempt fails (not on refresh, list, status, or unload errors, which keep
using modelsError) and cleared when the next load starts. The image gate (all
three call sites) and the audio gate now use it to report a failed load and
point at the server logs, while still blocking in exactly the same cases.

* Tighten namespace-shadow guard and load-error comments
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