Local inference¶
The anyinfer.local subsystem: hardware detection, backend selection, runtime
acquisition, tuning, fit classification, model acquisition and storage, server supervision,
and hardware→tier recommendation. Concepts:
the local subsystem ·
the model catalog ·
acquiring models · guides:
run a model locally ·
choose and download a local model.
from anyinfer import local
Hardware¶
anyinfer.local.detect ¶
detect(*, use_cache: bool = True) -> HardwareProfile
Detect this machine's hardware.
Never raises: anything that could not be determined becomes a warning and a None
field. Callers treat the result as advice, not as fact.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
use_cache
|
bool
|
Read and write the disk cache. Overridden by
|
True
|
Returns:
| Type | Description |
|---|---|
HardwareProfile
|
The detected profile. |
anyinfer.local.HardwareProfile
dataclass
¶
HardwareProfile(
os_name: str,
arch: str,
total_ram_bytes: int | None = None,
available_ram_bytes: int | None = None,
cpu_name: str | None = None,
physical_cores: int | None = None,
logical_cores: int | None = None,
accelerators: tuple[Accelerator, ...] = (),
warnings: tuple[str, ...] = (),
detected_at: float = 0.0,
)
What we could learn about this machine.
Every field may be None: absence means "not determined", never "zero".
Attributes:
| Name | Type | Description |
|---|---|---|
os_name |
str
|
|
arch |
str
|
Machine architecture as reported by |
total_ram_bytes |
int | None
|
Physical RAM. |
available_ram_bytes |
int | None
|
RAM currently free, when the platform reports it. |
cpu_name |
str | None
|
Processor model string. |
physical_cores |
int | None
|
Physical core count, preferred for thread tuning. |
logical_cores |
int | None
|
Logical processor count. |
accelerators |
tuple[Accelerator, ...]
|
Detected accelerators, strongest first. |
warnings |
tuple[str, ...]
|
Everything that could not be determined, and why. |
detected_at |
float
|
Unix timestamp of the probe, for cache display. |
primary_accelerator
property
¶
primary_accelerator: Accelerator | None
The accelerator a server should target, or None for CPU-only.
total_vram_bytes
property
¶
total_vram_bytes: int | None
Total memory of the primary accelerator, when known.
user_supplied
property
¶
user_supplied: bool
Whether this profile came from from_user_input rather than a probe.
from_user_input
classmethod
¶
from_user_input(
*,
ram_gb: float | None = None,
vram_gb: float | None = None,
accelerator: AcceleratorKind | None = None,
accelerator_name: str | None = None,
compute_capability: str | None = None,
os_name: str = "",
arch: str = "",
) -> HardwareProfile
Build a profile from specs a person supplied, in familiar units.
The remote-Ollama case: local probing describes the wrong machine, and no Ollama
API reports its host's specs, so asking the user is the only honest source. Values
arrive in gigabytes because that is what a user reads off a spec sheet; anything
left out stays None and keeps its "not determined" meaning.
The profile is marked as self-reported in warnings, so any advice derived from it
can say so.
from_json
classmethod
¶
from_json(data: dict[str, Any]) -> HardwareProfile
Deserialize from the disk cache.
anyinfer.local.Accelerator
dataclass
¶
Accelerator(
kind: AcceleratorKind,
name: str | None = None,
total_vram_bytes: int | None = None,
free_vram_bytes: int | None = None,
unified_memory: bool = False,
compute_capability: str | None = None,
driver_version: str | None = None,
)
One detected accelerator.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
AcceleratorKind
|
Which runtime family can drive it. |
name |
str | None
|
Human-readable device name, when reported. |
total_vram_bytes |
int | None
|
Total device memory, or |
free_vram_bytes |
int | None
|
Free device memory at probe time, or |
unified_memory |
bool
|
True when device memory is shared with system RAM (Apple Silicon), which makes VRAM budgeting a different calculation entirely. |
compute_capability |
str | None
|
NVIDIA compute capability as reported by the driver
( |
driver_version |
str | None
|
Vendor driver version string, when reported. Used to check that a downloadable CUDA runtime's toolkit version is supported before installing it. |
anyinfer.local.AcceleratorKind
module-attribute
¶
AcceleratorKind = Literal[
"cuda", "rocm", "metal", "vulkan", "cpu"
]
Accelerator families we can detect and target.
anyinfer.local.probe_signature ¶
probe_signature() -> str
Fingerprint the probe tooling, so the cache invalidates when it changes.
Keyed on the resolved path and mtime of each probe executable plus the interpreter's platform: installing a GPU driver, or moving to different hardware, changes this.
anyinfer.local.CACHE_BYPASS_ENV
module-attribute
¶
CACHE_BYPASS_ENV = 'ANYINFER_HARDWARE_CACHE_BYPASS'
Set to skip the cache entirely (read and write).
anyinfer.local.CACHE_REFRESH_ENV
module-attribute
¶
CACHE_REFRESH_ENV = 'ANYINFER_HARDWARE_CACHE_REFRESH'
Set to ignore a cached result and re-probe, then rewrite the cache.
Backends¶
anyinfer.local.available_backends ¶
available_backends(
*,
search_paths: list[Path] | None = None,
hardware: HardwareProfile | None = None,
runtime_root: Path | None = None,
include_runtime_root: bool = True,
) -> list[Backend]
Find installed llama-server binaries, best first.
Three sources, in descending order of how much they can be trusted:
- Manifest-validated variants under the well-known runtime root. The manifest states the backend, so nothing is inferred.
- Caller-supplied directories, where the backend is guessed from the directory name — a convention, not a fact.
PATH, where the backend is guessed from the hardware, because a binary onPATHsays nothing at all about what it was compiled against.
The distinction is recorded in Backend.detail rather than hidden, so a surprising
selection can be explained.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
search_paths
|
list[Path] | None
|
Extra directories to search, each expected to hold a runtime variant
named after its backend ( |
None
|
hardware
|
HardwareProfile | None
|
Detected hardware, used to label what a found binary can actually drive. |
None
|
runtime_root
|
Path | None
|
Override the well-known runtime root. |
None
|
include_runtime_root
|
bool
|
Search the well-known runtime root at all. |
True
|
Returns:
| Type | Description |
|---|---|
list[Backend]
|
Usable backends, ranked. Empty when no binary is found at all. |
anyinfer.local.select_backend ¶
select_backend(
hardware: HardwareProfile,
*,
search_paths: list[Path] | None = None,
runtime_root: Path | None = None,
include_runtime_root: bool = True,
) -> Backend | None
Pick the best backend this machine can actually use.
A CUDA build on a machine with no NVIDIA device is useless, so the selection is the
intersection of what is installed and what the hardware can drive. When the best
drivable variant is not the best variant the hardware could theoretically use — a
Vulkan build on an NVIDIA card because the CUDA add-on is not installed — the returned
Backend.detail says so, which is what turns a silent degradation into a
discoverable recommendation.
anyinfer.local.Backend
dataclass
¶
Backend(
kind: AcceleratorKind,
binary: Path,
rank: int = 0,
detail: str = "",
)
One usable llama.cpp runtime variant.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
AcceleratorKind
|
The acceleration family this build targets. |
binary |
Path
|
Path to its |
rank |
int
|
Preference score from |
detail |
str
|
Why this backend was or was not selected. |
anyinfer.local.BACKEND_RANK
module-attribute
¶
BACKEND_RANK: dict[AcceleratorKind, int] = {
"cuda": 30,
"metal": 25,
"rocm": 22,
"vulkan": 20,
"cpu": 10,
}
Preference order across runtime variants; higher wins.
Runtime variants¶
AnyInfer ships no llama.cpp binaries. These fetch, validate, and select them; CUDA is an explicit opt-in, never installed on a user's behalf.
anyinfer.local.install_runtime ¶
install_runtime(
kind: AcceleratorKind | None = None,
*,
hardware: HardwareProfile | None = None,
root: Path | None = None,
table: RuntimeTable | None = None,
progress: ProgressCallback | None = None,
client: Client | None = None,
force: bool = False,
) -> InstallReport
Fetch, verify, and unpack a llama-server runtime variant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
AcceleratorKind | None
|
Which backend to install. |
None
|
hardware
|
HardwareProfile | None
|
Detected hardware, needed for the default choice and the CUDA gate. |
None
|
root
|
Path | None
|
Runtime root; defaults to |
None
|
table
|
RuntimeTable | None
|
Pinned artifact table; defaults to the bundled one. |
None
|
progress
|
ProgressCallback | None
|
Download progress callback. |
None
|
client
|
Client | None
|
An |
None
|
force
|
bool
|
Install CUDA even when the precondition checks object. Never skips digest verification — only the hardware gate. |
False
|
Returns:
| Type | Description |
|---|---|
InstallReport
|
A report naming the executable to launch. |
Raises:
| Type | Description |
|---|---|
LocalRuntimeError
|
If no build exists for this platform and backend, if a CUDA
precondition fails without |
anyinfer.local.installed_runtimes ¶
installed_runtimes(
root: Path | None = None, *, build: str | None = None
) -> list[RuntimeManifest]
Every validated runtime variant under the runtime root.
anyinfer.local.remove_runtime ¶
remove_runtime(
kind: AcceleratorKind, *, root: Path | None = None
) -> bool
Delete an installed runtime variant. Returns whether anything was removed.
anyinfer.local.default_runtime_kind ¶
default_runtime_kind(
hardware: HardwareProfile | None,
) -> AcceleratorKind
Which variant to install when the caller expresses no preference.
Never CUDA. The vendor-neutral small builds cover every GPU well enough to be useful immediately, and a several-hundred-megabyte download is a decision a user makes, not one a library makes on their behalf:
- Apple Silicon → Metal, which needs no vendor runtime and is the native path.
- Intel Mac → CPU; llama.cpp's Metal backend targets Apple Silicon.
- Windows or Linux with any GPU → Vulkan, which drives NVIDIA, AMD, and Intel alike.
- Anything else → CPU.
anyinfer.local.check_cuda_preconditions ¶
check_cuda_preconditions(
hardware: HardwareProfile, table: RuntimeTable
) -> tuple[tuple[str, ...], tuple[str, ...]]
Return (blocking_reasons, warnings) for installing the CUDA add-on.
The pinned build implies a CUDA toolkit version, which implies a minimum driver and a minimum compute capability. Checking them up front turns "your GPU is too old" into a clear refusal before a 400 MB download instead of an incomprehensible crash at load.
anyinfer.local.install_hint ¶
install_hint(
hardware: HardwareProfile | None,
table: RuntimeTable | None = None,
) -> str
A one-line suggestion of which runtime this machine should install.
anyinfer.local.load_runtime_table ¶
load_runtime_table(
path: Path | None = None,
) -> RuntimeTable
Load the pinned runtime table.
Raises:
| Type | Description |
|---|---|
LocalRuntimeError
|
If the bundled table is unreadable or malformed. Unlike the probes, this is real data shipped with the package: a broken table is a build defect, not a property of the user's machine. |
anyinfer.local.runtime_root ¶
runtime_root() -> Path
Where runtime variants are installed.
Follows the same per-OS data-dir convention as the model directory, so a user who knows
where one lives can find the other. Overridable with ANYINFER_RUNTIME_DIR.
anyinfer.local.RuntimeTable
dataclass
¶
RuntimeTable(
build: str,
generated: str = "",
release_url: str = "",
cuda_toolkit: str = "",
min_cuda_driver_major: int = 0,
min_compute_capability: float = 0.0,
warn_below_vram_bytes: int = 0,
artifacts: tuple[RuntimeArtifact, ...] = (),
)
The pinned set of fetchable runtime builds.
Attributes:
| Name | Type | Description |
|---|---|---|
build |
str
|
The llama.cpp release tag every variant here comes from. |
generated |
str
|
When the table was pinned. |
release_url |
str
|
The upstream release page. |
cuda_toolkit |
str
|
CUDA version the pinned CUDA build links against. |
min_cuda_driver_major |
int
|
Driver major version that toolkit requires. |
min_compute_capability |
float
|
Lowest GPU compute capability the build supports. |
warn_below_vram_bytes |
int
|
Below this, CUDA works but is not worth the download. |
artifacts |
tuple[RuntimeArtifact, ...]
|
Every pinned variant. |
anyinfer.local.RuntimeArtifact
dataclass
¶
RuntimeArtifact(
platform: str,
backend: AcceleratorKind,
filename: str,
url: str,
sha256: str,
size_bytes: int | None = None,
companions: tuple[RuntimeArtifact, ...] = (),
)
One downloadable runtime archive.
Attributes:
| Name | Type | Description |
|---|---|---|
platform |
str
|
Platform key this build targets ( |
backend |
AcceleratorKind
|
Acceleration family the build was compiled for. |
filename |
str
|
Archive file name. |
url |
str
|
Where to fetch it. |
sha256 |
str
|
Expected digest of the archive. |
size_bytes |
int | None
|
Expected archive size. |
companions |
tuple[RuntimeArtifact, ...]
|
Extra archives unpacked into the same directory — the CUDA runtime libraries ship separately from the llama.cpp build. |
anyinfer.local.RuntimeManifest
dataclass
¶
RuntimeManifest(
backend: AcceleratorKind,
build: str,
architecture: str,
executable: Path,
directory: Path,
)
A validated runtime.json describing an installed variant.
Attributes:
| Name | Type | Description |
|---|---|---|
backend |
AcceleratorKind
|
The acceleration family this build targets. |
build |
str
|
The llama.cpp build id it was cut from. |
architecture |
str
|
The machine architecture it runs on. |
executable |
Path
|
Absolute path to |
directory |
Path
|
The variant directory. |
anyinfer.local.InstallReport
dataclass
¶
InstallReport(
backend: AcceleratorKind,
build: str,
directory: Path,
executable: Path,
downloaded_bytes: int = 0,
reused: bool = False,
warnings: tuple[str, ...] = (),
)
The outcome of installing a runtime variant.
Attributes:
| Name | Type | Description |
|---|---|---|
backend |
AcceleratorKind
|
Which variant was installed. |
build |
str
|
The build id it came from. |
directory |
Path
|
Where it landed. |
executable |
Path
|
The |
downloaded_bytes |
int
|
Bytes actually transferred (zero when everything was cached). |
reused |
bool
|
Whether an already-valid install was kept. |
warnings |
tuple[str, ...]
|
Non-blocking notes, including CUDA precondition warnings. |
Tuning¶
anyinfer.local.plan_server ¶
plan_server(
hardware: HardwareProfile,
model: TuningInputs,
*,
posture: Posture = "balanced",
) -> ServerPlan
Derive a server plan from hardware, model facts, and posture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hardware
|
HardwareProfile
|
The detected profile. Unknown fields make the plan more conservative, never more optimistic. |
required |
model
|
TuningInputs
|
What is known about the model being served. |
required |
posture
|
Posture
|
How much of the machine to commit. |
'balanced'
|
Returns:
| Type | Description |
|---|---|
ServerPlan
|
A plan whose memory estimate the caller can check before spawning. |
anyinfer.local.TuningInputs
dataclass
¶
TuningInputs(
artifact_size_bytes: int | None = None,
parameter_size: str | None = None,
max_context: int | None = None,
requested_context: int | None = None,
)
What the tuner needs to know about the model being served.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_size_bytes |
int | None
|
On-disk size of the weights; they must be resident too. |
parameter_size |
str | None
|
Parameter class ( |
max_context |
int | None
|
Upper bound from the model itself, when known. |
requested_context |
int | None
|
An explicit context the caller wants, overriding the ladder. |
anyinfer.local.ServerPlan
dataclass
¶
ServerPlan(
context_size: int,
parallel: int = 1,
threads: int = 4,
batch_size: int = 512,
ubatch_size: int = 128,
gpu_layers: int = 0,
cache_type_k: str = "f16",
cache_type_v: str = "f16",
flash_attention: bool = False,
estimated_kv_bytes: int = 0,
estimated_total_bytes: int = 0,
posture: Posture = "balanced",
rationale: tuple[str, ...] = (),
)
A concrete, explainable llama-server configuration.
Attributes:
| Name | Type | Description |
|---|---|---|
context_size |
int
|
|
parallel |
int
|
|
threads |
int
|
|
batch_size |
int
|
|
ubatch_size |
int
|
|
gpu_layers |
int
|
|
cache_type_k |
str
|
|
cache_type_v |
str
|
|
flash_attention |
bool
|
Whether to request flash attention. |
estimated_kv_bytes |
int
|
Predicted KV-cache footprint, for admission control. |
estimated_total_bytes |
int
|
Weights plus KV cache. |
posture |
Posture
|
The posture this plan was derived under. |
rationale |
tuple[str, ...]
|
Human-readable notes explaining the choices. |
server_arguments ¶
server_arguments(
model_path: str, *, host: str, port: int
) -> list[str]
Render the plan as llama-server CLI arguments.
--jinja is always on: without it llama-server cannot apply a model's chat
template, and tool calling silently does not work at all.
anyinfer.local.Posture
module-attribute
¶
Posture = Literal['conservative', 'balanced', 'aggressive']
How much of the machine the user is willing to spend on inference.
anyinfer.local.CONTEXT_LADDER
module-attribute
¶
CONTEXT_LADDER: tuple[int, ...] = (
8192,
16384,
32768,
65536,
)
Context sizes to consider, smallest first. The largest that fits wins.
anyinfer.local.kv_bytes_per_token ¶
kv_bytes_per_token(
parameter_size: str | None, cache_type: str
) -> int
Estimate KV-cache bytes per token for a model class and cache precision.
Fit and variant selection¶
Whether a model will run on this machine, and which quantization to acquire for it. Both are advisory and both explain themselves.
anyinfer.local.classify_fit ¶
classify_fit(
entry: SizedEntry,
hardware: HardwareProfile | None,
*,
posture: Posture = "balanced",
backend: Backend | None = None,
) -> ModelFit
Classify one catalog entry against a machine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry
|
SizedEntry
|
The catalog model, with its stored memory estimates. |
required |
hardware
|
HardwareProfile | None
|
The profile to budget against. |
required |
posture
|
Posture
|
How much of the machine to commit; matches the tuner's postures. |
'balanced'
|
backend
|
Backend | None
|
The runtime variant that would actually drive this. Used only to surface the upgrade path when a faster one is available but not installed. |
None
|
Returns:
| Type | Description |
|---|---|
ModelFit
|
A fit level with reasons. Never raises. |
anyinfer.local.ModelFit
dataclass
¶
ModelFit(
level: FitLevel,
reasons: tuple[str, ...] = (),
headroom_bytes: int | None = None,
)
How a model relates to a machine's memory.
Attributes:
| Name | Type | Description |
|---|---|---|
level |
FitLevel
|
The classification. |
reasons |
tuple[str, ...]
|
Human-readable notes, mirroring |
headroom_bytes |
int | None
|
Budget minus requirement for the level that was chosen; negative
when nothing fit, |
anyinfer.local.FitLevel
module-attribute
¶
FitLevel = Literal['gpu', 'cpu', 'tight', 'no', 'unknown']
How well a model fits: fully offloaded, CPU-resident, marginal, impossible, or unknown.
anyinfer.local.memory_budget ¶
memory_budget(
hardware: HardwareProfile,
*,
posture: Posture = "balanced",
) -> tuple[int | None, int | None]
Return (vram_budget, ram_budget) in bytes for a posture.
None means "not determinable". Free memory is preferred over total when the platform
reported it, because a device already hosting a desktop compositor does not have its
nameplate VRAM available. Unified memory reports no separate VRAM budget: it is the
RAM budget, and counting it twice is how a plan overcommits an Apple Silicon machine.
anyinfer.local.sort_by_fit ¶
sort_by_fit(
pairs: Sequence[tuple[_EntryT, ModelFit]],
) -> list[tuple[_EntryT, ModelFit]]
Order entries best-fit-first, then by descending headroom, then by id.
Ties broken deterministically so the same catalog and the same machine always produce the same listing — a browsing UI that reshuffles between calls is unusable. Generic in the entry type so a caller keeps whatever it put in, rather than having its rows widened to the protocol.
anyinfer.local.SizedEntry ¶
Bases: Protocol
The subset of a catalog model this module needs.
anyinfer.local.select_variant ¶
select_variant(
variants: Sequence[SelectableVariant],
hardware: HardwareProfile | None,
*,
engine: str | None = None,
parameter_size: str | None = None,
backend: Backend | None = None,
prefs: VariantPrefs | None = None,
) -> VariantChoice | None
Choose the best quantization this machine can actually run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variants
|
Sequence[SelectableVariant]
|
The model's ladder, in any order. |
required |
hardware
|
HardwareProfile | None
|
The machine to budget against. |
required |
engine
|
str | None
|
Restrict to one engine's variants. |
None
|
parameter_size
|
str | None
|
Parameter class, for the KV-cache cost. |
None
|
backend
|
Backend | None
|
The runtime that would drive this, used to surface an upgrade path. |
None
|
prefs
|
VariantPrefs | None
|
Selection preferences. |
None
|
Returns:
| Type | Description |
|---|---|
VariantChoice | None
|
The chosen rung, or |
VariantChoice | None
|
|
anyinfer.local.evaluate_variants ¶
evaluate_variants(
variants: Sequence[SelectableVariant],
hardware: HardwareProfile | None,
*,
engine: str | None = None,
parameter_size: str | None = None,
backend: Backend | None = None,
prefs: VariantPrefs | None = None,
) -> tuple[
VariantChoice | None, tuple[tuple[str, str], ...]
]
Choose a rung and return why every other rung was passed over.
The rejections matter even — especially — when nothing was chosen: "no quantization
fits" is only useful advice if it comes with the numbers behind it. select_variant
is the convenience wrapper for callers that only need the choice.
Returns:
| Type | Description |
|---|---|
VariantChoice | None
|
|
tuple[tuple[str, str], ...]
|
which is a real answer, not a failure — the caller should then offer a smaller |
tuple[VariantChoice | None, tuple[tuple[str, str], ...]]
|
model. |
anyinfer.local.VariantChoice
dataclass
¶
VariantChoice(
variant_id: str,
quantization: str,
fit: ModelFit,
engine: str = "llama.cpp",
est_file_bytes: int | None = None,
reasons: tuple[str, ...] = (),
rejected: tuple[tuple[str, str], ...] = (),
tensor_parallel_size: int = 1,
gpu_memory_utilization: float | None = None,
)
The chosen rung, and why the others were not.
Attributes:
| Name | Type | Description |
|---|---|---|
variant_id |
str
|
The chosen variant. |
quantization |
str
|
What it ships. |
fit |
ModelFit
|
How it fits this machine. |
engine |
str
|
Which engine it targets. |
est_file_bytes |
int | None
|
What it will cost to download. |
reasons |
tuple[str, ...]
|
Why this rung, and why not the next one up. |
rejected |
tuple[tuple[str, str], ...]
|
|
tensor_parallel_size |
int
|
How many devices a vLLM launch should span. |
gpu_memory_utilization |
float | None
|
The utilization the budget assumed. |
anyinfer.local.VariantPrefs
dataclass
¶
VariantPrefs(
posture: Posture = "balanced",
context: int = _DEFAULT_CONTEXT,
allow_low_quality: bool = False,
allow_multi_gpu: bool = False,
gpu_memory_utilization: float = 0.9,
max_download_bytes: int | None = None,
)
How aggressively to choose.
Attributes:
| Name | Type | Description |
|---|---|---|
posture |
Posture
|
Memory posture, matching the tuner's. |
context |
int
|
Context length to budget the KV cache for. |
allow_low_quality |
bool
|
Permit rungs below Q4_K_M. |
allow_multi_gpu |
bool
|
Let vLLM sum VRAM across identical devices, and emit a
|
gpu_memory_utilization |
float
|
vLLM's fraction of device memory to plan against. |
max_download_bytes |
int | None
|
Refuse variants larger than this, whatever fits in memory. |
Acquisition and the model store¶
Getting weights onto this disk and finding them again. Model acquisition lives here, never in a provider adapter.
anyinfer.local.acquire ¶
Getting model weights onto this disk: plan → preflight → fetch → verify → place.
One engine for both artifact shapes. A GGUF variant is a file set whose handle is the first shard; a Hugging Face snapshot is a directory whose handle is the directory. The file list and the handle differ; the machinery — resume, digest verification, locking, cancellation, and progress accounting — does not, and those are exactly the things that are expensive to get right twice.
Progress is reported for the whole acquisition, not per file. A sharded artifact whose
byte counter restarts at zero on every shard is worse than no progress bar, because a user
cannot tell a restart from a stall. AcquisitionProgress therefore carries aggregate
totals, counts bytes that were already on disk, and knows its full size before the first
byte arrives — the payoff for pinning sizes in the catalog.
AcquisitionPhase
module-attribute
¶
AcquisitionPhase = Literal[
"resolving",
"planning",
"downloading",
"verifying",
"placing",
"done",
]
Where an acquisition is. Every transition produces a callback, unthrottled.
ProgressSink
module-attribute
¶
ProgressSink = Callable[[AcquisitionProgress], None]
Receives AcquisitionProgress. See its docstring for the threading contract.
AcquisitionProgress
dataclass
¶
AcquisitionProgress(
model_id: str,
variant_id: str,
phase: AcquisitionPhase,
file_index: int = 0,
file_count: int = 0,
filename: str = "",
file_downloaded_bytes: int = 0,
file_total_bytes: int | None = None,
total_downloaded_bytes: int = 0,
total_bytes: int | None = None,
total_is_estimate: bool = False,
session_bytes: int = 0,
bytes_per_second: float | None = None,
eta_seconds: float | None = None,
)
One progress report for a whole acquisition.
The sink may be invoked from a worker thread. It must not block, must not raise, and must not re-enter the client — a progress bar that can deadlock a download is a bug with no upside. A sink that raises anyway is caught, recorded once as a warning on the report, and then dropped for the rest of the run.
Attributes:
| Name | Type | Description |
|---|---|---|
model_id |
str
|
The catalog model being acquired. |
variant_id |
str
|
The variant being acquired. |
phase |
AcquisitionPhase
|
Which stage this report came from. |
file_index |
int
|
1-based index of the file that most recently advanced. |
file_count |
int
|
How many files this acquisition covers. |
filename |
str
|
Name of the file that most recently advanced. |
file_downloaded_bytes |
int
|
Bytes present for that file, including a resumed prefix. |
file_total_bytes |
int | None
|
Expected size of that file, when known. |
total_downloaded_bytes |
int
|
Bytes present across every file — including what was already on disk, so resuming a 90%-complete transfer reports 90%, not 0%. |
total_bytes |
int | None
|
Total expected bytes, known before the first byte arrives whenever the catalog or the listing API supplied sizes. |
total_is_estimate |
bool
|
True when any file's size came from a guess rather than a pinned or reported figure. |
session_bytes |
int
|
Bytes this run actually transferred, which is what rate and ETA are derived from. |
bytes_per_second |
float | None
|
Transfer rate, or |
eta_seconds |
float | None
|
Seconds remaining, or |
fraction
property
¶
fraction: float | None
Completion as a fraction, or None when the total is unknown.
AcquisitionPlan
dataclass
¶
AcquisitionPlan(
entry_id: str,
model_id: str,
variant_id: str,
kind: str,
engine: str,
quantization: str,
directory: str,
handle: str,
files: tuple[RemoteFile, ...],
already_have_bytes: int = 0,
total_bytes: int | None = None,
warnings: tuple[str, ...] = (),
satisfied: bool = False,
revision: str | None = None,
repo: str | None = None,
license: str = "",
)
What an acquisition will do, before it does any of it.
Attributes:
| Name | Type | Description |
|---|---|---|
entry_id |
str
|
The store entry that will be written. |
model_id |
str
|
The catalog model. |
variant_id |
str
|
The catalog variant. |
kind |
str
|
|
engine |
str
|
Which engine the result is for. |
quantization |
str
|
What will be on disk. |
directory |
str
|
Store-relative destination. |
handle |
str
|
Store-relative engine handle. |
files |
tuple[RemoteFile, ...]
|
Every file, resolved. |
already_have_bytes |
int
|
Bytes already on disk — verified files plus resumable
|
total_bytes |
int | None
|
Total size, or |
warnings |
tuple[str, ...]
|
Notes from resolution. |
satisfied |
bool
|
True when everything is already present and verified. |
AcquisitionReport
dataclass
¶
AcquisitionReport(
plan: AcquisitionPlan,
entry: StoreEntry | None = None,
downloaded_bytes: int = 0,
reused: bool = False,
cancelled: bool = False,
dry_run: bool = False,
warnings: tuple[str, ...] = (),
)
The outcome of an acquisition.
Attributes:
| Name | Type | Description |
|---|---|---|
plan |
AcquisitionPlan
|
What was planned. |
entry |
StoreEntry | None
|
The registered store entry, or |
downloaded_bytes |
int
|
Bytes transferred by this run. |
reused |
bool
|
True when nothing had to be transferred. |
cancelled |
bool
|
True when the caller stopped it. Partial transfers are kept. |
dry_run |
bool
|
True when nothing was written. |
warnings |
tuple[str, ...]
|
Everything the caller should know. |
AcquisitionRequest
dataclass
¶
AcquisitionRequest(
ref: SourceRef,
model_id: str,
variant_id: str = "",
kind: str = "gguf",
engine: str = "llama.cpp",
quantization: str = "",
license: str = "",
token: str | None = None,
max_concurrent_files: int = 3,
allow_unverified: bool = False,
enforce_license: bool = False,
launch_hints: Mapping[str, Any] = dict(),
)
Everything one acquisition needs.
Attributes:
| Name | Type | Description |
|---|---|---|
ref |
SourceRef
|
Where the bytes come from. |
model_id |
str
|
Catalog model id, for the index and for progress reports. |
variant_id |
str
|
Catalog variant id. |
kind |
str
|
|
engine |
str
|
Which engine the result is for. |
quantization |
str
|
What will be on disk. |
license |
str
|
License id, checked when |
token |
str | None
|
Credential for the source, when it needs one. |
max_concurrent_files |
int
|
How many transfers may run at once. |
allow_unverified |
bool
|
Accept files no digest can check. |
enforce_license |
bool
|
Refuse licenses outside the allowlist. |
launch_hints |
Mapping[str, Any]
|
Advisory engine arguments to attach to the result. |
plan_acquisition
async
¶
plan_acquisition(
request: AcquisitionRequest,
*,
store: ModelStore | None = None,
client: AsyncClient | None = None,
) -> AcquisitionPlan
Resolve a source and work out exactly what would be transferred.
Separated from the transfer so an application can put a real confirmation dialog in front of a forty-gigabyte download instead of discovering the size afterwards.
acquire
async
¶
acquire(
request: AcquisitionRequest,
*,
store: ModelStore | None = None,
client: AsyncClient | None = None,
progress: ProgressSink | None = None,
plan: AcquisitionPlan | None = None,
dry_run: bool = False,
cancel_check: Callable[[], bool] | None = None,
) -> AcquisitionReport
Acquire a model variant into the store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
AcquisitionRequest
|
What to acquire and from where. |
required |
store
|
ModelStore | None
|
The destination store; defaults to the standard root. |
None
|
client
|
AsyncClient | None
|
An |
None
|
progress
|
ProgressSink | None
|
Aggregate progress sink. |
None
|
plan
|
AcquisitionPlan | None
|
A plan from |
None
|
dry_run
|
bool
|
Resolve and report sizes without writing anything. |
False
|
cancel_check
|
Callable[[], bool] | None
|
Polled between chunks; returning |
None
|
Returns:
| Type | Description |
|---|---|
AcquisitionReport
|
The report, naming the registered entry. |
Raises:
| Type | Description |
|---|---|
LocalRuntimeError
|
On a transfer failure, a digest mismatch, or insufficient disk. |
acquire_sync ¶
acquire_sync(
request: AcquisitionRequest,
*,
store: ModelStore | None = None,
progress: ProgressSink | None = None,
dry_run: bool = False,
cancel_check: Callable[[], bool] | None = None,
) -> AcquisitionReport
Blocking wrapper around acquire, for callers with no event loop.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called from inside a running event loop, where it would deadlock.
Use |
launch_hints_for ¶
launch_hints_for(
entry: StoreEntry,
*,
path: Path,
context_size: int | None = None,
gpu_layers: int | None = None,
tensor_parallel_size: int | None = None,
gpu_memory_utilization: float | None = None,
) -> dict[str, Any]
Build the advisory engine arguments that accompany a located model.
Data, not process control. These are keys a caller — the llama.cpp supervisor, a future vLLM launcher, or a user pasting a command line — turns into arguments. Producing them from numbers already computed is translation; launching is not, and nothing here starts a process.
anyinfer.local.acquire_sync ¶
acquire_sync(
request: AcquisitionRequest,
*,
store: ModelStore | None = None,
progress: ProgressSink | None = None,
dry_run: bool = False,
cancel_check: Callable[[], bool] | None = None,
) -> AcquisitionReport
Blocking wrapper around acquire, for callers with no event loop.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called from inside a running event loop, where it would deadlock.
Use |
anyinfer.local.plan_acquisition
async
¶
plan_acquisition(
request: AcquisitionRequest,
*,
store: ModelStore | None = None,
client: AsyncClient | None = None,
) -> AcquisitionPlan
Resolve a source and work out exactly what would be transferred.
Separated from the transfer so an application can put a real confirmation dialog in front of a forty-gigabyte download instead of discovering the size afterwards.
anyinfer.local.AcquisitionPlan
dataclass
¶
AcquisitionPlan(
entry_id: str,
model_id: str,
variant_id: str,
kind: str,
engine: str,
quantization: str,
directory: str,
handle: str,
files: tuple[RemoteFile, ...],
already_have_bytes: int = 0,
total_bytes: int | None = None,
warnings: tuple[str, ...] = (),
satisfied: bool = False,
revision: str | None = None,
repo: str | None = None,
license: str = "",
)
What an acquisition will do, before it does any of it.
Attributes:
| Name | Type | Description |
|---|---|---|
entry_id |
str
|
The store entry that will be written. |
model_id |
str
|
The catalog model. |
variant_id |
str
|
The catalog variant. |
kind |
str
|
|
engine |
str
|
Which engine the result is for. |
quantization |
str
|
What will be on disk. |
directory |
str
|
Store-relative destination. |
handle |
str
|
Store-relative engine handle. |
files |
tuple[RemoteFile, ...]
|
Every file, resolved. |
already_have_bytes |
int
|
Bytes already on disk — verified files plus resumable
|
total_bytes |
int | None
|
Total size, or |
warnings |
tuple[str, ...]
|
Notes from resolution. |
satisfied |
bool
|
True when everything is already present and verified. |
anyinfer.local.AcquisitionProgress
dataclass
¶
AcquisitionProgress(
model_id: str,
variant_id: str,
phase: AcquisitionPhase,
file_index: int = 0,
file_count: int = 0,
filename: str = "",
file_downloaded_bytes: int = 0,
file_total_bytes: int | None = None,
total_downloaded_bytes: int = 0,
total_bytes: int | None = None,
total_is_estimate: bool = False,
session_bytes: int = 0,
bytes_per_second: float | None = None,
eta_seconds: float | None = None,
)
One progress report for a whole acquisition.
The sink may be invoked from a worker thread. It must not block, must not raise, and must not re-enter the client — a progress bar that can deadlock a download is a bug with no upside. A sink that raises anyway is caught, recorded once as a warning on the report, and then dropped for the rest of the run.
Attributes:
| Name | Type | Description |
|---|---|---|
model_id |
str
|
The catalog model being acquired. |
variant_id |
str
|
The variant being acquired. |
phase |
AcquisitionPhase
|
Which stage this report came from. |
file_index |
int
|
1-based index of the file that most recently advanced. |
file_count |
int
|
How many files this acquisition covers. |
filename |
str
|
Name of the file that most recently advanced. |
file_downloaded_bytes |
int
|
Bytes present for that file, including a resumed prefix. |
file_total_bytes |
int | None
|
Expected size of that file, when known. |
total_downloaded_bytes |
int
|
Bytes present across every file — including what was already on disk, so resuming a 90%-complete transfer reports 90%, not 0%. |
total_bytes |
int | None
|
Total expected bytes, known before the first byte arrives whenever the catalog or the listing API supplied sizes. |
total_is_estimate |
bool
|
True when any file's size came from a guess rather than a pinned or reported figure. |
session_bytes |
int
|
Bytes this run actually transferred, which is what rate and ETA are derived from. |
bytes_per_second |
float | None
|
Transfer rate, or |
eta_seconds |
float | None
|
Seconds remaining, or |
fraction
property
¶
fraction: float | None
Completion as a fraction, or None when the total is unknown.
anyinfer.local.AcquisitionReport
dataclass
¶
AcquisitionReport(
plan: AcquisitionPlan,
entry: StoreEntry | None = None,
downloaded_bytes: int = 0,
reused: bool = False,
cancelled: bool = False,
dry_run: bool = False,
warnings: tuple[str, ...] = (),
)
The outcome of an acquisition.
Attributes:
| Name | Type | Description |
|---|---|---|
plan |
AcquisitionPlan
|
What was planned. |
entry |
StoreEntry | None
|
The registered store entry, or |
downloaded_bytes |
int
|
Bytes transferred by this run. |
reused |
bool
|
True when nothing had to be transferred. |
cancelled |
bool
|
True when the caller stopped it. Partial transfers are kept. |
dry_run |
bool
|
True when nothing was written. |
warnings |
tuple[str, ...]
|
Everything the caller should know. |
anyinfer.local.AcquisitionPhase
module-attribute
¶
AcquisitionPhase = Literal[
"resolving",
"planning",
"downloading",
"verifying",
"placing",
"done",
]
Where an acquisition is. Every transition produces a callback, unthrottled.
anyinfer.local.ProgressSink
module-attribute
¶
ProgressSink = Callable[[AcquisitionProgress], None]
Receives AcquisitionProgress. See its docstring for the threading contract.
anyinfer.local.ModelStore ¶
ModelStore(root: Path | None = None)
A directory of acquired models, with an index over it.
Not thread-safe by construction; correctness across processes comes from the same cooperative file lock downloads use, taken around every index mutation.
staging_dir ¶
staging_dir(entry_id: str) -> Path
Where an in-progress acquisition writes its .part files.
resolve_within ¶
resolve_within(entry: StoreEntry, relative: str) -> Path
Resolve a path inside an entry, refusing anything that escapes it.
Called for every file before it is opened. Names come from a remote API, so this is the containment gate, applied after resolution so a symlink cannot step outside.
Raises:
| Type | Description |
|---|---|
ConfigError
|
If the path is unsafe or resolves outside the entry directory. |
load_index ¶
load_index() -> dict[str, StoreEntry]
Read the index, tolerating absence and corruption.
A store whose index cannot be parsed reports as empty rather than raising: the
files are still there, rebuild_index can recover them, and refusing to work at
all because a cache file got truncated would be the wrong trade.
register ¶
register(entry: StoreEntry) -> StoreEntry
Add or replace an index entry.
Called only after every file has been verified: a half-complete multi-file set is
never registered, which is what makes locate trustworthy.
unregister ¶
unregister(entry_id: str) -> StoreEntry | None
Drop an index entry without touching files.
rebuild_index ¶
rebuild_index() -> list[StoreEntry]
Drop entries whose files are gone, and re-stat the rest.
The recovery path for a user who deleted a directory by hand. It does not invent entries for unknown directories — an unrecognized tree could be anything, and registering it would be a claim about bytes nobody checked.
find ¶
find(
model_id: str,
*,
variant_id: str | None = None,
quantization: str | None = None,
engine: str | None = None,
) -> StoreEntry | None
The best registered entry matching a model and optional constraints.
"Best" is the most recently installed match, so re-acquiring at a different quantization changes what a bare model id resolves to — which is what a user who just downloaded something expects.
locate ¶
locate(
model_id: str,
*,
variant_id: str | None = None,
quantization: str | None = None,
engine: str | None = None,
verify: bool = False,
launch_hints: Mapping[str, Any] | None = None,
) -> ResolvedModel | None
Find a stored model and return a path an engine can be launched against.
No network I/O, ever. Verification is the deliberate exception to "always check":
hashing forty gigabytes on every request would be absurd, so the rule is verify on
install and on adoption, then on lookup compare size and mtime against the index
and re-hash only on a mismatch. verify=True forces a full re-hash.
Returns:
| Type | Description |
|---|---|
ResolvedModel | None
|
The located model, or |
check ¶
check(
entry: StoreEntry, *, deep: bool = False
) -> tuple[str, ...]
Report what is wrong with a stored entry, cheaply by default.
The shallow check compares size and mtime against the index; the deep check re-hashes. Either way an empty result means "as installed".
disk_usage ¶
disk_usage() -> int
Total bytes the registered entries occupy, excluding external ones.
remove ¶
remove(entry_id: str) -> RemovalReport
Delete an entry's files and unregister it.
An external entry — one adopted from somebody else's cache — is only unregistered. Deleting files this store never wrote would be overstepping.
clear_staging ¶
clear_staging(entry_id: str) -> None
Remove an entry's staging directory and its partial transfers.
adopt_legacy_flat ¶
adopt_legacy_flat(
artifacts: Sequence[Any],
) -> list[StoreEntry]
Register pre-existing flat-layout GGUF files without moving or re-fetching them.
Earlier builds wrote <root>/<filename>.gguf with no revision in the path. Those
files are perfectly good bytes a user paid bandwidth for, so the store adopts them
where they lie — but only after verifying each one against its catalog hash, so
adoption is never a lie about what is on disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
artifacts
|
Sequence[Any]
|
Pinned catalog artifacts to look for, each with |
required |
Returns:
| Type | Description |
|---|---|
list[StoreEntry]
|
Newly registered entries. |
adopt_external ¶
adopt_external(
directory: Path,
*,
entry_id: str,
model_id: str,
variant_id: str,
kind: str = "hf_repo",
engine: str = "vllm",
quantization: str = "",
source: Mapping[str, Any] | None = None,
expected: Mapping[str, str] | None = None,
) -> StoreEntry | None
Register a directory this store does not own, if every file checks out.
The Hugging Face cache case. We do not adopt another library's layout for our own
writes — it is their private implementation detail — but re-downloading forty
gigabytes the user already has is user-hostile. Adopted entries are marked
StoreEntry.external, are never written to, and are never deleted by remove.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
Path
|
The existing snapshot directory. |
required |
entry_id
|
str
|
Id to register it under. |
required |
model_id
|
str
|
Catalog model this realizes. |
required |
variant_id
|
str
|
Catalog variant this realizes. |
required |
kind
|
str
|
Artifact kind. |
'hf_repo'
|
engine
|
str
|
Engine the variant targets. |
'vllm'
|
quantization
|
str
|
What is on disk. |
''
|
source
|
Mapping[str, Any] | None
|
Provenance to record. |
None
|
expected
|
Mapping[str, str] | None
|
Per-relative-path sha256 that every file must match. Adoption is refused outright when this is empty — an unverified adoption is a guess. |
None
|
Returns:
| Type | Description |
|---|---|
StoreEntry | None
|
The registered entry, or |
anyinfer.local.StoreEntry
dataclass
¶
StoreEntry(
id: str,
kind: str = "gguf",
model_id: str = "",
variant_id: str = "",
quantization: str = "",
engine: str = "llama.cpp",
source: Mapping[str, Any] = dict(),
directory: str = "",
handle: str = "",
files: tuple[StoredFile, ...] = (),
license: str = "",
installed_at: float = 0.0,
last_used_at: float = 0.0,
external: bool = False,
warnings: tuple[str, ...] = (),
)
One acquired model in the store.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable entry id, also the lock and staging name. |
kind |
str
|
|
model_id |
str
|
The catalog model this realizes, when it came from the catalog. |
variant_id |
str
|
The catalog variant, when it came from the catalog. |
quantization |
str
|
The quantization on disk. |
engine |
str
|
Which engine this variant is for. |
source |
Mapping[str, Any]
|
How it was acquired, including the resolved immutable revision. |
directory |
str
|
Where the files live, relative to the store root. |
handle |
str
|
The path an engine is pointed at, relative to the store root — the first shard for GGUF, the directory for a snapshot. |
files |
tuple[StoredFile, ...]
|
Every file, with digests. |
license |
str
|
License id recorded at acquisition. |
installed_at |
float
|
Unix timestamp of successful registration. |
last_used_at |
float
|
Unix timestamp of the most recent |
external |
bool
|
True when the bytes are owned by something else (an adopted Hugging Face cache). Removal only unregisters an external entry; it never deletes. |
warnings |
tuple[str, ...]
|
Anything the user should know — unverified files, most importantly. |
verified
property
¶
verified: bool
Whether every file was verified against a digest at install time.
anyinfer.local.ResolvedModel
dataclass
¶
ResolvedModel(
entry_id: str,
kind: str,
path: Path,
quantization: str | None = None,
engine: str = "llama.cpp",
verified: bool = False,
warnings: tuple[str, ...] = (),
launch_hints: Mapping[str, Any] = dict(),
)
A located model, ready to launch an engine against.
Attributes:
| Name | Type | Description |
|---|---|---|
entry_id |
str
|
The store entry. |
kind |
str
|
|
path |
Path
|
The engine handle — a file for GGUF, a directory for a snapshot. |
quantization |
str | None
|
What is actually on disk. |
engine |
str
|
Which engine this variant is for. |
verified |
bool
|
Whether every file was verified. |
warnings |
tuple[str, ...]
|
Notes carried from the entry. |
launch_hints |
Mapping[str, Any]
|
Engine-shaped arguments a caller can turn into a command line. Advisory data, not process control: this module locates weights, it does not start servers. |
anyinfer.local.RemovalReport
dataclass
¶
RemovalReport(
entry_id: str,
removed: bool = False,
freed_bytes: int = 0,
external: bool = False,
)
The outcome of removing an entry.
Attributes:
| Name | Type | Description |
|---|---|---|
entry_id |
str
|
What was removed. |
removed |
bool
|
Whether an entry was found and unregistered. |
freed_bytes |
int
|
Bytes reclaimed; zero for an external entry, which is only unregistered. |
external |
bool
|
Whether the files were left alone because something else owns them. |
Sources¶
Where weights come from. Adding an internal mirror is a resolver, not a dependency.
anyinfer.local.SourceRef
dataclass
¶
SourceRef(
resolver: str = "huggingface",
repo: str | None = None,
revision: str | None = None,
files: tuple[str, ...] = (),
digests: Mapping[str, str] = dict(),
sizes: Mapping[str, int] = dict(),
urls: tuple[str, ...] = (),
include: tuple[str, ...] = (),
exclude: tuple[str, ...] = (),
path: str | None = None,
)
A declarative pointer at a set of remote (or already-local) files.
Attributes:
| Name | Type | Description |
|---|---|---|
resolver |
str
|
Which resolver understands this reference. |
repo |
str | None
|
Repository id, for repository-shaped resolvers ( |
revision |
str | None
|
Branch, tag, or — preferably — an immutable commit sha. |
files |
tuple[str, ...]
|
Explicit file list. Empty means "whatever the include globs match". |
digests |
Mapping[str, str]
|
Per-file expected sha256, when the catalog pinned them. |
sizes |
Mapping[str, int]
|
Per-file expected byte counts, when the catalog pinned them. |
urls |
tuple[str, ...]
|
Direct download URLs, for the |
include |
tuple[str, ...]
|
Glob patterns selecting files from a repository listing. |
exclude |
tuple[str, ...]
|
Glob patterns removing files the include list matched. |
path |
str | None
|
An existing on-disk location, for the |
anyinfer.local.SourceResolver ¶
Bases: Protocol
Turns a SourceRef into a ResolvedArtifact.
scheme
instance-attribute
¶
scheme: str
The SourceRef.resolver value this implementation answers to.
resolve
async
¶
resolve(
ref: SourceRef,
*,
token: str | None = None,
client: Any | None = None,
) -> ResolvedArtifact
Expand a reference into a concrete file list.
client is an optional httpx2.AsyncClient the caller already owns. Passing
it keeps resolution and the subsequent transfer on one connection pool — and is
what makes a resolver testable against a mock transport.
anyinfer.local.ResolvedArtifact
dataclass
¶
ResolvedArtifact(
resolver: str,
files: tuple[RemoteFile, ...] = (),
repo: str | None = None,
revision: str | None = None,
warnings: tuple[str, ...] = (),
)
A concrete, ordered file list ready for acquisition.
Attributes:
| Name | Type | Description |
|---|---|---|
resolver |
str
|
Which resolver produced this. |
files |
tuple[RemoteFile, ...]
|
Every file to fetch, in acquisition order. |
repo |
str | None
|
The repository this came from, when applicable. |
revision |
str | None
|
The immutable revision this resolved to, when the resolver could determine one. A branch name is always resolved to a commit before use. |
warnings |
tuple[str, ...]
|
Anything the caller should know — unverifiable files, skipped pickle weights, and so on. |
total_bytes
property
¶
total_bytes: int | None
Sum of every file size, or None when any one is unknown.
anyinfer.local.RemoteFile
dataclass
¶
RemoteFile(
path: str,
url: str,
size_bytes: int | None = None,
digest: str = "",
digest_kind: DigestKind = "none",
)
One file a resolver decided belongs to an artifact.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
str
|
Destination path relative to the entry directory, POSIX-separated. |
url |
str
|
Where to fetch it. |
size_bytes |
int | None
|
Expected size, or |
digest |
str
|
Expected digest, lowercase hex. |
digest_kind |
DigestKind
|
How to compute |
Artifacts and downloads¶
anyinfer.local.GgufArtifact
dataclass
¶
GgufArtifact(
id: str,
files: tuple[GgufFile, ...],
license: str = "",
description: str = "",
parameter_size: str | None = None,
quantization: str | None = None,
est_ram_bytes: int | None = None,
est_vram_bytes: int | None = None,
)
A pinned, verifiable local model artifact.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable artifact id, the handle alias targets and the llama.cpp adapter use. |
files |
tuple[GgufFile, ...]
|
Every file the artifact comprises — one entry, or several for a sharded model. |
license |
str
|
License id; checked against the download allowlist for application-supplied entries. |
description |
str
|
Free text for display. |
parameter_size |
str | None
|
Parameter class ( |
quantization |
str | None
|
Quantization of the shipped weights ( |
est_ram_bytes |
int | None
|
Estimated memory needed on the CPU-only path. |
est_vram_bytes |
int | None
|
Estimated memory needed when fully offloaded. |
anyinfer.local.GgufFile
dataclass
¶
GgufFile(
filename: str,
url: str,
sha256: str = "",
size_bytes: int | None = None,
)
One file of a (possibly sharded) GGUF artifact.
Attributes:
| Name | Type | Description |
|---|---|---|
filename |
str
|
The name the file is stored under in the model directory. |
url |
str
|
Pinned download URL. |
sha256 |
str
|
Expected content hash. Empty means the file cannot be verified, and the downloader warns instead of checking. |
size_bytes |
int | None
|
Expected size, when known; feeds download progress totals. |
anyinfer.local.download_artifact ¶
download_artifact(
artifact: GgufArtifact,
*,
model_dir: Path | None = None,
progress: ProgressCallback | None = None,
client: Client | None = None,
enforce_license: bool = False,
) -> DownloadReport
Ensure every file of an artifact is present and verified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
artifact
|
GgufArtifact
|
The pinned catalog entry. |
required |
model_dir
|
Path | None
|
Destination directory; defaults to |
None
|
progress
|
ProgressCallback | None
|
Called as bytes arrive. |
None
|
client
|
Client | None
|
An |
None
|
enforce_license
|
bool
|
Reject artifacts whose license is not in
|
False
|
Returns:
| Type | Description |
|---|---|
DownloadReport
|
A report naming the on-disk files. |
Raises:
| Type | Description |
|---|---|
LocalRuntimeError
|
On a hash mismatch, a transfer failure, or a rejected license. |
anyinfer.local.iter_missing ¶
iter_missing(
artifacts: Iterable[GgufArtifact],
model_dir: Path | None = None,
) -> list[GgufArtifact]
Which artifacts are absent or fail verification.
anyinfer.local.verify_file ¶
verify_file(path: Path, expected_sha256: str) -> bool
Whether a file matches its expected hash.
An artifact with no recorded hash cannot be verified; its mere existence is accepted, and the caller is warned.
anyinfer.local.artifact_paths ¶
artifact_paths(
artifact: GgufArtifact, model_dir: Path | None = None
) -> tuple[Path, ...]
Where an artifact's files would live on disk.
anyinfer.local.default_model_dir ¶
default_model_dir() -> Path
Where downloaded artifacts live by default.
anyinfer.local.DownloadReport
dataclass
¶
DownloadReport(
artifact_id: str,
paths: tuple[Path, ...],
downloaded_bytes: int = 0,
reused: bool = False,
warnings: tuple[str, ...] = (),
)
The outcome of ensuring an artifact is present.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_id |
str
|
The artifact this report describes. |
paths |
tuple[Path, ...]
|
Where each of the artifact's files lives on disk, in declaration order. |
downloaded_bytes |
int
|
Bytes actually transferred; zero when everything was reused. |
reused |
bool
|
Whether every file was already present and verified, so nothing was fetched. |
warnings |
tuple[str, ...]
|
Non-fatal notes — files with no recorded hash, or files that failed verification and were re-downloaded. |
primary_path
property
¶
primary_path: Path
The file to hand to llama-server (the first shard of a sharded artifact).
anyinfer.local.ProgressCallback
module-attribute
¶
ProgressCallback = Callable[[str, int, int | None], None]
(artifact_id, downloaded_bytes, total_bytes_or_None).
anyinfer.local.ALLOWED_LICENSES
module-attribute
¶
ALLOWED_LICENSES = frozenset(
{
"apache-2.0",
"falcon-llm-2.0",
"gemma-terms",
"llama-3.1-community",
"llama-3.2-community",
"llama-3.3-community",
"mit",
"openrail-m",
}
)
Licenses permitted for catalog entries, compared case-insensitively.
The bundled catalog is curated; entries an application adds are checked so that a convenience feature cannot quietly redistribute weights under terms the user has not seen. Non-commercial and research-only terms are deliberately absent — an application that has accepted those terms adds the model through a catalog overlay, which is an explicit act.
anyinfer.local.license_allowed ¶
license_allowed(license_id: str) -> bool
Whether a license id is in ALLOWED_LICENSES, ignoring case.
Server supervision¶
anyinfer.local.ServerSupervisor ¶
ServerSupervisor(
*,
binary: Path | str = "llama-server",
hardware: HardwareProfile | None = None,
idle_ttl_s: float | None = 900.0,
max_resident: int = 1,
allow_remote_exposure: bool = False,
host: str = LOOPBACK_HOST,
health_timeout_s: float = _HEALTH_TIMEOUT_S,
on_lifecycle: LifecycleCallback | None = None,
)
Owns the llama-server processes for one client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
binary
|
Path | str
|
Path to the |
'llama-server'
|
hardware
|
HardwareProfile | None
|
Detected hardware, used for VRAM admission control. |
None
|
idle_ttl_s
|
float | None
|
Unload a server after this long with no active streams. |
900.0
|
max_resident
|
int
|
How many servers may run at once. Exceeding it evicts the least-recently-used idle server. |
1
|
allow_remote_exposure
|
bool
|
Bind a non-loopback address. Off by default: a local model server is loopback-only unless deliberately exposed. |
False
|
on_lifecycle
|
LifecycleCallback | None
|
Called with lifecycle events. |
None
|
acquire
async
¶
acquire(
model_key: str,
model_path: Path,
plan: ServerPlan,
*,
persist: bool = False,
) -> ManagedServer
Get a ready server for a model, starting or reusing one.
Blocks until the server answers its health probe. Concurrent callers requesting different models are serialized, so loads never overlap.
Raises:
| Type | Description |
|---|---|
LocalRuntimeError
|
If the model cannot fit, the binary is missing, or the server fails to become ready. |
set_hardware ¶
set_hardware(hardware: HardwareProfile) -> None
Late-bind the detected hardware profile.
Detection is deliberately lazy — probing at construction would tax clients that never run a local model — so the adapter hands the profile over once it has one. Admission control and backend fallback stay disabled until then.
resolve_binary ¶
resolve_binary() -> Path
Locate the llama-server executable without starting anything.
Public so a health probe can answer "could a server start?" cheaply.
Falls back to the best installed backend variant for this hardware when the configured name is not on PATH (a CUDA build in a known runtime directory beats a missing PATH entry).
Raises:
| Type | Description |
|---|---|
LocalRuntimeError
|
When no usable binary exists anywhere. |
collect_idle
async
¶
collect_idle() -> int
Stop servers idle beyond the TTL. Returns how many were stopped.
Call periodically. Servers with active streams are never collected, however long the generation has been running.
anyinfer.local.ManagedServer ¶
ManagedServer(handle: ServerHandle)
A context manager marking a server busy for the duration of a request.
This is what makes the idle timer honest: the server is busy while a stream is open, not merely while a request is arriving.
anyinfer.local.ServerHandle
dataclass
¶
ServerHandle(
model_key: str,
model_path: Path,
plan: ServerPlan,
host: str,
port: int,
process: Popen[bytes],
started_at: float,
log_tail: deque[str] = (
lambda: deque(maxlen=_LOG_TAIL_LINES)
)(),
active_streams: int = 0,
last_activity: float = time.monotonic(),
persist: bool = False,
stopping: bool = False,
)
A running llama-server and everything known about it.
model_key
instance-attribute
¶
model_key: str
The model this server serves; also its key in the supervisor's server table.
plan
instance-attribute
¶
plan: ServerPlan
The tuned launch plan; its memory estimate is what admission control committed.
host
instance-attribute
¶
host: str
Interface the server is bound to — loopback unless exposure was explicitly allowed.
process
instance-attribute
¶
process: Popen[bytes]
The supervised child, polled for liveness and terminated on stop.
log_tail
class-attribute
instance-attribute
¶
log_tail: deque[str] = field(
default_factory=lambda: deque(maxlen=_LOG_TAIL_LINES)
)
The child's most recent output lines, kept so failures can explain themselves.
active_streams
class-attribute
instance-attribute
¶
active_streams: int = 0
Open response streams. Nonzero means busy: the idle clock and eviction ignore it.
last_activity
class-attribute
instance-attribute
¶
last_activity: float = field(default_factory=time.monotonic)
Monotonic time of the last request or stream release; the idle clock's baseline.
persist
class-attribute
instance-attribute
¶
persist: bool = False
Exempt this server from idle collection and capacity eviction.
stopping
class-attribute
instance-attribute
¶
stopping: bool = False
Set while the supervisor is tearing this server down.
idle_seconds ¶
idle_seconds() -> float
How long this server has been idle. Zero while any stream is active.
anyinfer.local.LifecycleCallback
module-attribute
¶
LifecycleCallback = Callable[[ServerLifecycle], None]
Receives every lifecycle transition of a supervised server.
anyinfer.local.allocate_port ¶
allocate_port(host: str = LOOPBACK_HOST) -> int
Reserve an ephemeral port by binding and immediately releasing it.
Inherently racy, but the alternative — letting llama-server pick and then discovering which port it chose — requires parsing its log output, which is far more fragile.
anyinfer.local.LOOPBACK_HOST
module-attribute
¶
LOOPBACK_HOST = '127.0.0.1'
Local servers bind loopback only, unless the caller explicitly opts out (D20).
anyinfer.local.is_loopback ¶
is_loopback(base_url: str | None) -> bool
Whether a base URL points at this machine.
Used by two callers with the same underlying question — "is the thing at the other end of this URL running on hardware I can probe?". A remote Ollama daemon answers no, and everything downstream (hardware detection, fit classification, zero-cost pricing) depends on not pretending otherwise.
A URL that cannot be parsed is treated as not loopback, because the safe default is to assume someone else's machine.
Recommendation¶
anyinfer.local.recommend_alias ¶
recommend_alias(
hardware: HardwareProfile,
catalog: TierSource,
*,
prefer_accelerated: bool = True,
) -> Recommendation
Recommend the largest catalog tier this machine can comfortably run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hardware
|
HardwareProfile
|
The detected profile. |
required |
catalog
|
TierSource
|
The catalog whose aliases carry |
required |
prefer_accelerated
|
bool
|
Budget against VRAM when an accelerator is present. With unified memory, system RAM is the budget regardless. |
True
|
Returns:
| Type | Description |
|---|---|
Recommendation
|
A recommendation. When memory is unknown, the smallest tier is proposed with |
Recommendation
|
|
anyinfer.local.Recommendation
dataclass
¶
Recommendation(
alias: str | None, reason: str, confident: bool = True
)
A recommended tier and the reasoning behind it.
Attributes:
| Name | Type | Description |
|---|---|---|
alias |
str | None
|
The recommended alias, or |
reason |
str
|
Why this tier was chosen, for display to a user. |
confident |
bool
|
|
anyinfer.local.Tier ¶
Bases: Protocol
The subset of a catalog alias this module needs.
Structural rather than nominal so the local subsystem does not import the catalog: artifacts (local data) are depended on by the catalog, and a reverse dependency here would make that a cycle.