Skip to content

Give the small Windows checks one box per image instead of one box per check - #9143

Merged
danielhanchen merged 6 commits into
mainfrom
windows-small-checks
Aug 18, 2026
Merged

Give the small Windows checks one box per image instead of one box per check#9143
danielhanchen merged 6 commits into
mainfrom
windows-small-checks

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

pester, no-vs-gpu-resolve and the two cells each of vs-integration and
vcredist-clean-box are six Windows job-runs that execute for 16-34s apiece. On
this repo's Windows pool that is not what they cost. Measured over recent main
runs, every Windows job waits 2600-3400s for a slot regardless of what it then
does:

exec queue job
16s 2606s real-VS detection (VS 2022)
22s 3315s real-VS detection (VS 2026)
24s 3170s VC++ round-trip (windows-latest)
26s 3358s VC++ round-trip (windows-2025-vs2026)
33s 3311s setup.ps1 unit tests
34s 3391s GPU prebuilt resolves without VS
1108s 3164s Chat UI Tests

So six slots deliver 155s of work, and the queue those slots help create is what
the 18-minute Chat UI job waits in. Merged by runner image they are three
job-runs and nothing moves platform: windows-2022 keeps its own box for VS 2022
detection, windows-2025-vs2026 keeps its own for VS 2026, and windows-latest
carries the three checks that only need a stock Windows box.

Ordering inside a box is load-bearing. The VC++ phase uninstalls the runtime and
restores the registry in a finally, so it runs last everywhere. The phases before
it install a PowerShell module, pip packages and a simulated no-build-tools tree
under the workspace; none writes the VC++ registry keys the round-trip reads as
ground truth, so its clean-box precondition still holds. The two long jobs
(inference-smoke 718s, no-vs-cpu 464s) stay on their own runners: their cost is
execution, not slot occupancy.

tests/studio/test_windows_small_checks_stay_on_their_image.py pins the
phase-to-image contract as data rather than reading it back out of the workflow,
because a check running on the wrong Windows image mostly still passes.

Stacked on #9139, which edits a comment in this file.

Not verifiable locally: needs a cross-platform staging run.

The three ubuntu-latest jobs differed only in model, port and test body;
checkout, apt, setup-node, setup-python and install.sh --local --no-torch were
identical in all three. Windows and macOS already run these three phases in a
single job. This is the Linux port of that layout, step bodies unchanged.

studio-windows-inference-smoke.yml carried a note saying the trade was not worth
making on ubuntu, because setup there is ~1.9 min against ~3.0 min of tests and
the sequential wall time would cost more than the slots are worth. The
arithmetic was right; the premise that the three jobs start together is not what
the runners do. Run 32089506294 started two jobs at 02:40:11 and the third at
02:46:52, then finished at 02:52:57: a 12m46s wall for about 6 min of work. Run
32089558062 staggered all three. Across 18 recent multi-job runs on main the
median gap between a run's first and last job start was 175s, and 13 of the 18
exceeded 60s.

Bundled is about 7m35 every time. When all three do get slots at once that is
~1m30 slower than the 6m05 longest job; when they do not it is 2-5 min faster.
It also returns 2 of the 60 concurrent slots, which is what makes the contended
case rarer for every other workflow.

Each phase keeps its own model cache directory, HF_HOME, port, server log,
artifact and step timeouts, and gates on the shared preamble rather than on the
phase before it, so a phase-1 failure still lets phases 2 and 3 report. Phase 3
moves to hf-cache-vision with a key bump because phase 1 keeps hf-cache, whose
key is byte-shared with the macOS and Windows gemma phases.

tests/studio/test_gguf_smoke_phases_stay_independent.py asserts all of that from
the workflow rather than from a list, since every one of these regressions is
silent rather than red.
… context

