mirror of
https://github.com/unslothai/unsloth.git
synced 2026-08-21 06:43:53 +00:00
* Fix two module resolution failures in the frontend test suite
* Fix the third Windows-only path failure in the frontend test suite
* Measure how the chat thread's interaction cost grows with message count
Studio's chat UI is reported as sluggish on Windows 11 and worsening as the
thread fills, while token generation is unaffected. That shape says the cost is
per-message renderer work, so the thing to establish first is the curve.
New smoke page mounts the real Thread against a synthetic local runtime, seeded
to N messages, each carrying prose plus one code fence plus one KaTeX block so
Streamdown, Shiki and KaTeX all pay their per-message price. No backend, no
auth, no router.
New harness runs four scripted actions at N in {10, 50, 200, 500} under 6x CDP
CPU throttling: one keystroke into the composer, one scroll gesture, one message
action menu opened and closed, and one message delete. Each is bracketed by
Performance.getMetrics, which separates the two families of cost: work that
grows because layout is uncontained lands in LayoutDuration, work that grows
because a listener or an export is O(messages) lands in TaskDuration alone.
It measures, it does not gate. There are no timing budgets: it prints the table
and exits 0 unless the harness itself broke. Budgets belong in a later change,
set from numbers taken on real hardware. What it does fail on is measuring
nothing, since that is the failure mode that reads as good news: a seed that did
not render, a menu that never opened, a delete that deleted nothing, a scroll
that did not move, or four columns that do not rise with N.
Measured on this tree, N=10 to N=500: menu open+close 1309ms to 33199ms,
delete 438ms to 8491ms, keystroke median 86ms to 268ms.
Every metric recorded reaches the printed table, and the harness contract test
now enforces that mechanically by parsing the recorded keys out of the source
and requiring each one in the table.
CDP CPU throttling and longtask are Chromium-only, so Firefox and WebKit runs of
this file are correctness checks and not performance ones.
* Stop the thread-weight harness charging its own cost to the app
A review of the first commit found four ways the measurement could look clean
while reporting something other than the app, and two of them were forging part
of the curve. All were reproduced before being fixed.
The local runtime does have a remote id. It synthesises `__LOCALID_...`, which is
truthy, so the per-message fork-count GET fires after all: seeding 20 messages
issued 10 requests, against a comment claiming nothing reached the network. They
were being answered by a Playwright route handler, so each one paused the
renderer for a round trip to another process, once per assistant message. The
page now answers them itself, before anything mounts, and the harness fails if a
single request escapes during a measured action.
Closing the menu was timed from after the Escape dispatch. Radix dismisses
synchronously inside it, so the layer teardown, focus restore and re-render --
the O(messages) fan-out this issue is about -- were excluded from the number
meant to capture them.
Every timing carries a ~33ms floor, since a double rAF cannot resolve faster than
two vsync intervals, and CPU throttling does not move it. An action that never
happened therefore reported ~33ms, which reads as a plausible measurement rather
than as a failure. The floor is now measured per N, printed, subtracted before
every growth ratio, and a keystroke at or under it fails the run.
The keystroke check read the DOM value back, which is what the harness itself
wrote. It now compares against the runtime's own composer state, so a keystroke
that reaches the textarea but not React is caught.
Also removed from the timed regions: a per-frame document-wide querySelector in
both poll loops, replaced by a MutationObserver flag and an isConnected check; a
counts() call per frame in the seed gate; an animated scrollIntoView still in
flight when the menu window opened; and a console warning per action-bar render,
by giving the page the router its useNavigate calls expect. Long tasks are now
read after a yield, since the observer delivers on a later task and the tail
entry was being dropped.
Corrected curve, N=10 to N=500, floors removed where they apply: menu open+close
1021ms to 33591ms, delete 297ms to 8563ms, keystroke 48ms to 283ms, scroll worst
frame 5ms to 126ms. Layout stays flat and tiny throughout; the growth is in
style recalc and task time.
* Stop mounting the assistant action bar for every message
At rest the full assistant action bar was mounted under every assistant
message. Each one carries around eight tooltips, and every tooltip holds a
useSyncExternalStore subscription to the shared modal-layer store, which
re-walks its ancestors reading style.pointerEvents whenever Radix puts the
body on the modal layer. A 500-message thread therefore mounted 250 bars and
1503 tooltip triggers, and every menu open fanned out across all of them.
autohide unmounts rather than hides (ActionBarRoot returns null on the hidden
status), so passing it removes the nodes and the subscriptions together. The
user bar has always done this.
Not unconditionally "always", though: this bar carries the only Stop reading
control, which is why it already passes hideWhenRunning={!speaking} and why
DeleteMessageButton guards the same case. With "always", moving the pointer
off a message being read aloud would take that control away. At most one
message speaks at a time, so exempting it costs nothing.
Measured with tests/studio/playwright_thread_weight.py at 6x CPU throttle,
before -> after, at 500 messages:
action bars 250 -> 0
tooltip triggers 1503 -> 3
DOM nodes 56332 -> 41082
delete ms 8595.8 -> 3642.7
scroll worst frame 159.5 -> 87.3
menu open+close ms 33624.5 -> 25279.3
keystroke median ms 316.2 -> 241.0
Note what did not move: menu style recalc, 23725.8 -> 22567.7 ms. A 27% cut
in DOM buys 5% there, so the bar is not what makes that number grow. It is the
document-wide invalidation from Radix writing pointer-events onto the body,
and it is still the dominant cost at large N.
The index.css comment is corrected in passing: it justified forcing
content-visibility: visible on every code block with "thread length is
bounded", which is the assumption this issue disproves. The rule is kept for
the flicker it was really fixing.
* Studio chat: one fork-count subscription per thread, not one per message
The fork badge registered its own CHAT_HISTORY_UPDATED_EVENT listener and issued
its own GET, and it is mounted once per message. A delete on a 200-message thread
therefore fired 200 requests before anything could repaint, and streaming raises
that event once per chunk.
Badges now share one debounced subscription per thread and one request that
returns every fork count of that thread, so the cost is flat in thread length.
* Studio chat: derive research-message ownership once per thread revision
useOwnsResearchMessage exported the whole thread from inside a per-message render
body, so one render pass over N messages exported N times and inspected N*N items.
Streaming re-renders the thread once per chunk, so that pass is hot.
The answer is a property of the thread revision, so derive it once for the message
list every message in the pass already shares. Measured on a synthetic thread: 200
exports and 0.80ms per pass becomes 1 export and 0.014ms; at 1000 messages 16.4ms
becomes 0.02ms.
* Studio chat: stop deep-cloning the thread on every delete and every save
exportedItemToRecord ran JSON.parse(JSON.stringify(...)) over every message's
content and attachments on its way to a PUT that serializes the same records
again, and syncExportedRepositoryToBackend ensured the thread row that
syncStoredChatMessages already ensures, so every save paid for GET /threads/{id}
twice.
The parts are replaced rather than mutated, so a copy of the list is snapshot
enough and the bytes on the wire are identical (asserted in the new test).
Measured on a 200-message thread of ~4KB messages: the record step drops from
1.17ms to 0.015ms, and a delete makes one thread-row read instead of two.
* Studio chat: open the message action menu non-modally
A modal Radix menu writes pointer-events:none on <body>. That is an inherited
property, so every open and close invalidates style for the whole document, and
on a long thread the recalc is the bulk of the cost. Non-modal never writes it.
Also teaches the harness the difference between a cost that was removed and a
page that never mounted one, so the after-tree does not read as broken.
* Pin the message action menu to the non-modal layer
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Drop the #8980 content this branch no longer needs
* Print the hovered trigger count and keep the modal layer load-bearing in the verdict
The contract test caught both: a metric recorded but not printed, and the verdict
no longer reading body_pointer_events_while_open after the non-modal fix removed the
old check. The layer is now compared ACROSS N instead, since either mode is
legitimate but mixing them means the columns measure different mechanisms.
* Stop the rapid-submit settle wait measuring the action bar instead of the reply
This branch autohides the assistant action bar, and that turned the last
wait of the rapid-submit step into a 7-of-7 failure on Windows CI. The wait
is not what the step proves, and the clause that broke was not measuring
what it claimed.
innerText of a [data-role=assistant] root spans the whole subtree, and the
action bar sits inside it, so 'every reply has non-empty innerText' was
satisfied by button labels regardless of what the model returned.
Instrumented at that point on the CI runners, two runs on this branch's
merge base read:
content=[0, 0] innerText=[73, 73] clause held, BOTH replies empty
content=[0, 19] innerText=[73, 89] clause held, first reply empty
gemma-3-270m-it answers 'Reply with exactly: rapid-first' with an empty
completion in 3 of 8 sampled runs, on the merge base as much as here, and
the clause held every time. So the empty reply is the model, is pre-existing,
and was simply masked. With the bar autohidden the subtree is content only,
and the same empty completion now fails.
Dropped rather than repointed at the content element: an empty completion is
the model's behaviour, so a content assertion would be flakier than what it
replaces. What is left is exactly the settle this wait is for, two bubbles,
nothing streaming, nothing queued. The behaviour the step exists to prove,
that a 100 ms follow-up queues behind a held first turn, is state.queueSeen
above and is untouched.
Test-only. No Studio code changes, so the menu open+close and nodes-at-rest
wins are unaffected. Verified on Windows CI at this branch's head: 4 of 4
Chat UI Tests jobs green with this change, against 7 of 7 red without it.
* Validate the delete measurement at every size, not just the last
* Keep the thread's fork counts across the autohidden badges
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Exempt the thread-weight harness from the CI-coverage check
* Keep the newest reply's action bar in the tab order
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Load the crypto polyfill on the thread-weight smoke page
The page this branch adds was the only one in studio/frontend without
<script src="/crypto-boot.js"></script> in its head, so
crypto-uuid-boot.test.ts fails with "smoke-thread-weight.html must load
/crypto-boot.js".
The rule is not cosmetic: the polyfill has to run before the module entry
or crypto.randomUUID is missing on the older WebViews Desktop embeds, and
a smoke page without it measures a page that differs from production in
the one respect the harness is meant to hold constant.
Reproduced on this branch and verified: the named assertion fails before
the change and the file's four tests pass after it.
* Debounce the fork count refresh instead of throttling it
onHistoryUpdated returned while a timer existed, which is a leading edge
throttle rather than a debounce. Streaming raises the history event once per
chunk, so the timer expired mid stream and the next chunk armed another one,
costing a whole thread fork count fetch every 300ms for as long as the reply
ran. Fork counts cannot change during generation, so all of those were waste.
Clear and reschedule on every event, as the sidebar refresh already does.
The existing burst test fired all 20 events inside one window, where a
throttle and a debounce behave identically, which is why this survived. The
new case spreads the events across the window like a real stream: 20 chunks
cost 10 refetches before this change and none after, with one refresh in the
quiet window that follows.
* Reveal the action bar on focus, not only on hover
Unmounting the bar on every message but the newest took its controls out of
the tab order, and there was no non-pointer way to bring them back, so Copy,
Edit, Refresh, Delete, Read aloud and More were unreachable by keyboard or
screen reader on every older reply. Deferred rendering is fine while the user
can still ask for what is deferred, and tabbing is asking.
Measured on the thread weight smoke page at 20 messages, older reply: the
accessibility tree exposed no bar controls before this change and still none
after focus entered the message; it now gains Copy, Refresh, Delete, Edit
response, Read aloud and More. Three tabs from an older reply used to walk
its two code fence buttons and step to the next message without ever
entering a bar; the same walk now lands on the bar's Copy.
The library has no focus path, so this drives its own isHovering flag from
focus within the message root. Two writers share that flag, so a pointer
leaving while focus is inside re-asserts it from a listener registered in an
effect, which runs after the primitive's own mouseleave in the same dispatch
and so never renders the intermediate false that would unmount the element
holding focus. The clear is a one frame watchdog reading activeElement
rather than a relatedTarget test, because relatedTarget is null both for
browser chrome and for a portal, and no focusout fires at all when the
focused element is removed, which is how the menu closes.
At rest this stays at one mounted bar of ten, and at one again after a focus
round trip, so the weight this branch is here to remove is unchanged: 1950
DOM nodes and 9 tooltip triggers, the same as before.
* Give a plain prose reply a way into the tab order
The focus reveal only fires once focus is inside the message, and a reply
whose body is plain prose contains nothing focusable after autohide unmounts
its action bar. The earlier measurement of two focusable controls per message
was an artefact of a fixture where every reply carried a code fence, and
Streamdown ships one Copy button per fence. Seeded with a prose only reply it
is zero, and Tab walks straight past the message into the next reply, so
Copy, Edit, Refresh, Delete, Read aloud and More stay unreachable.
tabIndex on the message root rather than a visually hidden button: it adds no
DOM node, which matters for a branch that exists to cut per message weight,
and it draws nothing at rest. The app's own focus-visible rule gives it the
same 1px keyboard indicator every other focusable container already has, and
focus-visible means a mouse click still draws nothing.
Measured at rest, unchanged: one action bar of ten, 1950 DOM nodes, 9 tooltip
triggers. Outline is none with neither focus nor hover, and none after a
click. The cost is one extra tab stop per assistant message, which is the
price of the controls being reachable at all.
The fixture gains an opt in plain prose variant so the existing weight
measurements keep the exact thread they had.
* Bound how long a fork change can wait behind an unrelated stream
CHAT_HISTORY_UPDATED_EVENT fires once per streaming chunk, and the fork-count refresh was a pure
trailing-edge debounce, so a reply running in a background thread reset the timer on every chunk.
Deleting a fork from the sidebar while looking at its parent changes the displayed count, and the
refresh was postponed until the unrelated stream went quiet, which on a long or queued run is
minutes. That is starvation, not slowness.
The event is a bare Event with no detail and six other consumers, so telling fork changes apart
from chunks means changing a contract well outside this store. FORK_COUNT_REFRESH_MAX_WAIT_MS
bounds the wait inside it instead: a second timer, started by the first event of a burst and
deliberately not restarted by the ones after it, races the debounce, and whichever fires first
cancels the other. A second timer rather than a Date.now() deadline so it runs off the same clock
as the debounce and is testable without a fake Date.
2000ms because the bound costs one whole-thread fetch per window while a stream runs. At the
300ms debounce that is the per-chunk traffic this store exists to remove; at 2000 it is under a
sixth of it, and only while something is streaming.
The existing continuous-stream test asserted ZERO mid-stream fetches, which is the behaviour the
review flagged, so it now measures the price of the bound instead of claiming there is none: it
pins the count against both what the ceiling allows and what a leading-edge throttle would have
cost, so a regression in either direction is a failure.
Four assertions were made to fail on their own broken tree before being kept: the ceiling
removed, the ceiling restarted per chunk so it never expires, the losing timer left uncancelled
when the other fires, and the ceiling left running past unsubscribe. That last one was vacuous at
first, since the entries map is empty after unsubscribe and a leaked timer refreshes nothing; it
now subscribes a second thread inside the ceiling window, which is both observable and the case
that actually costs a user a request.
* Scope the popup lookup to the action bar
The watchdog treated any expanded descendant as this message's open menu. Reasoning cards and
tool-fallback cards are Radix CollapsibleTriggers and render aria-expanded=true for as long as
the reader leaves them open, which is the resting state of a message whose tool output has been
expanded. decide() therefore found a popup every frame, rescheduled itself every frame, held
focusWithinRef and the synthetic hover set, and left the bar mounted indefinitely, at the cost of
a DOM query per frame per such message. Scoped to .aui-assistant-action-bar-root, which is where
the trigger the hook has to hand focus back to actually lives.
Proving this took three attempts and the first two were wrong, which is worth recording because
the failure was in the test rather than in the fix.
isHovering has two writers. This hook is one; assistant-ui's own MessagePrimitive.Root mouseleave
handler is the other, and it writes false directly. So a phase that reveals the bar by HOVERING
and then moves the pointer away sees the bar unmount on both trees, because the library unmounted
it. On the broken tree the watchdog genuinely spins forever, and the bar still goes away. An
assertion on the mounted bar count cannot attribute that outcome to this branch, and C2 passed on
the fixed and broken trees alike.
The phase now keeps the pointer off the message entirely and reveals the bar by focus, which the
tabIndex on the message root makes possible. With no mouseleave to fire, focus is the only writer
and the bar's fate is decided by the watchdog alone. C2 is green on the fixed tree and red under
--break widepopup, 1 bar still mounted and held indefinitely. Two guards sit in front of it: one
asserts the pointer really is off the message, the other that the bar really was mounted and
focused, so the phase fails loudly rather than passing vacuously if either precondition breaks.
Also fixes a pre-existing crash the sweep was hiding. Under --break eagerclear the bar is gone by
the time A8 runs, and a bare more.focus() on an undefined element threw a Playwright TypeError
that killed the process before phases P, B and C ever ran, so the break reported fewer reds than
it earns and ended in a traceback rather than a red result. It is more?.focus() now and the
trigger's absence is folded into A8's condition, so eagerclear completes and A8 goes red on its
own merits.
Full sweep: head 14/23, notabindex 19/23, restring 21/23, focusring 22/23, leakflag 21/23,
noreassert 22/23, eagerclear 21/23, widepopup 22/23 with C2 the only red. thread.tsx checksummed
before and after all eight runs, identical every time.
* Reveal the action bar from the backward traversal too
The tabIndex on the message root only worked going FORWARD. A container is reached before its own
descendants, so Shift+Tab arriving from the message below landed on the last tabbable thing in the
message, and with the bar unmounted that is the root, which sits BEFORE the bar in DOM order.
Focusing it mounted the controls and the next Shift+Tab then stepped straight past them to the
previous message. Copy, Edit, Delete and More were reachable going forward and unreachable going
backward, which is worse than being unreachable outright, because the forward pass makes it look
solved.
A sentinel span after the bar is what makes the backward pass land inside the message: focus stops
there, the bar mounts, and the next Shift+Tab goes into the last control rather than out. It is
deliberately NOT a focus redirect to that control, which would trap the forward pass in a loop
between the last button and the sentinel. It carries no onFocus of its own because React's onFocus
is focusin and already bubbles to the root, and no role, because it performs no action; the
aria-label is what stops it being an unannounced stop.
It DOES cost one DOM node per assistant message, and this branch is about per-message weight, so
that is asserted rather than absorbed: the at-rest guard now requires exactly 1950 baseline nodes
plus one sentinel per reply and nothing else, measured 1960 for 10 replies, with the sentinel count
checked separately so the extra nodes are attributed rather than tolerated.
Three assertions, each proven red. nosentinel takes D2 and D3 red while D1 stays green, which is
precisely the reported asymmetry: focus still enters the reply, it just skips the bar. D1 says
something weaker, so it needs noentry, which removes the sentinel and the tabIndex together and
leaves nothing in the message reachable at all; D1, D2 and D3 all go red there.
Full sweep, 26 assertions: fixed 26/26, nosentinel 23/26, noentry 18/26, notabindex 24/26, head
15/26, widepopup 25/26 with C2 alone, eagerclear 21/26, noreassert 25/26, leakflag 24/26, restring
24/26, focusring 25/26. thread.tsx checksummed before and after every run, identical throughout.
* Draw a focus indicator on the backward reveal sentinel
The shared soft-outline rule is :where(div, main, section, aside, ul, ol):focus-visible, which
never matched a span, so the sentinel was a real tab stop that drew nothing: Shift+Tab into a
message made focus visibly disappear for one stop before the next press reached the action bar.
That is a focus-visible failure, not a cosmetic one.
The element stays 0x0 and the ring is drawn by outline-offset. Outlines take no part in layout,
so the indicator appears without shifting the message, which giving the span dimensions on focus
would have done. Still nothing at rest, and :focus-visible means a mouse click draws nothing
either.
Measured: with keyboard focus the ring spans 14px against the UA default's 2px on a zero-sized
span, which is the difference between an indicator and no indicator. D4 asserts the SPAN of the
drawn ring rather than merely that an outline style exists, because the broken tree still reports
outline-style auto and would satisfy a presence check while showing nothing.
Proven red by --break blindsentinel, which removes the indicator rule and leaves the tab stop
itself intact, so only D4 fails.
* Stop tracking the generated Studio test database
.studio-test-root/studio.db is written at test time by
tests/studio/install/test_selection_logic.py, which points storage_roots.studio_root
at that path. It is a mutable SQLite runtime database, not a fixture: nothing
reads it, any test run or Studio start rewrites it and dirties the checkout, a
later accidental commit could capture real local chat or settings data, and it
puts 221 KB into every clone while exercising nothing.
It was not in the tree deliberately. It arrived in the merge commit here because
that commit was staged with `git add -A` after running the suite, and no
gitignore rule covered the path. Main does not track it.
Untracked and ignored, and the file is left on disk since creating it is normal.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: danielhanchen <unslothshared@gmail.com>
Co-authored-by: oobabooga <112222186+oobabooga@users.noreply.github.com>
Co-authored-by: danielhanchen <danielhanchen@users.noreply.github.com>
|
||
|---|---|---|
| .. | ||
| _shared | ||
| fast_inference | ||
| kaggle | ||
| notebooks | ||
| python | ||
| qlora | ||
| saving | ||
| security | ||
| sh | ||
| studio | ||
| studio_setup_ps1 | ||
| utils | ||
| version_compat | ||
| vllm_compat | ||
| __init__.py | ||
| _grpo_dispatch_source.py | ||
| _rl_source.py | ||
| _zoo_aggressive_cuda_spoof.py | ||
| _zoo_rocm_spoof.py | ||
| conftest.py | ||
| run_all.sh | ||
| test_allow_cpu_import_driverless.py | ||
| test_attention_implementation.py | ||
| test_attn_impl_honor_explicit.py | ||
| test_bad_mappings_redirect.py | ||
| test_broken_tf_does_not_break_import.py | ||
| test_broken_torchvision_probe.py | ||
| test_callback_signature_drift.py | ||
| test_cli_export_unpacking.py | ||
| test_collection_hygiene.py | ||
| test_compressed_export_gpu_release.py | ||
| test_cuda_spoof_reports_free_memory.py | ||
| test_dataclass_default_backfill.py | ||
| test_deliberate_crashes_suppress_cores.py | ||
| test_device_helpers.py | ||
| test_enforce_kwargs_spacing.py | ||
| test_fa2_fast_generate_bypass.py | ||
| test_fast_gemv_dispatch.py | ||
| test_fast_generate_slow_guard.py | ||
| test_finetune_last_n_layers.py | ||
| test_flash_attn_4_namespace_shadow.py | ||
| test_flex_attention_needs_ampere.py | ||
| test_float32_generate_autocast.py | ||
| test_float32_no_fp16_autocast.py | ||
| test_fp8_device_context.py | ||
| test_fp8_restore_dropped_scale.py | ||
| test_fp8_tiny_e8m0.py | ||
| test_fused_ce_not_return_dict_logits.py | ||
| test_gemma4_chat_template.py | ||
| test_gemma_2b_mapper_key.py | ||
| test_generate_kwarg_gate.py | ||
| test_generation_failure_is_visible.py | ||
| test_get_model_name.py | ||
| test_gguf_basename_platform_matrix.py | ||
| test_gguf_disk_headroom.py | ||
| test_gguf_model_basename.py | ||
| test_gguf_windows_export_routing.py | ||
| test_gguf_windows_native.py | ||
| test_gradient_checkpointing_restore.py | ||
| test_grouped_gemm_optional_gather_indices.py | ||
| test_grpo_accumulated_loss_hidden_states_signal.py | ||
| test_grpo_autocast_disabled.py | ||
| test_grpo_autocast_per_trainer.py | ||
| test_grpo_hidden_states_logits_cost.py | ||
| test_grpo_hidden_states_per_call_degradation.py | ||
| test_grpo_hidden_states_signal.py | ||
| test_grpo_hidden_states_wrap_target.py | ||
| test_grpo_packed_raw_logits_nograd.py | ||
| test_grpo_padded_raw_logits.py | ||
| test_grpo_width_dispatch_sites.py | ||
| test_ignored_tokenizer_casing.py | ||
| test_import_fixes_drift.py | ||
| test_installer_interactive_prompts.py | ||
| test_installer_profile_hardening.py | ||
| test_installer_shortcut_icons.py | ||
| test_installer_skip_autostart.py | ||
| test_installer_system32_guard.py | ||
| test_installer_unsloth_version.py | ||
| test_kaggle_gguf_error_message.py | ||
| test_lint_no_parallel_clamp.py | ||
| test_loader_glob_skip.py | ||
| test_map_eos_token.py | ||
| test_mapper_no_duplicate_keys.py | ||
| test_missing_optional_dep_skips.py | ||
| test_missing_torchvision_vlm.py | ||
| test_model_registry.py | ||
| test_moe_lora_targets.py | ||
| test_multi_image_grpo_chunking.py | ||
| test_new_mapper_fetched_fp8.py | ||
| test_new_mapper_no_global_leak.py | ||
| test_nvfp4_quant_load.py | ||
| test_offline_loading_helpers.py | ||
| test_offload_embedding_hooks.py | ||
| test_offload_tied_autodisable.py | ||
| test_offload_tied_guard.py | ||
| test_offloaded_parameter_hint.py | ||
| test_peft_stale_torchao.py | ||
| test_peft_symbol_backfill.py | ||
| test_peft_tensor_parallel_compat.py | ||
| test_peft_weight_converter_compat.py | ||
| test_prefetch_snapshot_scope.py | ||
| test_pretrain_compile_reset.py | ||
| test_profile_startup_gate.py | ||
| test_psutil_apple_cpu_freq.py | ||
| test_public_api_surface.py | ||
| test_python39_compatibility.py | ||
| test_pythonpath_empty_components.py | ||
| test_raw_text.py | ||
| test_raw_text_json_loading.py | ||
| test_resolve_model_class.py | ||
| test_rl_config_compat.py | ||
| test_runtime_text_encoding.py | ||
| test_save_entrypoints_reach_converter.py | ||
| test_save_lora_without_vllm.py | ||
| test_settle_eager_fallbacks_between_steps.py | ||
| test_sft_vision_dataset_gate.py | ||
| test_source_read_encoding.py | ||
| test_st_save_merged_signature.py | ||
| test_st_subfolder_weights_are_fetched.py | ||
| test_studio_install_workspace_guard.py | ||
| test_studio_root_resilience.py | ||
| test_studio_shutdown_thread_wait.py | ||
| test_synthetic_chunk_data.py | ||
| test_synthetic_vllm_startup_failure.py | ||
| test_tool_mask_zoo_compat.py | ||
| test_torchao_aten_grouped_mm.py | ||
| test_torchao_nf4tensor_move.py | ||
| test_torchao_subprocess_fix.py | ||
| test_torchao_torch_symbol_skew.py | ||
| test_torchaudio_cuda_mismatch.py | ||
| test_transformers5_bare_annotation_live.py | ||
| test_transformers_dependency_floor.py | ||
| test_uma_safetensors_load.py | ||
| test_uninitialized_position_ids.py | ||
| test_version_single_source.py | ||
| test_video_path_validation.py | ||
| test_vllm_broken_detection.py | ||
| test_vllm_cuda_mismatch_wheel_url.py | ||
| test_warnings_issued_guard.py | ||
| test_windows_amd_gpu_scan_fallback.py | ||
| test_windows_rocm_bnb_version.py | ||