Skip to content

feat(cli): add --revision to the -m/--model resolver - #1132

Merged
inureyes merged 4 commits into
mainfrom
feature/issue-1113-model-revision-flag
Aug 13, 2026
Merged

feat(cli): add --revision to the -m/--model resolver#1132
inureyes merged 4 commits into
mainfrom
feature/issue-1113-model-revision-flag

Conversation

@inureyes

Copy link
Copy Markdown
Member

Summary

mlxcel download accepted --revision but the -m/--model resolver did not, so a pinned revision could be fetched and then not run by repo-id: mlxcel run owner/name always resolved against main. --revision <REV> is now on generate, run, serve, inspect and mlxcel-server, using mlxcel download's flag name and help wording.

The issue asked for two behaviours to be decided rather than left implicit. Both answers narrow the feature deliberately, and both are below.

Decision 1: the mlxcel store is not revision-aware, and this PR does not change that

store::model_dir_under composes <models_root>/<owner>/<name> with no revision component. This is not an oversight, it is already recorded in two places on main: src/downloader/store.rs says revision "is only used for the HF-cache probe (the mlxcel store is not revision-namespaced)", and mlxcel rm --revision ships help text saying the same.

So --revision is honoured only where it can be honoured correctly:

Location Revision-aware? Behaviour for a revision-qualified request
store::hf_cache_snapshot (HF cache) Yes, resolves refs/<rev> or a commit-named snapshot Consulted normally; a hit is correct
Legacy ./models/<basename> No Skipped. A hit could be any revision
mlxcel store <owner>/<name> No Skipped for reuse; refused as a download destination when occupied
Network fetch Yes, via DownloadOptions.revision Fetches the requested revision

The refusal is the part worth explaining. download_repo_blocking checks snapshot_complete(&local_dir, &wanted) against the requested revision's file list and, when every wanted filename is already present and non-zero, prints "all expected files already present ..., skipping" and returns without fetching. Two revisions of a repo normally share filenames, so proceeding would hand back whichever revision was already on disk while reporting success. Failing loudly is the only honest option without a layout change.

This is a pre-existing hazard for mlxcel download --revision too, which has the same collision today. It is not introduced here and deserves its own issue.

Revision-namespacing the store would lift every restriction above, but it changes an on-disk layout shared with list, rm and the download verb. The issue itself says that is where this stops being a good first issue, so it is left as follow-up rather than forced in. --models-dir is the escape hatch for holding two revisions at once.

Decision 2: --revision with an existing local path is an error

Step 1 of the resolver returns an existing path verbatim and has nothing to resolve a revision against. Ignoring the flag would leave a user believing they had pinned something, so it is refused:

$ mlxcel generate -m /tmp -p x -n 1 --revision v2
Error: --revision v2 cannot be applied to '/tmp', which is an existing local path: mlxcel uses a local
model directory exactly as given and has no revision to resolve against it. Drop --revision to use this
directory, or pass a repo-id (`owner/name`) to resolve that revision.

What changed

  • src/downloader/resolver.rs: resolve_model_source_with_override takes revision: Option<&str> and threads it into both repo-id branches. locate_cached_snapshot gates the two non-revision-aware locations on revision.is_none(). New locate_landed_snapshot is used for the post-download probes, because that asks a different question: not "may this location answer for this revision" but "where did the bytes just fetched land", which is knowable since the store directory was verified Absent beforehand. Two new errors, revision_with_local_path_error and revision_store_occupied_error. The doc comment recording the gap ("The CLI subcommands do not currently expose a --revision flag, so they pass None") is replaced, and the module docs gain a Revisions section.
  • resolve_model_source(value) keeps its one-argument shape and delegates with None, so the public convenience entry point is not broken for external callers.
  • src/main.rs, src/commands/run.rs, src/bin/mlx_server.rs: --revision on ModelOptions, InspectArgs, ServeArgs, RunArgs and ServerArgs.
  • src/commands/{generate,chat,serve,inspect}.rs, src/bin/mlx_server.rs: threaded through all five resolver call sites; ChatOptions carries it so the REPL path matches.
  • src/downloader/resolver_tests.rs: 5 new tests. src/commands/{generate,serve}_tests.rs: fixtures initialize the new field.
  • CHANGELOG.md: entry under ## [Unreleased] / ### Added.

Test plan

  • cargo test --profile test-fast --features cuda --lib downloader::resolver: 33 passed, 0 failed (5 new, covering the local-path refusal, the legacy skip, the store skip, and the occupied-store refusal, which also asserts nothing was fetched).
  • cargo test --profile test-fast --features cuda --test cli_help_consistency: 25 passed, including the_two_server_binaries_accept_the_same_flag_surface, so mlxcel serve and mlxcel-server stay in sync on the new flag.
  • cargo test --profile test-fast --features cuda --bin mlxcel serve_ (13 passed) and ... validate_pipeline_parallel (5 passed).
  • cargo clippy --profile test-fast --features cuda --lib --tests -- -D warnings clean.
  • cargo fmt --all -- --check clean.
  • Against the built binary: --revision advertised with the intended wording on generate and run; a local path plus --revision refused; an occupied store plus --revision refused with both workarounds named and no network call.