Actions rejects the whole workflow before scheduling a job, so the first staging
push produced a run with zero jobs and only 'This run likely failed because of a
workflow file issue'. Job-level env can read github, needs, strategy, matrix,
vars, inputs and secrets, not runner.
Green end to end on a staging repo with an empty actions cache, so every model
downloaded cold: 6m03s for all three phases, of which 105s is the now-shared
setup. The phases themselves are 26s / 179s / 36s. That is at or under the 6m05
the longest of the three jobs took on its own, on a third of the runners, so the
earlier estimate of ~7m35 was pessimistic.
…r check

pester, no-vs-gpu-resolve and the two cells each of vs-integration and
vcredist-clean-box are six Windows job-runs that execute for 16-34s apiece. On
this repo's Windows pool that is not what they cost. Measured over recent main
runs, every Windows job waits 2600-3400s for a slot regardless of what it then
does:

  exec   queue  job
   16s   2606s  real-VS detection (VS 2022)
   22s   3315s  real-VS detection (VS 2026)
   24s   3170s  VC++ round-trip (windows-latest)
   26s   3358s  VC++ round-trip (windows-2025-vs2026)
   33s   3311s  setup.ps1 unit tests
   34s   3391s  GPU prebuilt resolves without VS
 1108s   3164s  Chat UI Tests

So six slots deliver 155s of work, and the queue those slots help create is what
the 18-minute Chat UI job waits in. Merged by runner image they are three
job-runs and nothing moves platform: windows-2022 keeps its own box for VS 2022
detection, windows-2025-vs2026 keeps its own for VS 2026, and windows-latest
carries the three checks that only need a stock Windows box.

Ordering inside a box is load-bearing. The VC++ phase uninstalls the runtime and
restores the registry in a finally, so it runs last everywhere. The phases before
it install a PowerShell module, pip packages and a simulated no-build-tools tree
under the workspace; none writes the VC++ registry keys the round-trip reads as
ground truth, so its clean-box precondition still holds. The two long jobs
(inference-smoke 718s, no-vs-cpu 464s) stay on their own runners: their cost is
execution, not slot occupancy.

tests/studio/test_windows_small_checks_stay_on_their_image.py pins the
phase-to-image contract as data rather than reading it back out of the workflow,
because a check running on the wrong Windows image mostly still passes.

Stacked on #9139, which edits a comment in this file.

Not verifiable locally: needs a cross-platform staging run.

@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: 2bd305d43d

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

return dict(found)


def test_the_workflow_is_a_single_job():

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 Wire the workflow guards into CI

These new guard tests—and the companion test_windows_small_checks_stay_on_their_image.py module—are never collected by CI. The repository's default pytest configuration limits discovery to tests/security, while workflow-trigger-lint.yml invokes a fixed list of individual Studio guard modules that does not include either new file. Consequently, a later workflow-only change can violate the phase-isolation or image-placement invariants and still pass every check, so add both modules to the workflow-trigger lint job or another unfiltered CI invocation.

Useful? React with 👍 / 👎.

test_pester_bootstrap_hardening.py keyed on jobs['pester'], so grouping the small
Windows checks by runner image turned all 8 of its assertions red on a rename
that changed nothing they assert. The staged run caught it: 'Guard the Pester
bootstrap' failed while every phase after it passed.

Looks up whichever job installs Pester instead. Worth noting that the gating held
exactly as designed under a real failure: the guard failed and the no-VS resolve
and VC++ round-trip phases on the same box still ran and passed.

@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: 24ccf34115

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

- name: Wait for /api/health, log in, change password, load model
id: ready-vision
timeout-minutes: 15
if: ${{ !cancelled() && steps.sdks.outcome == 'success' && steps.prime-hf-vision.outcome != 'failure' }}

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 Gate the vision wait on a successful boot

If the boot-vision step fails—for example because the server exits before binding—this condition still runs the health/login/load step because it only checks the model-prime outcome. The step then polls a port with no server for up to 180 seconds and produces a second, misleading failure; require steps.boot-vision.outcome == 'success' here, as the API and tool phases already do.

Useful? React with 👍 / 👎.

@danielhanchen
danielhanchen merged commit 9eb6142 into main Aug 18, 2026
30 of 32 checks passed
@danielhanchen
danielhanchen deleted the windows-small-checks branch August 18, 2026 04:13
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