feat(download): pull oci:// models via llmman serve - #2280
Open
ericcurtin wants to merge 1 commit into
Open
Conversation
Lets a model id point at a model published as a CNCF ModelPack OCI artifact, anywhere a HuggingFace repo id works today. Model distribution is increasingly moving to OCI registries, which lets a deployment reuse the registry, credentials, mirroring and air-gap tooling it already has for container images -- often easier to run on an isolated cluster than reaching the Hub. Acquisition is delegated to a running `llmman serve`, which already implements the ModelPack media types, registry auth, resumable blob download and a content-addressed store. The daemon does the pull (POST /api/pull, streamed as NDJSON so a multi-gigabyte fetch is not silent, and an error arriving in-band at HTTP 200 is caught) but deliberately exposes no local path, so `llmman resolve --no-pull` reports where the bytes landed. The client is stdlib-only, so no new dependency. download_shard gains an early branch: an artifact is pulled whole, so there is no per-file list to walk and no allow_patterns counterpart. The daemon's aggregate progress is mapped onto the same RepoDownloadProgress the HuggingFace path emits, so the cluster UI is unchanged. The pull runs in a thread so the event loop is not blocked, and skip_download resolves without pulling. An explicit oci:// scheme is required rather than sniffing a bare registry/name:tag: that shape is indistinguishable from a HuggingFace repo id, so guessing would silently hijack existing model ids. Signed-off-by: Eric Curtin <eric.curtin@docker.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an
oci://scheme so a model published as a CNCF ModelPack artifact can be used anywhere a HuggingFace repo id works today.Model distribution is increasingly moving to OCI registries -- the same registries, credentials, mirroring and air-gap tooling a deployment already uses for container images. For an isolated or bandwidth-constrained cluster that is often easier to run than reaching the Hub, and it lets one local registry mirror serve every node.
How
download_shardgains an early branch. An artifact is pulled whole, so there is no per-file list to walk and noallow_patternscounterpart -- the shard-aware pattern filtering simply does not apply. The daemon's aggregate progress is mapped onto the sameRepoDownloadProgressthe HuggingFace path emits, so the cluster UI andon_progresscontract are unchanged.The blocking pull runs via
asyncio.to_threadso the event loop is not stalled, andskip_download=Trueresolves throughllmman resolve --no-pullwithout pulling anything.Acquisition is delegated to a running
llmman serverather than hand-rolled: llmman already implements the ModelPack media types, registry auth, resumable blob download and a content-addressed store.New
src/exo/download/llmman.pyis the daemon client, stdlib-only (urllib), no new dependency:GET /api/versionprobes reachability and identity -- a server answering without aversionfield is reported as "not an llmman daemon", worth distinguishing from nothing listening.POST /api/pullstreams NDJSON. An error arrives in-band at HTTP 200, and a stream that ends withoutsuccessis also a failure -- both are errors, not a completed pull.llmman resolve --no-pullreports where the bytes landed;--no-pullkeeps the daemon the only thing that touches the network.LLMMAN_HOSTis honoured with llmman's own parsing, including rewriting a wildcard bind (0.0.0.0,[::]) to loopback.A pull needs both the daemon reachable and the binary on
PATH(orEXO_LLMMAN_BIN); each missing piece has its own actionable error, and neither is required unless anoci://model id is used.Explicit scheme, no sniffing. A bare
registry/name:tagis indistinguishable from a HuggingFace repo id (mlx-community/Llama-3-8B-Instruct-4bit); guessing would silently hijack existing model ids. Every other id reaches exactly the path it did before.Testing
New
src/exo/download/tests/test_llmman.py, running against a real HTTP server on a loopback port rather than mocks, so the NDJSON contract is genuinely exercised.All 22 executed here. Coverage:
/api/versionaccepted, a non-llmman server rejected, nothing-listening reported actionably; pull success with forwarded byte progress and the exact request body asserted; in-band error at HTTP 200; a stream ending withoutsuccess; non-OK status; a non-JSON diagnostic tolerated; scheme detection incl. case-insensitivity; that a HF repo id, a bare registry ref and a local path are not claimed;strip_schemeround-trips; empty reference rejected; everyLLMMAN_HOSTform incl. wildcard-to-loopback.ruff formatandruff checkclean on both new files.download_utils.pyreports 14 ruff findings both before and after this change (verified against a stashed clean tree), so none are introduced here.Not verified here, flagged rather than implied:
download_shard_from_ociitself is covered by inspection rather than execution -- exercising it needs the full exo stack (mlx/torch), unavailable in this environment. No end-to-end cluster run against a livellmman serve.