Store
API reference for the distributed object store
API reference for urun.store — the distributed object store.
Public API at a Glance
| Name | Description |
|---|---|
app.store | App-bound store — auto org+app-scoped cache keys |
store.model(url, ...) | Download + load a HuggingFace model in one call |
store.repo(url, ...) | De-vendor an upstream repo at a pinned commit |
store.get(key, ...) | Load content by key |
store.set(key, value) | Store named key-value data |
store.put(obj) | Store ephemeral object, get ref handle |
store.path(key) | Get filesystem path for key |
store.lookup(pointer_key, ...) | Resolve pointer to latest version |
store.stream(name, ...) | Create global stream |
store[key] / store[key] = val / del store[key] | Dict-style access |
key in store | Membership test |
@store.cache() | Cache model-loading results, keeping them hot in GPU memory |
store.cache_stats() | Get cache statistics |
store.clear_cache() | Clear local cache |
store.configure_cache() | Configure cache settings |
For patterns and narrative explanation, see the Store concept page.
app.store
app = App("liveavatar")
store = app.storeReturns a store proxy bound to the app. Its only behavioral change versus from urun import store is that @store.cache(key=...) keys are automatically namespaced with the owning org and the app name ("<org>:<app>:<key>"), so two tenants deploying the same app with the same bare key get distinct cache namespaces.
Everything else delegates unchanged to the shared store. Critically, store.model / store.path / store.repo stay content-addressed and shared across apps and orgs — only the per-app cache-key namespace is scoped. The org segment is sourced from the runtime/deploy context; you never pass it by hand. (Outside a deploy — local/dev — there is no org to scope to, so the cache key is app-scoped only and a one-time warning is logged.)
store.model()
store.model(
url: str,
*,
cls: type | None = None,
subfolder: str | None = None,
device: str | None = None,
device_map: str | None = None,
dtype: Any | None = None,
allow_patterns: list[str] | None = None,
ignore_patterns: list[str] | None = None,
**from_pretrained_kwargs,
) -> AnyDownload and load a HuggingFace model in one call — download (with auth), auto-detect the model class, load via from_pretrained, and place on device. Content-addressed and shared across apps and orgs (like store.path / store.repo).
| Parameter | Type | Default | Description |
|---|---|---|---|
url | str | required | HuggingFace URI, e.g. "hf://Wan-AI/Wan2.1-T2V-14B" |
cls | type | None | None | Model class. None auto-detects from model_index.json (diffusers) or config.json (transformers); pass explicitly for research models without standard metadata |
subfolder | str | None | None | Load a sub-component (e.g. "text_encoder") |
device | str | None | None | Target device ("cuda", "cuda:0"); ignored when device_map is set |
device_map | str | None | None | Accelerate device map (e.g. "auto") for multi-GPU sharding / CPU-disk offload |
dtype | -- | None | Torch dtype for the load |
allow_patterns / ignore_patterns | list[str] | None | None | Restrict which files download |
pipe = store.model("hf://org/restyle-14b", device=str(ctx.device), dtype=torch.bfloat16)For the canonical loader shape, wrap the store.model call in @store.cache(key=...) so the loaded object stays resident across sessions.
store.repo()
store.repo(
url: str,
*,
include: list[str] | None = None,
patches: list[tuple[str, str, str]] | None = None,
diff: str | list[str] | None = None,
overlay: str | dict[str, bytes | str] | None = None,
base_dir: str | None = None,
subdir: str | None = None,
) -> RepoHandleMaterialize an upstream git repo (or a subtree) into the content-addressed store at a pinned commit, so its modules are importable without vendoring the files. See De-vendoring for the full pattern.
| Parameter | Type | Default | Description |
|---|---|---|---|
url | str | required | Repo reference github.com/org/repo@<sha> (also https://, git://, org/repo@<sha>, or a local path). Always pin a SHA. |
include | list[str] | None | None | Restrict materialisation to these subtrees |
patches | list[tuple[str, str, str]] | None | None | (file, find, replace) tuples applied after checkout |
diff | str | list[str] | None | None | Unified/git diff(s) — inline text or .patch path — applied with git apply; a hunk that doesn't apply raises |
overlay | str | dict | None | None | Net-new / whole-file replacements: a dir path or a relpath -> bytes|str mapping |
base_dir | str | None | None | Base dir for relative diff/overlay paths (defaults to the caller's directory) |
subdir | str | None | None | Re-root the tree at this repo subdirectory — its packages become top-level importables; include stays repo-root relative |
Apply order: checkout @sha → diff → overlay → patches → subdir re-root.
The handle does three jobs:
REPO = store.repo("github.com/org/repo@<sha>", include=["pkg"]) # module top
DEPS = Dependencies(python=[*REPO.deps(), "extra==1.2"]) # upstream's own deps
@store.cache(key="model")
def load(*, device):
with REPO: # scoped import — path removed after the block
from pkg import Thing
return build(Thing, device=device)REPO.deps(extra=None, exclude=None, pin_overrides=None)→ the upstream's own python dependency list, parsed at the pinned SHA fromrequirements.txt/pyproject.toml[project.dependencies]/setup.pyinstall_requires. Selectors:extra="name"picks an optional-dependencies extra;exclude=["dep", ...]drops upstream deps by canonicalized name;pin_overrides={"dep": "==1.2"}replaces a dep's version spec (an overridden dep survives even if also excluded). Forurun deploy's static analysis, the selector arguments must be literal constants, and a repo whose deps you splice must not carrydiff=/overlay=deltas (deploy aborts loudly — declare the resulting deps as literals instead).with REPO:→ a context manager that puts the repo's modules on the import path only inside the block. Compile-safe: it scopes the path (no new top-level repo import resolves after the block), while modules imported inside the block stay resident for the process — a model loaded under the block still works whentorch.compiletraces it later.REPO.sha→ the resolved, pinned commit SHA.
store.get()
store.get(
key: str | ObjectRef | ResidentRef,
*,
target: str | None = None,
variant: str | None = None,
accept: Callable[[Any], bool] | None = None,
) -> Any | NoneLoad content by a Store key, object id, ObjectRef, or ResidentRef set via store.set() / store.put(). For URIs (hf://, s3://, http://, git://), use store.path() or store.model() instead — store.get() does not resolve URI schemes.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
key | str | ObjectRef | ResidentRef | required | Store key, object id, ObjectRef, or ResidentRef | Content identifier |
target | str | None | None | Valid directory path | Reserved; not used by get() today |
variant | str | None | None | -- | Select a model variant. Tries {key}:{variant} first, then falls back to the base key. |
accept | Callable[[Any], bool] | None | None | Resident-ref form only | Payload-type predicate — a tier hit the predicate rejects is treated as a miss |
Use store.get() to load previously store.set() / store.put() content. Returns None if the key is not found.
from urun import store
store.set("checkpoint", weights)
weights = store.get("checkpoint")
# Select a variant (falls back to the base key if the variant is absent)
model = store.get("my-model", variant="int8")
# Download a HuggingFace model's local path instead
path = store.path("hf://meta-llama/Llama-2-70b")store.set()
store.set(key: str, value: Any) -> strStore named key-value data. Overwrites any existing value for the key. Returns the key
(same as the input), so a chained get doesn't need a separate variable.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
key | str | required | -- | Name to store under |
value | Any | required | Must be serializable | Content to store |
Use store.set() for named, addressable data like configs, session artifacts, and model weights.
store.put()
store.put(obj: Any) -> ObjectRefStore an ephemeral object and get a ref-counted handle. The object is lazily evicted when no references remain.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
obj | Any | required | Must be serializable | Object to store |
Returns: ObjectRef — a ref-counted handle to the stored object.
Use store.put() for intermediate data passed between functions. Unlike store.set(), there is no name — the ref handle is how you retrieve the data.
from urun import store
ref = store.put(large_tensor)
# Pass ref to another function
upscale(frame_ref=ref)store.path()
store.path(key: str) -> strGet the filesystem path for a stored key. Downloads the content if not already cached locally.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
key | str | required | URI or Store key | Content identifier |
Returns: str — absolute filesystem path.
Use store.path() when you need a file path instead of loaded content (e.g., passing to libraries that expect paths).
store.lookup()
store.lookup(
pointer_key: str,
target: str | None = None,
) -> AnyResolve a pointer to the latest version of stored content. Useful for "latest artifact" patterns where the pointer key stays the same but the underlying data changes.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
pointer_key | str | required | -- | Pointer key to resolve |
target | str | None | None | Valid directory path | Local directory to download into |
Use store.lookup() to follow a pointer to the latest version of a model or artifact.
store.stream()
store.stream(
name: str,
format: str | None = None,
codec: str | None = None,
fps: int | None = None,
) -> EventStreamCreate a global stream. Global streams are addressable by name and accessible from any process. Bidirectional — both sides can emit.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
name | str | required | -- | Stream identifier |
format | str | None | None | -- | Data format hint |
codec | str | None | None | -- | Compression codec |
fps | int | None | None | Positive integer | Target frame rate for video streams |
Returns: EventStream — a bidirectional stream handle.
Use store.stream() for global event streams (e.g., progress updates, metrics). For session-scoped streams, see Context.
from urun import store
metrics_stream = store.stream("fleet-metrics")
await metrics_stream.emit("health", {"fps": 20.0, "active_sessions": 12})Dict-style access
store[key] # equivalent to store.get(key)
store[key] = value # equivalent to store.set(key, value)
del store[key] # delete key from store
key in store # membership test, returns boolDict-style access provides convenient shorthand for common store operations.
from urun import store
store["my-config"] = {"lr": 1e-4, "batch_size": 32}
config = store["my-config"]
if "my-model" in store:
model = store["my-model"]@store.cache()
@store.cache(
key: str | None = None,
prefix: str = "",
ttl: int | None = None,
gpu_pin: bool = True,
process_local: bool = False,
max_gpu_memory_mb: int | None = None,
)Decorator that caches a loader's result across calls and sessions. On first call the function executes and the result is cached resident; subsequent calls return the cached object directly — zero-copy for GPU-pinned tensors.
| Parameter | Type | Default | Description |
|---|---|---|---|
key | str | None | None | Explicit cache key. With app.store it is auto org+app-scoped ("<org>:<app>:<key>"). The canonical form for model loaders. |
prefix | str | "" | Namespace for cache keys (e.g., "model", "pipeline") |
ttl | int | None | None | Time-to-live in seconds; None = infinite |
gpu_pin | bool | True | Keep CUDA tensors in GPU memory (fastest, zero-copy) |
process_local | bool | False | Skip distributed store -- keep in worker process memory only |
max_gpu_memory_mb | int | None | None | Cap GPU memory usage for this cache |
The canonical loader is @store.cache(key="...") def load(*, device): return model — device comes from the platform (ctx.device), never hardcoded. Beyond memoizing the load, store.cache is the weights-residency tier (content-addressed, tiered GPU memory → host memory → local disk → peers → durable storage) and the snapshot collaborator: cached weights are excluded from the process snapshot on capture and streamed back from the resident tier on restore, so the snapshot image is weight-free. There is no lifecycle on the loader — warmup, precompile, and validation all happen in ctx.self_test.
app = App("liveavatar")
store = app.store
@store.cache(key="wan22-s2v-14b") # resolves to "<org>:liveavatar:wan22-s2v-14b"
def load_model(*, device):
import torch
from diffusers import DiffusionPipeline
return DiffusionPipeline.from_pretrained(
store.path("hf://org/wan22-s2v-14b"), torch_dtype=torch.bfloat16
).to(device)process_local=True is for non-serializable runtime objects (live pipelines, compiled graphs) that must stay in the worker process. gpu_pin=True (the default) keeps CUDA tensors on-device for zero-copy access. The older prefix=/gpu_pin= callers keep working; key= is additive and is the recommended form.
Cache control:
# Clear all entries with a prefix
load_model.clear_cache(prefix="model")
# Get cache stats
info = load_model.cache_info()store.cache_stats()
store.cache_stats() -> dictGet statistics about the local cache including size, hit rate, and entry count.
Returns: dict — cache statistics.
Use store.cache_stats() to monitor cache utilization and debug download behavior.
store.clear_cache()
store.clear_cache() -> NoneClear the local disk cache. Forces re-download on next store.get() call.
Use store.clear_cache() to free disk space or force fresh downloads.
store.configure_cache()
store.configure_cache(
cache_dir: str | None = None,
max_size_gb: float | None = None,
) -> NoneConfigure cache settings.
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
cache_dir | str | None | None | Valid directory path | Cache directory location |
max_size_gb | float | None | None | Positive number | Maximum cache size in GB |
Use store.configure_cache() to change cache location or set size limits.