* Trim comments in tests, scripts and CI helpers
Comment-only pass over 453 changed files under tests/ (excluding the
studiobench harness), scripts/ and .github/scripts/.
17,749 -> 10,635 comment lines (-40.1%)
1,183,079 -> 757,190 comment characters (-36.0%)
Distinctive-fact retention against the base commit: 93.2%. A sample of 100
restoration and deletion decisions was judged by hand: 98 correct.
No comment line exceeds 120 physical characters. Nothing outside comments
changes: no code, docstrings, string literals, licence headers or pragmas.
Verified with an AST code-only signature of every file against the base.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Put the moved comments back on the code they describe
The signal-handler rationale in launch.py had been reattached to the
handler registration loop, which has no try/finally, and its except-branch
note had been split into a fragment starting mid-sentence with the noqa
marker duplicated into the prose. The PORT_FILE launcher comments had been
reattached to the _studio_root_id caching test, which does not touch
PORT_FILE.
* Put the section banners back, and use plain dashes in comments
The trim removed the rule-line comments that index these files. Tests locate a
region by finding its banner and end it on the next one, so a missing banner is
a broken anchor rather than lost decoration. Each is restored above the code
line it preceded at 1d2679f; where that anchor is not unique the banner is left
out rather than guessed at.
Em and en dashes are rewritten as plain dashes inside comment tokens only.
String literals are code and are untouched, so the diff stays comment-only.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Put reattached comments back on the code they describe
The comment pass moved a number of explanatory comments off the code they
were written for and onto unrelated statements nearby, so they now read as
false or as truncated fragments. This restores each one at its correct site:
- lint_duplicate_definitions: the import-key rationale goes back on the
emitted Finding key instead of being half-pasted onto `implicit =`.
- lockfile_supply_chain_audit: "Empty by default" belongs on the empty
CARGO_IOC_STRINGS tuple, not on the non-empty source allowlist; the
missing-lockfile and traceback notes go back on their own branches.
- scan_packages: "archives landed in dest" describes the successful pip
download, not `if not downloaded:`; the backslash-continuation note goes
back on the continuation check in _scan_line_end.
- interrupted_install_probe: the start_new_session/getpgid note belongs on
the posix branch that calls getpgid, not on the win32 branch.
- virustotal_scan: the missing-secret rationale goes back on the skip path
and the :⚠️: note back on the detection print.
- scan_npm_packages: the rmtree cleanup note goes back on the rmtree.
- check_frontend_dep_removal: one per-key rationale per key again, instead
of five concatenated onto browserslist.
- profile_startup: failed-launch and nothing-measured notes swapped back.
- enforce_kwargs_spacing: fold-rejection reasons back on the guards that
reject for them, and the f-string fold comment back in order.
- tests/conftest: the bitsandbytes broken-wheel note back on the
bitsandbytes handler.
Comments only; no code, string literal, docstring or pragma changes.
* Repair the banner restore: drop duplicate headings, keep the blank line
The scripted restore re-added each section banner without removing the
de-bannered plain heading the trim had left behind, so a heading appeared
twice, once ruled and once bare. It also inserted the banner flush against the
next line of code where the base had a blank line after it, which turns a
section separator into a doc comment for whichever def follows.
Both are the same fault the trim itself was being screened for, introduced by
the remediation rather than by the trim.
* Reattach 12 review comments to the code they describe
Each of these was a rationale that ended up on a neighbouring test or
assertion, or a fragment cut mid-sentence, so it described something
other than the line under it.
- test_gguf_disk_preflight: the 3GB-embeddings arithmetic belongs to the
untied case, not the tied one (2GB there).
- lockfile_supply_chain_audit: reassemble the workflow-command escaping
rationale on the skip branch.
- studio_smoke/multi_turn_chat: restore the retry-policy explanation in
main().
- test_virustotal_scan: restore the advisory-posture rationale on the
scan step.
- test_amd_fastpath_probe: return each ROCm rationale to its parameter
row.
- test_cuda_repair: the Blackwell span note goes back on the Blackwell
test; complete the Kepler sentence.
- test_windows_rocm_bnb_version: realign the sitecustomize / opt-out
rationales with their cases.
- test_dataset_num_proc: the Pool(1) note belongs to the low-memory auto
path.
- test_install_whisper_prebuilt_logic: decoy-DLL, libomp, build-bump and
same-ggml notes back on their own tests; the link copy fallback needs
none.
- playwright_loaded_models_indicator: stale-eject notes back on their
sections and the preference banner restored.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Restore the review comments that were cut mid-sentence or reattached to the wrong code
* Restore the comment tails the 120-column pass cut off
The trim enforced the column limit by chopping the physical line rather than
rewrapping it, so a block's last sentence stopped mid-thought and the clause
carrying the reason was gone:
-# ...fails in a second rather than
+# ...fails in a second rather than after the quantise and the multi-gigabyte save.
-# fp8 / mxfp8 need bf16 weights, so skip non-bf16 Linears;
+# fp8 / mxfp8 need bf16 weights, so skip non-bf16 Linears; nvfp4 handles fp32. Mirrors the runtime gate.
883 blocks across 266 files, restored from the merge base and rewrapped at word
boundaries within 120 columns. Blocks that end on proper punctuation followed by
a new sentence are left alone: those were shortened on purpose, and only a base
that continues the same sentence where the head stops counts as a cut. No word
is invented; every restored word comes from the base. Pragmas, shebangs, coding
lines, licence text and rule-line banners are skipped.
Dangling truncations drop from 336 to 77. Full suite is 65 failed / 18683
passed, matching main exactly, so no test changes behaviour.
* Restore comment tails cut inside a parenthetical or abbreviation
The first repair pass keyed on terminal punctuation, so a block cut after
"(e.g." or mid-code-span looked finished and was left alone. Balance the
brackets and backticks instead, and treat a trailing abbreviation as
mid-sentence:
-# ...skip the whole module up front on runners without it (e.g.
+# ...skip the whole module up front on runners without it (e.g. CPU-only) before importing unsloth, otherwise
+# collection errors instead of producing a skip.
22 further blocks across 20 files. _ts_deps.py is fixed by hand: it had also
lost a sentence from the MIDDLE of the block, so it was not a prefix of the
base text and no prefix rule could reach it.
* Reassemble the review comments that were left scrambled or cut short
Restore the full rationale for the comments the trim pass left as reversed
or truncated fragments, and put the openenv, reasoning-effort and recovery
window notes back on the code they describe.
* Put the scrambled comment blocks back over the code they describe
* Put the inline labels back on the entries they label
* Restore an inline label lost in the merge
* Restore the facts the reworded comments dropped
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Move the displaced notes back onto the tests they describe
* Re-pair the fixture rationales with their fixtures
* Put the relocated notes back and finish the sentences the trim cut
* Restore the dropped facts and correct the ones that were never true
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Unfuse the merged notes and drop the reformatting the trim smuggled in
* Restore the clauses the trim cut off and unfuse the notes it merged
Thirty sites where a comment was shortened past the point it still said
anything. Most ended on a colon or a dangling word, so the reason the
branch exists was gone while the sentence introducing it stayed. The
rest had absorbed a neighbouring test's note, which left one row
documented as another row's behaviour.
Each restored clause was checked against the code under it rather than
copied back on trust. Three were not restored: a pointer to a
measurement that is not in the function it names, a claim that a missing
import fails as NameError when it fails at collection, and a redundant
restatement in the autoscroll check.
tests/kaggle/t4_smoke/training_evidence.py ends up identical to main,
which is the right outcome for a file whose only trimmed sentence was
load-bearing.
* Give back the halves of the comments the trim cut off
Two hundred and twenty comments across a hundred and twenty files had
been shortened to the sentence that introduces a fact and none of the
fact. A note ending on a colon or on "so" or "carried over" still reads
as a comment and still parses, which is why every structural check has
been passing over them: a truncated comment is a well-formed comment.
Most of these were only findable by asking what a block ENDS on rather
than what it starts with. The seven passes before this one all keyed on
the first line, so they were blind to the commonest damage in the whole
trim. The supply-chain audit had seven "Mini Shai-Hulud May-12 wave:"
headers with the package names deleted, which was the entire content of
each one.
A handful were worse than truncated. A "can't fit on one line, so leave
it" note had moved from the test asserting changed is False onto the one
asserting changed is True; the sm_61 and sm_70 rationales in the CUDA
repair tests were stacked on a test that exercises neither, leaving
their own tests bare; an unpinned-latest note sat on a test that stubs a
working install on disk.
Restored clauses were checked against the code under them, not copied
back on trust. Around a dozen were left out or rewritten because the
base was wrong about the code as it stands: a pip stub credited with an
append it does not do, five exports said to be filled by a loop that
fills one, a __dict__ rationale that argues for the opposite of what the
loop needs, and UI strings quoted as current that the sources no longer
use.
* Put the fused row notes back on the rows they are true of
Ten sites where a comment described a sibling. The torchao skew table
had three rows' notes concatenated onto the 0.18.0+cu130 row; the
revision-forwarding table had the adapter-tokenizer exception one row
below the only row it holds for; the vision LoRA pair had the tied and
untied cases on a single anchor.
Four of these were mine, not the trim's. The 120-column rewrap left an
orphaned continuation line sitting on a class, one repair copied an
untagged-build note onto a fixture that pins a URL and has no untagged
build in it, another duplicated a namespace-package note and pushed the
interrupted-install label onto the wrong row, and a fourth moved a
collision note onto a fixture with a single host, where it is false. A
pass that rewrites 220 comments will get some of them wrong, and these
are the ones it got wrong.
Two reported sites were left alone. The masking note in the VirusTotal
client already carries its full clause, reflowed; the IOC scan comment
reads complete, and its missing "4." went with the numbering on every
other step in that function.
* Move the row and branch notes back onto what they describe
Nineteen sites. The token-count parity table had every row comment one
param too low, so each seeded reasoning style was documented as the next
one; the font-scale contract had two rationales slid down a continue
each; the validator fixture banner for R-INST-001 sat under R-INST-005,
leaving three tests unlabelled and two double-labelled.
Three of these I caused. The parity table was misplaced by the previous
commit, which moved fused notes onto rows without checking the seeds
underneath them; a timing bound landed in a test that has no timing
assertion; a restore note was reinserted between the two commands it
claims to cover instead of above them.
The pacing benchmark had lost its measurement table entirely, including
the #7892 result that is the reason there are two budgets rather than
one headline number. Restored with the columns intact.
* Undo the damage the last four repair passes did
Twenty-five sites, and almost none of them were the trim's. Round nine
was called "move the row and branch notes back onto what they describe",
and it moved a good many onto things they do not describe: the xpu swap
tests had the pinned-mirror rationale on the test that asserts the index
is dropped, the cuda repair file had a whole run of rationales slid one
test upward, and several notes were duplicated onto a neighbour rather
than moved. Where a clause was already correct on its real anchor, the
copy is deleted rather than parked somewhere new.
Separately, this PR had been changing code, and the gate could not see
it. Five files: three where a repair split a tuple across lines to make
room for a restored trailing comment, two where the trim deleted a
trailing comment and then joined up the wrapping that existed only to
carry it. Parentheses and line breaks leave the parse tree untouched, so
an AST comparison calls all of it comments. Every one is back to base
byte for byte, with the comments that justified the wrapping restored.
scripts/code_reflow.py is the check for it: same tokens in a different
arrangement is a code change, whatever the AST says.
* Let ruff rejoin the two lines the shorter comments freed
Both were split only to make room for a trailing comment. Round ten cut
those comments back to what is true of the row, the expressions now fit,
and the formatter joins them. Running it is not optional: pre-commit.ci
was red on exactly these two.
* Delete the stray copies rather than move them again
Five sites. Three were duplicates: the correct sentence still sits on
the test it describes, and a repair pass left a second copy on a
neighbour. Moving those would have been the fourth round of shuffling
one clause between two tests, so the copies are simply gone.
The mislabelling was the point in each case. An empty handover tag and
an absent one are different precedence rules, and the PowerShell 7.5
parsing note had been copied from the empty-string test onto the one
whose variable is unset. A non-zero-exit rationale sat on a parser test
that asserts a successful exit.
The other two are the trim's own: "#5854)," with its subject cut away,
and a regex description that stopped before naming the self. prefix it
matches, which is what explains the narrower filter below it.
* Put the nine slid notes back on what they assert
Seven had moved one branch or one call down. The autocast one had landed
on the case that forces float32 and asserts dtype float16, directly
under a note saying there is no dtype at all; the JAX rationale sat on
the USE_FLAX env-var case, whose module map has no jax in it; the
kill-and-sentinel note sat on a test that only checks a GPU row regex.
Each is back on the assert it describes, with a tenth found in the same
file as the probe timeouts.
Two are the trim's own. A mid-block deletion in the settings-tabs driver
spliced two half sentences into "blocking a / error-handling regression
and was a stale constant"; the macOS slot table lost the only statement
of why macos-14 is absent from a set named for images GitHub still
schedules. The retirement dates are restored, verified against all three
workflows that carry them.
The settings-tabs note is shorter than base on purpose. It now says the
invariant, that blocking a panel must not collapse the dialog, and drops
the tab count, which nothing asserts.
* Move twelve more notes back onto the branch they are about
All twelve had slid onto an adjacent test or branch, and in each the
displaced note contradicted what it now sat on. The CPU-backend note
about a wheel being left alone had landed on the test asserting a repair
runs; the XPU one about a dispatched shard sat on the single-device
case; the redirect-body note sat in a test that never binds served; the
draft-state refusal sat on the test where the target is present and the
run returns 0.
One lost text on the way as well: the missing-target rationale had been
cut to its first two sentences, dropping the reason the draft and
prerelease refusal above it would otherwise degrade into no check.
Also a rewrap that duplicated its own tail instead of replacing it, so
the merge-budget note read "else a sharded own device, not GPU0, else a
sharded model OOMs".
* Return six notes to the test whose behaviour they describe
Eight reports, six defects: two pairs described the same misplacement
from either side.
One was worse than misplaced. "A comment naming a probe is not a probe"
had moved onto the metadata scan, whose predicate matches the raw line
with no comment handling at all, so it claimed an exemption that scan
does not implement. It belongs on the Test-Path scan, the only one that
splits on # before matching. Moved, not reworded.
The rest argued against the test they sat on. The lazy-trl note was on
a stub with no __getattr__ to raise; the bogus-variant note was above a
request sending a valid variant; the imported-TensorFlow note was on
the case that supplies no tensorflow and asserts the flag is cleared;
the 62-token rationale was on the test that asserts 12.
* Move ten more notes onto the statement they explain
Seven had slid off their anchor, and in most the stray was also cut
short, so the fix was to restore the whole sentence where it is true and
delete the fragment. The torchao dependency reason sat on a test about
re-verification rather than on the --no-deps assertion it justifies; the
bitsandbytes heading sat on a test that fetches quantizers_utils.py; the
no-selection latch note sat on the delete block, which asserts nothing
reaches the action.
Two are the trim's own truncations. The stand-aside rationale in the
shortcut driver stopped at a colon, losing why Enter on a focused Deny
button would otherwise be defaulted away into approve. The kwargs-union
case stopped at a semicolon, and the trim had eaten the leading ** from
**kwargs as well.
One was about the driver rather than a test: the JSON note sat beside
the final print, where it described the document, instead of on the
stderr redirection that is what actually keeps stdout clean.
* Restore what the reflow cut, including three markers it mangled
Nine sites. Most are the trim's own truncations: the dill fixture had
lost the property that makes lookup by reference fail, so an otherwise
unexplained __module__ = "builtins" sat there with no reason; the rope
drift test had two clauses spliced into "every buffer keeps scaling in a
buffer"; the playwright guard had lost the three command shapes it
matches and why the regex is not anchored on an engine list.
Three are a shape I had not seen. The reflow rewrote the `#:` variable
documentation markers in the fast-copy constructs to `# :`, and dropped
stray colons into the wrapped prose, leaving "text : node". Those
markers are syntax, not punctuation to be tidied.
One was misplacement rather than truncation. The dotted-import rationale
had moved onto the second copy of its case and lost its tail, leaving a
dangling "and" above a capitalised "Still caught" and the first row bare.
Restored where base has it, stray deleted.
lint_duplicate_definitions self-test still passes, 63 cases.
* Put five drifted comments back on the code they describe
The trim slid several notes onto neighbouring tests, where they describe
something they no longer sit on:
- test_qat.py: the TODO moved off _test_full_model_fake_quantize, whose
leading underscore is what keeps pytest from collecting it, onto a test
that is collected and was never disabled.
- playwright_chat_ime_i18n.py: 6f carried a stray copy of 6e's
compositionstart note above an Enter KeyboardEvent, and had lost its own
rationale for why Enter reports isComposing=false while composingRef is
pinned.
- test_install_node_prebuilt_logic.py: the short-circuit note landed on the
transient-download-failure test, leaving the SHA-enforcement test bare and
dropping the carve-out that a different usable version is still kept for
offline resilience.
- test_chat_autoload_failure_gate.py: the default/namespace note moved onto
the named-import loop, and the mixed-form note explaining that regex's
optional prefix was dropped.
- test_frontend_dep_removal.py: the unquoted CSS url() note moved onto the
quoted fixture, and the HTML package-name-boundary note moved off the
script-src case onto the link-href case.
Comments only; the gate reports code unchanged on all five files.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
* Survive a torchao that does not match the installed torch
torchao is an optional dependency that unsloth never pins tightly, so the
version a notebook resolves is whatever the index serves that day. Several
recent torchao releases broke `import unsloth` outright, each in a different
place. This collects the fixes.
1. An old torchao aborts LoRA creation that never uses it.
`peft.import_utils.is_torchao_available` returns False when torchao is
absent, but raises when it is installed and older than peft's minimum:
ImportError: Found an incompatible version of torchao. Found version
0.10.0, but only versions above 0.16.0 are supported
peft calls it from `dispatch_torchao` for every LoRA layer, so one stale
optional dependency ends `get_peft_model` for a model that has nothing to
do with torchao. Kaggle ships torchao 0.10.0 preinstalled, so the
difference between pass and fail was one `pip install` line in a notebook.
`fix_peft_stale_torchao_import_error` answers False and warns once, naming
the upgrade. Only the version complaint is caught; any other ImportError
still propagates. Both binding sites are patched, since
`from peft.import_utils import is_torchao_available` copies the original
into peft.tuners.lora.torchao, the actual caller.
2. torchao 0.18 imports torch symbols that torch below 2.10 does not have.
0.17 guarded the import behind `torch_version_at_least("2.10.0")`; 0.18.0
left it at module level:
ImportError: cannot import name 'ScalingType' from 'torch.nn.functional'
It surfaces while importing transformers, so it reaches the user as a bare
Exception naming neither torchao nor torch, and kills `import unsloth`.
`fix_torchao_torch_symbol_skew` supplies placeholders that import cleanly
and refuse to be used: 0.17 simply left these names undefined on old torch,
so anything wanting them already raised, and a stub impersonating a real
enum could hand a float8 path a meaningless value. The symbol list comes
from the whole installed package, not one file:
grep -rh "from torch.nn.functional import" torchao/
which is how scaled_grouped_mm got in. Reading only mx_formats/mx_tensor.py
gives ScalingType and SwizzleType and misses the one on the path of a plain
`import torchao`. scaled_dot_product_attention exists on every supported
torch, and the loop skips anything torch already provides.
3. torchao 0.18 also wants an aten op that torch below 2.8 does not have.
It registers a float8 handler at module scope with
`@implements([aten._grouped_mm.default])`, and the attribute lookup raises
before the decorator is applied. Sibling of the symbol skew but a different
lookup: that one goes through torch.nn.functional, this one through
torch.ops, so supplying the functional names does nothing for it. The
schema is registered and nothing else, as a FRAGMENT into aten, and calling
it raises: torchao only wants somewhere to hang a handler this torch will
never dispatch to, and a plausible-looking grouped matmul would be worse
than the crash it replaces.
4. The in-process fix does not reach vLLM. vLLM inspects model architectures
in a separate process which imports torchao itself, so with
fast_inference = True the run still died, and the user saw only
"Model architectures ['...'] failed to be inspected".
`propagate_torchao_fix_to_subprocesses` stages a sitecustomize on
PYTHONPATH, which children inherit. It chains to any other sitecustomize on
sys.path rather than shadowing it, guarded so a broken one elsewhere cannot
become a startup crash for every subprocess. The logic is inlined rather
than importing unsloth, which would pay the full import cost at the start of
every subprocess on the machine and could recurse. Writes via os.replace so
a concurrent run cannot expose a truncated file, and uses os.pathsep for
Windows.
5. torchao 0.18.0 moved torchao/dtypes/nf4tensor.py to
torchao/quantization/quantize_/workflows/nf4/nf4_tensor.py. torchtune still
imports the old path and xcodec2 imports torchtune, so TTS work died with
ModuleNotFoundError one cell after a green install. `fix_torchao_nf4tensor_move`
appends a meta path finder after the real finders, so an older torchao that
still ships the module always wins, and the loader returns the relocated
module itself rather than a hand-copied stub, so it cannot rot as symbols
are added. Resolution is lazy, so `import unsloth` does not pay for a
torchao import nobody asked for.
Every fix is gated on torchao being installed, the version actually having the
bug, and the torch symbol genuinely being absent. On a healthy pair none of
them do anything, so nothing is masked. The symbol fix is also called from the
MLX branch of __init__.py, which never reaches _gpu_init.
Tests: 84 across five files. They build real package trees and import them in
subprocesses rather than stubbing sys.modules, since appended-versus-inserted
finders and PYTHONPATH inheritance only mean anything to the real import
machinery. 82 pass here; 2 skip because this machine has torchao installed and
a torch that already provides the symbols.
* Stop the in-process torchao fix from disabling the subprocess one
_gpu_init.py calls fix_torchao_torch_symbol_skew() immediately before
propagate_torchao_fix_to_subprocesses(). The first adds a placeholder to
torch.nn.functional for every symbol torch is missing and registers the
aten::_grouped_mm schema; the second then asked hasattr(F, n) and
_torch_op_is_missing("aten", "_grouped_mm") and read both as healthy. So it
returned before staging anything, in exactly the environments it exists for.
Measured on torch 2.9.1: the fix places all three symbols, propagate returns
None, nothing is written and PYTHONPATH is untouched. vLLM's architecture
inspector child therefore still died with the original ImportError, which is
the whole reason for that half of the change.
The gate now asks whether TORCH provides the symbol rather than whether one is
merely present, using the __unsloth_placeholder__ marker the placeholder
already carries, and treats a registered _aten_grouped_mm_library as proof the
op was missing. Reordering the two calls in _gpu_init.py would also work today
but leaves the same landmine for the next reorder.
The decisive symbol test never ran. Its skip predicate was any(hasattr(F, n)
for n in _TORCHAO_TORCH_SYMBOLS), and that tuple contains
scaled_dot_product_attention, which the test above it asserts exists on every
supported torch, so it was unconditionally true. It skipped on every machine,
including this one, which is genuinely missing ScalingType and SwizzleType. Now
gated on the two symbols the reproduced import line actually names, and it
passes.
The nf4 alias is installed on the MLX path too. _gpu_init.py was its only
caller and the _IS_MLX branch never reaches it, so an Apple Silicon user whose
audio work reaches xcodec2 and torchtune still hit the ModuleNotFoundError this
fix exists for. It sits next to the symbol fix already called there.
The alias loader no longer corrupts the relocated module's specification.
create_module returns the real module, and module_from_spec then overwrites its
__spec__ with the old-name one, because importlib/_bootstrap.py assigns
__spec__ unconditionally while guarding every other attribute. find_spec for
the new name reported the old one, and reload ran the alias loader's no-op
exec_module instead of the file. exec_module now restores it.
Tests: 90 pass, 1 skip, over the five torchao files. 4 new, each failing
without its fix.
* Add the AGPL-3.0 header to the new test files
* Use this repository's Apache-2.0 header on the new test files
unsloth is Apache-2.0 (LICENSE, and pyproject license = "Apache-2.0"), and 303
of its 314 test files carry no header at all while 10 carry the Apache one. The
new files here went in with an AGPL-3.0 notice attributed to Unsloth Zoo, which
is the wrong licence, the wrong project and against the convention here.
* tests: stop reading the runner's own torchao as the fixture
CI now installs torchao 0.18.0, which broke two premises.
test_the_real_environment_is_left_alone decided "this machine has the
old layout" from find_spec, but by then the session had imported unsloth
and the alias answers that query, so a new-layout runner looked like an
old-layout one and the assert fired. Ask the filesystem instead, in a
child, like every other case in that file.
test_it_does_nothing_when_torchao_is_absent shadowed torch on PYTHONPATH
but could not un-install torchao, and importlib.metadata is what the hook
reads, so the runner's real torchao made "absent" false. Run that child
in a venv without system packages.
* Keep empty PYTHONPATH components, and stop two probes reading their own placeholders
The rebuild dropped empty components. An empty component is an import
location, not padding: it is what PYTHONPATH="$PYTHONPATH:/opt/lib" leaves
behind when PYTHONPATH was unset, and CPython reads it as the cwd (3.11+
absolutises every component in Modules/getpath.py and abspath("") is the cwd;
3.10 puts the literal "" on sys.path and site.removeduppaths() absolutises
it). Dropping them took an import location away from every descendant. The
opposite case still has to be handled: a set-but-empty PYTHONPATH is ignored
by CPython, so splitting it would ADD the cwd. Hence the 'if current' guard
rather than dropping the filter outright.
Two test probes had the mirror-image bug. conftest.py imports unsloth, so on
the environment these fixes exist for the placeholders are already on
torch.nn.functional before any test runs, and a plain hasattr reads that as a
healthy torch. Use _torch_really_has, and drop the placeholders first in the
two tests that need to observe the 'before' state.
* Apply ruff kwarg-spacing formatting
pre-commit.ci's ruff-format-with-kwargs hook reported "files were modified by
this hook" and could not push the result back, so the check stayed red.
Applied locally instead.
Three source-asserting tests had to be made robust rather than re-pinned,
since the reformatting is legitimate and will happen again:
- test_the_flag_is_recorded_beside_the_dtype counted the literal
"self._autocast_enabled = (", which the formatter collapsed onto one line.
Now pairs each _autocast_dtype initialiser with a nearby flag assignment by
position, so it still catches a genuinely missing one.
- test_the_original_error_is_still_reported anchored on a whole f-string
literal, which the formatter merged with the hint that follows it. Anchors
on the message text alone now; either shape satisfies the intent.
- test_the_source_beats_the_version_fallback exposed a real fragility rather
than a test problem: the detector matched the literal "kw_only=True", so any
whitespace in transformers' own source would have read an install that needs
nothing as one that needs patching. Made whitespace-tolerant.
* Chain package and pyc form sitecustomize hooks
The generated hook probed each sys.path entry for a sitecustomize.py file,
so an existing sitecustomize installed as a package or shipped as a bare
.pyc was skipped in every child process. Use PathFinder.find_spec instead,
which is what CPython itself would have done.
* Settle deferred compile-mode switches between training steps
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix the torchao ImportError filter and the subprocess fix directory mode for PR #7955
Match peft's stale-version complaint instead of the word "torchao", so a
half-installed torchao ("No module named 'torchao.quantization'") or an
unloadable extension still surfaces rather than being reported as absent.
Tighten an already-existing subprocess fix directory: os.makedirs(exist_ok)
leaves a group- or world-writable directory alone, and that directory holds
the sitecustomize on PYTHONPATH. chmod it back to 0700, re-read, and refuse
it if the bits do not come off.
* Fix the subprocess sitecustomize importing torch at every startup for PR #7955
The generated sitecustomize goes on PYTHONPATH, so it runs in every
Python descendant, and it called _apply() eagerly: with torchao 0.18
installed that imported torch during interpreter startup even in
children that never touch torchao (0.011s to 1.284s for python -c pass
here). Defer it behind a meta_path finder that fires on the torchao
import, which is still before torchao's module body.
* Fix the untrusted pre-existing subprocess hook and the shadowed chained sitecustomize for PR #7955
* Tighten the comments and docstrings added by this PR
Same intent, fewer lines: collapse the multi-paragraph explanations in
import_fixes.py and the new tests to their load-bearing sentences, and drop
the restatements of what the code beside them already says. No code changes.
* Chain by canonical path and stage the subprocess hook through a private temp file
Two symlink holes in the generated sitecustomize for PR #7955.
A symlink alias of the hook directory also on PYTHONPATH compared unequal to
the string spelling the hook was loaded from, so PathFinder handed back this
same file as the sitecustomize to chain to. The two spellings executed one
another until the stack ran out, and every unwound level installed another
import hook: 246 of them in sys.meta_path here, each one running the fix on a
later import torchao, and the real sitecustomize elsewhere on the path never
ran at all. Compare canonical paths, and refuse a spec whose origin resolves
to this hook.
The staging file used a predictable name (sitecustomize.py.<pid>.tmp). In the
upgrade case the directory tightener exists for, a directory left group- or
world-writable by an older run still holds whatever was planted in it, so that
name can already be a symlink to a file another user owns. The write followed
it, the chmod failed silently, and os.replace installed the link itself as
sitecustomize.py, leaving it theirs to rewrite after the directory was secured.
Create the file with a random name under O_CREAT|O_EXCL|O_NOFOLLOW instead, and
remove it if anything fails.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: danielhanchen <unslothshared@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: danielhanchen <unslothai@gmail.com>