Skip to content

ci: uv installs on the slow tier, no-aimnet and the documentation jobs - #242

Merged
isayev merged 4 commits into
mainfrom
ci/uv-slow-tier
Nov 3, 2026
Merged

isayev merged 4 commits into
mainfrom
ci/uv-slow-tier

Conversation

@isayev

@isayev isayev commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

The fast legs moved to uv last change; these are the jobs that were left, and
the ones where the install actually cost something. Six install steps convert:
the four slow shards, no-aimnet, and both docs.yml jobs.

Result

RUN WALL 165s            (baseline 243s / 253s / 304s over the last three runs)
  +165s  slow tests (NNP integration)                   (7s)
  +155s  slow shard 1 of 4 (end-to-end pipeline)        (152s)  <- binds the run
  +145s  slow shard 3 of 4 (workers, compile, thermo)   (142s)
  +145s  slow shard 2 of 4 (main and smiles2mols)       (142s)
  +145s  slow shard 4 of 4 (everything else)            (129s)
  + 83s  pytest py3.12                                   (80s)
  + 71s  pytest py3.11                                   (68s)
  + 41s  Build Documentation                             (39s)
  + 37s  no-aimnet                                       (35s)
  + 11s  ruff                                             (9s)

Per install step:

job pip uv
slow shard 1 72 s 7 s
slow shard 2 78 s 8 s
slow shard 3 63 s 10 s
slow shard 4 66 s 6 s
no-aimnet 86 s 7 s
Build Documentation 50 s 7 s
Check Links 85 s 8 s

Built head to head in one runner, with nothing else differing: 81-90 s with
pip, 9-10 s with uv.

No job's install is its largest step any more. The gate's binding constraint is
now shard 1's own test step (120 s of a 152 s job), so the next lever is the
shard balance -- 120 / 109 / 105 / 74 test-seconds -- which this change
deliberately leaves alone.

The two flags

--index-strategy unsafe-best-match: uv's default stops at the first index
that has a package at all, and the PyTorch index carries a requests (2.28.1)
far below this project's floor, so the default resolves nothing and the install
fails outright. --system: a runner has no virtual environment and uv declines
to touch an interpreter it was not pointed at.

The resolution is unchanged, checked per job

