FEAT: support oci:// model URIs via llmman serve - #5457
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
760f895 to
26fa838
Compare
qinxuye
left a comment
There was a problem hiding this comment.
The actual LLM custom-model path does not use the xinference.model.utils.cache_from_uri function changed here. create_llm_model_instance constructs LLMCacheManager, and LLMCacheManager.cache_uri() still accepts only file://; an oci://ghcr.io/org/model:tag spec therefore fails immediately with ValueError: Unsupported URL scheme: oci before llmman is called. The llama.cpp embedding and rerank cache managers reuse this same method.
Please wire OCI resolution into LLMCacheManager.cache_uri() (or centralize the URI handling so both paths share it), and add a test that calls LLMCacheManager.cache() with an OCI URI to cover the production path.
Also, the changed-file pre-commit run currently fails codespell at xinference/model/llmman.py:115: unparseable should be unparsable.
26fa838 to
482eb58
Compare
|
Thanks @qinxuye, both points were addressed. |
Model URIs only accepted file://. This adds oci://, so a model published
as a CNCF ModelPack artifact in a container registry can be registered as
a custom model, reusing the registry, credentials, mirroring and air-gap
tooling a deployment already has:
{"model_uri": "oci://ghcr.io/org/model:tag"}
Pulling 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 client is stdlib-only, so no new dependency.
Scheme handling is centralized in utils.resolve_model_uri(), so LLM,
embedding and rerank all accept oci://. The cache entry is a symlink into
llmman's store, exactly as for file://, so everything downstream is
unchanged.
Signed-off-by: Eric Curtin <eric.curtin@docker.com>
482eb58 to
1184a78
Compare
| check_daemon(base) | ||
| logger.info("Pulling %s via llmman daemon at %s", reference, base) | ||
| pull(base, reference, progress) | ||
| return resolve(reference) |
There was a problem hiding this comment.
The pull and resolve steps can target different stores. pull() sends /api/pull to the daemon selected by LLMMAN_HOST, but resolve() then runs a local llmman resolve --no-pull; upstream resolve reads the local process's LLMMAN_MODELS/default store and never contacts that daemon. Therefore a remote daemon, or a local daemon started with a different LLMMAN_MODELS, can complete the pull successfully and then fail here with "not found in local store." Please make resolution use the same daemon/store and return a path accessible to Xinference, or explicitly constrain and validate the co-located shared-store invariant. Please also add a regression test with distinct daemon and client stores; mocking pull_and_resolve currently hides this mismatch.
Motivation
model_uriaccepted onlyfile://. This addsoci://, so a model published as a CNCF ModelPack artifact in a container registry can be registered as a custom model:{"model_name": "my-model", "model_specs": [{"model_uri": "oci://ghcr.io/org/model:tag", "...": "..."}]}The same registries, credentials, mirroring and air-gap tooling a deployment already uses for container images then apply to models.
Implementation
xinference/model/llmman.py(new): stdlib-only client for a runningllmman servedaemon, so no new dependency. llmman already implements the ModelPack media types, registry auth, resumable blob download and a content-addressed store, so none of that is reimplemented.GET /api/versionprobes reachability and identity;POST /api/pullstreams NDJSON so a multi-gigabyte fetch is not silent (an error can arrive in-band at HTTP 200, and a stream ending withoutsuccessis a failure too);llmman resolve --no-pullreports where the bytes landed, keeping the daemon the only thing that touches the network.xinference/model/oci_utils.py(new): theoci://scheme handling.xinference/model/utils.py: scheme dispatch is centralized inresolve_model_uri(), used byCacheManager._cache_from_uri(),LLMCacheManager.cache_uri()andcache_from_uri()— so LLM, embedding and rerank all acceptoci://. The cache entry is a symlink into llmman's store, exactly the shape thefile://branch produces, so everything downstream is unchanged.Only
oci://is claimed:file://, bare paths and every other scheme reach exactly the branch they did before. A pull needs both the daemon reachable and the binary onPATH(orXINFERENCE_LLMMAN_BIN); each missing piece has its own actionable error, and neither is required unless anoci://URI is used.Review feedback addressed
LLMCacheManager.cache_uri()— the production path, also reused by the embedding and rerank cache managers — is wired up, not just the unusedutils.cache_from_uri(). Covered bytest_llm_cache_manager_caches_an_oci_uri, which fails without that change.unparseable->unparsable.endpoint()now keeps theLLMMAN_HOSTscheme long enough to apply llmman's own default-port rules (http://-> 80,https://-> 443, anything else -> 17434) and falls back to the default port for an out-of-range or non-numeric one, as llmman'su16parse does. The origin stays HTTP either way, matching llmman's daemon client. Regression cases added forhttp://host,https://hostand rejected ports.Testing
test_llmman.pyruns against a real HTTP server on a loopback port, not mocks, so the NDJSON streaming contract is genuinely exercised:/api/versionaccepted and a non-llmman server rejected, nothing listening reported actionably; pull success with forwarded byte progress and the exact request body asserted; an in-band error at HTTP 200; a stream ending withoutsuccess; a non-JSON diagnostic tolerated.test_oci_utils.pycoversparse_uri, the resolve output contract plus eight malformed-output cases, everyLLMMAN_HOSTform (scheme default ports, bad ports, wildcard-to-loopback, IPv6), binary default/override and the missing-binary error, an empty reference rejected without touching the daemon, andLLMCacheManager.cache()end to end on anoci://URI.xinference/model/testsalso run with no new failures; the remaining failures there are pre-existing in this environment (missingmodelscope) and reproduce onmain.pre-commitis clean on the changed files.Not covered: an end-to-end launch against a live
llmman servebacked by a real registry.Docs
A
model_urinote indoc/source/models/custom.rstandXINFERENCE_LLMMAN_BINindoc/source/getting_started/environments.rst, with catalog entries for all nine locales, compiled withpython doc/build_i18n.py --alland built in each language.