Acceptance criteria

  • mlxcel run owner/name --revision <REV> resolves that revision (via the HF cache, or by fetching it when the store destination is free).
  • --revision help text matches mlxcel download's wording.
  • The local-path interaction is an error.
  • The store-collision question is answered above, not left implicit.
  • The doc comment documenting the absence is updated.

Follow-ups

  • Revision-namespacing the store, which would also fix the pre-existing mlxcel download --revision collision described above.

Closes #1113

Post-merge precision fixes to comment text landed in #1129. No code changes.

The hermetic 2D test's name and doc overstated what it proves. `--help` short-circuits ahead of clap's conflict validation, not only ahead of the runtime, so the test establishes that `--pp-size` and `--tp-size` are recognized long names on the `generate` subcommand, not that the combination survives validation; a future `conflicts_with` between them would not fail it. The same effect is observable elsewhere in this binary: `mlxcel list --json --verbose` exits 2 on the conflict while appending `--help` exits 0. The doc now states that limit explicitly and the test is renamed to match. The module doc also described only the parity test and now covers both.

The validator comment used "repository" where this repo's own wording is "tree" (`docs/README.md`: "maintained in a separate documentation tree"; `Makefile`), which is worth matching in a comment whose job is precision about exactly that split. It also now says "per-axis operator manual pages", so it does not read as though those two pages document the 2D composition, which neither they nor `docs/distributed.md` do.

`run_generate_once`, not `run_generate`, is the function that resolves `-m` ahead of the validators; `run_generate` is correct only transitively.

Refs #1112
`mlxcel download` accepted `--revision` but the `-m/--model` resolver did not, so a pinned revision could be fetched and then not run by repo-id: `mlxcel run owner/name` always resolved against `main`. The resolver already threaded `revision: Option<&str>` end to end; only the two public entry points hardcoded `None`. `--revision <REV>` is now on `generate`, `run`, `serve`, `inspect` and `mlxcel-server`, with `mlxcel download`'s flag name and help wording.

The issue asked for two behaviours to be decided rather than left implicit, and both answers narrow the feature deliberately.

Store revision-awareness. `store::model_dir_under` composes `<models_root>/<owner>/<name>` with no revision component, which `store.rs` and `mlxcel rm --revision`'s own help text already record. So a revision is honoured only where it can be honoured correctly. `store::hf_cache_snapshot` is revision-aware and answers normally, and a miss fetches the requested revision. The legacy per-CWD directory and the mlxcel store carry no revision provenance, so a revision-qualified request skips them rather than accepting a hit that could be any revision. A revision-qualified miss whose store directory is already occupied is refused with the two workarounds named, because `download_repo` treats same-named non-zero files as "already present" and returns without fetching (`src/downloader/mod.rs`), so proceeding would hand back whichever revision was already on disk. That is a pre-existing hazard for `mlxcel download --revision` as well, and is worth its own issue. `locate_landed_snapshot` is split from `locate_cached_snapshot` because the post-download probe answers a different question: not "may this location answer for this revision" but "where did the bytes just fetched land", which is knowable since the store directory was verified Absent beforehand.

Revision-namespacing the store would lift all of this, but it changes an on-disk layout shared with `list`, `rm` and the `download` verb, which the issue itself says takes this out of good-first-issue territory. Left as follow-up rather than forced in here.

Local paths. `--revision` alongside an existing local path is an error. Step 1 returns such a path verbatim and has nothing to resolve a revision against, so ignoring the flag would leave a user believing they had pinned something.

The doc comment recording the gap ("The CLI subcommands do not currently expose a `--revision` flag, so they pass `None`") is replaced, and the module docs gain a Revisions section covering which locations may answer and why.

Verified: 33 `downloader::resolver` unit tests pass including 5 new ones covering the local-path refusal, the legacy and store skips, and the occupied-store refusal (which asserts nothing was fetched); 25 `cli_help_consistency` tests pass, including the cross-binary flag-surface check that both server binaries now share `--revision`; and against the built binary, `--revision` is advertised with the intended wording on `generate` and `run`, a local path plus `--revision` is refused, and an occupied store plus `--revision` is refused without a network call.

Closes #1113
`sample_generate_args` and `sample_args` build `ModelOptions` and `ServeArgs` with explicit field initializers, so adding `revision` broke the bin crate's test target with two E0063s. Both now pass `None`, which is the pre-#1113 behavior these fixtures are asserting against.

Caught by `cargo clippy --lib --tests -- -D warnings`. The earlier run appeared to pass only because its output was piped through `grep | tail`, which reports the exit status of the last stage rather than cargo's.

Refs #1113
@inureyes inureyes added type:enhancement New features, capabilities, or significant additions priority:low Low priority status:review Under review labels Aug 13, 2026
@inureyes inureyes added status:done Completed and removed status:review Under review labels Aug 13, 2026
@inureyes
inureyes merged commit 25f8eb5 into main Aug 13, 2026
8 checks passed
@inureyes
inureyes deleted the feature/issue-1113-model-revision-flag branch August 13, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:low Low priority status:done Completed type:enhancement New features, capabilities, or significant additions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): -m/--model cannot select a revision, while mlxcel download can

1 participant