A temporary workflow (run 41546099135, removed from this branch's head) built
each converted environment twice in one runner -- the pip command the job used
to run and the uv command it runs now -- and froze both with the same pip
binary:

extras job(s) pip uv identical torch nvidia_*/triton
ase,ani,dev slow shard 1-4 118 118 yes 2.13.0+cpu none
ani,ase,dev minus aimnet no-aimnet 117 117 yes 2.13.0+cpu none
ase,ani,docs build, linkcheck 106 106 yes 2.13.0+cpu none

For no-aimnet the absence is the point, so it was asserted rather than
inferred: on both environments aimnet is missing from the freeze and python -c "import aimnet" fails. The 118 to 117 drop is aimnet and nothing else on
both sides, so uv pip uninstall left its transitive dependencies in place
exactly as pip uninstall -y did.

cache: pip goes with them

uv does not read pip's HTTP cache, so the restore was 6-10 s of setup feeding a
tool that never looks at it. Nothing writes those entries now either; the five
stored ones (~3.3 GB of the repository's 10 GB budget) age out on their own.
The only cache a run still depends on is the 31 MB model-weight entry the slow
shards restore, which cache-keepalive.yml holds open -- that file needs no
change and gets none.

uv is pinned

UV_VERSION: "0.13.6" as one workflow-level value per file, referenced as pip install "uv==${UV_VERSION}" at each of the five sites. Unpinned, uv was a
resolver taking whatever version was current at run time in front of every gate
here -- the same fails-on-luck mode that pins ruff exactly and SHA-pins every
action. The comment that declined a marketplace action partly to avoid another
pinned dependency now says why that is an argument against the action, not for
leaving the installer floating.

0.13.6 because that is the version every number above was measured with,
confirmed from the logs of all four runs cited. It cannot go in the dev
extra: uv is what installs the project, so it exists before any extra does.

The pin costs nothing. On the pinned head (run 41548392879) the gate is 181 s
and every install is 6-9 s, unchanged; the 165 to 181 s difference is entirely
shard 1's test step (120 s then 137 s), which is the tier's ordinary noise. Two
runs put the gate at 165-181 s against a 243-304 s baseline.

Not converted

lint installs ruff and nothing else in 2 s, which is less than pip install uv would add. publish.yml is out of scope, and separately its pip install of
the built wheel is deliberately the way a user would install it.

Two comments that had gone stale are corrected while the files are open: the
fast legs' note on cache: pip claimed the other jobs still wrote that cache,
and docs.yml described tests.yml's cron as weekly for the model cache,
which went monthly when cache-keepalive.yml took that job over.

Check Links is skipped on a pull request, so its install was exercised by
dispatching the workflow on this branch (run 41547117953, green). Its pip
baseline is the 2026-11-02 monthly cron, run 41402781229.

The fast legs moved to uv last change and their installs went from 78 s and
57 s to 8 s and 7 s. The jobs still on pip were the ones where that mattered
more: 63-78 s per slow shard, 86 s for no-aimnet, 50 s for the documentation
build -- each of them longer than an entire fast leg now takes, and the slow
tier is what the run's gate waits on. Converting them is worth more than
everything in the two changes before it.

Both uv flags carry over because both are load-bearing rather than tuning.
--index-strategy unsafe-best-match, because uv's default stops at the first
index carrying a package at all and the PyTorch index has a requests (2.28.1)
far below this project's floor, so the default resolves nothing; --system,
because a runner has no virtual environment and uv declines to touch an
interpreter it was not pointed at.

The resolution is unchanged, checked per job rather than assumed. Each
converted environment was built twice in one runner, once with the pip command
it used to run and once with the uv command it runs now, and frozen with the
same tool on both sides: the two dependency sets are identical, torch is the
+cpu build, and there is no nvidia_* or triton wheel either way. For no-aimnet
that check is the point of the job rather than a formality -- its value is
which package it ends up without, so both freezes were also asserted to lack
aimnet and both interpreters to fail `import aimnet`.

cache: pip goes with them. uv does not read pip's HTTP cache, so restoring the
627 MB entry would cost about 5 s of a job's setup to feed a tool that never
looks at it. No job writes those entries now either, so the stored ones age
out of GitHub's 7-day window unused; that costs nothing, because the only
cache a run still depends on is the model-weight entry the slow shards
restore, which cache-keepalive.yml holds open.

The lint job keeps pip: it installs ruff and nothing else, in 2 s, which is
less than `pip install uv` would add. publish.yml is left alone -- its pip
install of a built wheel into a throwaway virtual environment is a check that
the published artifact installs the way a user installs it.

Two comments that had gone stale are corrected while the files are open: the
fast legs' note on cache: pip said the other jobs still wrote that cache, and
docs.yml described tests.yml's cron as weekly for the model cache, which went
monthly when cache-keepalive.yml took that job over.
Builds each converted job's environment twice in the same runner -- the pip
command it used to run and the uv command it runs now -- and fails if the
freezes differ. Three legs, one per extras set: ase,ani,dev for the slow
shards, ani,ase,dev plus the uninstall for no-aimnet, ase,ani,docs for the
documentation jobs.

Both sides are frozen with the same tool, because `uv pip freeze` and `pip
freeze` disagree about pip and setuptools themselves and that difference says
nothing about the resolution. The project's own editable self-entry is
recorded differently by the two installers, so it is reported separately
rather than ignored.

Deleted before the pull request's head; it exists to produce one measurement.
It had one measurement to produce and produced it, in run 41546099135: three
legs, one per converted extras set, each building the environment twice in the
same runner. ase,ani,dev froze to 118 distributions on both sides, ani,ase,dev
minus aimnet to 117, and ase,ani,docs to 106; every pair was byte-identical,
torch was 2.13.0+cpu throughout with no nvidia_* and no triton, and in the
no-aimnet leg both freezes lacked aimnet and both interpreters failed to
import it. The logs and the uploaded freezes are what remains of it.

That run also measured the two installers against each other with nothing else
differing: 81-90 s for pip, 9-10 s for uv. The install comment now carries
those numbers instead of a ratio.
`pip install uv` took whatever was current at run time, at five sites across
two workflows, in front of every gate in the repository. A uv release that
changed resolution -- `--index-strategy` behavior is exactly the kind of thing
that moves -- would have broken all of them at once on whichever pull request
happened to be open, with no diff to point at. That is the same fails-on-luck
mode the lint job's comment gives as the reason ruff is pinned exactly, and
every action here is SHA-pinned for it too.

It was also inconsistent with the argument already written next to it: that
comment declines a marketplace action partly to avoid one more pinned
dependency, which is a reason against the action, not a reason to leave the
installer floating.

One workflow-level UV_VERSION per file, referenced at each install site rather
than a literal repeated five times. It cannot go in pyproject.toml's dev
extra, which was the obvious place: uv is what installs the project, so it has
to exist before any extra does.

0.13.6, because that is the version that produced every number now recorded in
these comments -- the 9-10 s installs, the 165 s run, and the three freeze
comparisons that showed pip and uv resolving identically. Picking latest
instead would have left the measurements describing a version no longer in
use. Raising it means raising both files together and re-checking the
resolution rather than assuming it held.
@isayev
isayev marked this pull request as ready for review November 3, 2026 00:12
@isayev
isayev merged commit b124afc into main Nov 3, 2026
11 checks passed
@isayev
isayev deleted the ci/uv-slow-tier branch November 3, 2026 00: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