Skip to content

Serving model documents

A portable model card describes identity, programs, variants, services, and canonical presets. loom-fetch installs its selected dependencies, and loom-serve --model=installed/model.json loads that local selection directly. Fetch and serve a model walks through acquisition and a complete request.

A deployment document gives a model its server name, device, admission policy, and execution capacities. The same object is accepted by --model=path/serve.json and POST /v1/models/register. Registration requires no model-specific native code or server rebuild.

A deployment can describe its programs directly or reference a portable model card. A card owns model identity, services, program sets, parameter variants, and presets independently of the server.

After fetching a model, pass its card directly to the server:

loom-fetch model.json --preset=interactive --output=./installed
loom-serve --device=task --model=./installed/model.json

This example selects the interactive preset and uses the task device; select a device supported by the model's programs. Fetch retains the selected preset, so serving needs no separate deployment file. Requests use the card's model.id as their model name. Programs and weights are prepared on worker threads.

An authored card with several presets requires a choice, for example loom-serve --device=task --model=model.json --preset=interactive. --preset applies to one card. For multiple deployments, custom routing aliases, device placement, or execution overrides, use the referencing documents below.

This document describes the arithmetic embedding package in the serving walkthrough:

{
  "name": "vectors",
  "definition": {
    "kind": "embedding",
    "prepare": "prepare.loom",
    "control": "control.loom",
    "sources": ["config.loom", "embed.loom"],
    "assets": ".",
    "options": {"batch_units": 64},
    "pending_requests": 16
  }
}

Portable model cards

A text deployment can select a local card and override its execution defaults:

{
  "name": "chat",
  "model": {
    "path": "model.json",
    "preset": "interactive",
    "programs": "mixed"
  },
  "definition": {"kind": "text", "max_sessions": 4}
}

max_sessions bounds concurrent session ownership for this deployment. Additional requests wait in its bounded pending queue. The initial allocation is automatically capped by this maximum, so ordinary deployments need not set rows as well. Separate deployments of the same card can use different limits.

The referenced model.json can contain:

{
  "schema_version": 1,
  "model": {"id": "example/model", "name": "Example model"},
  "programs": {
    "mixed": {
      "inputs": ["prepare.loom", "control.loombc"],
      "libraries": ["kernel.loombc"],
      "config": {"model.width": 64}
    }
  },
  "services": {"chat": {"kind": "text", "version": 1}},
  "variants": {
    "small": {
      "services": {
        "chat": {
          "programs": ["mixed"],
          "config": {"model.width": 32},
          "parameters": {
            "model": {
              "files": [
                "weights-00001.safetensors",
                {"path": "weights-00002.data", "format": "safetensors"}
              ]
            }
          },
          "tokenizers": {
            "text": {"path": "vocabulary.data", "format": "huggingface-json"}
          }
        }
      }
    }
  },
  "presets": {
    "interactive": {
      "service": "chat",
      "variant": "small",
      "programs": "mixed",
      "execution": {"rows": 4, "context_capacity": 128, "prefill_capacity": 32}
    }
  }
}

These programs implement the text service ABI, including text_prepare_v1 and the corresponding text_<role>_v1 control exports. Service version is independent of schema_version and the bytecode encoding version. Omitting it selects the unversioned development names, text_<role>. A missing versioned export is an error; bare exports are not substituted. Several services and ABI revisions can coexist in one provider set. The host checks the selected service. Native card execution supports service ABI 1 for text, sequence, image, embedding, decision, speech, recognition, and activity, plus their unversioned development contracts. Each uses its own <kind>_<role>_v1 exports for both preparation and control. Selecting a different service does not rename exports or substitute another service's ABI.

Text services can declare an independently versioned image projector:

{
  "services": {
    "chat": {
      "kind": "text",
      "version": 1,
      "components": {"image": {"version": 1}}
    }
  }
}

The component uses text_image_prepare_v1, text_image_prepare_request_v1, text_image_prepare_batch_v1, text_image_collect_batch_v1, and text_image_run_v1. These exports may share one bytecode bundle with the primary text exports. Both use the card's selected providers and named parameter scopes. The server supports image component ABI 1 and the unversioned text_image_<role> development contract. Other selected component roles or revisions fail before program loading.

Presets can supply components.image.options for option names declared by the component, and components.image.execution.specialization_cache for its idle request-code cache. A serving deployment's definition.image can override options and specialization_cache; program and parameter paths remain card facts. Referenced cards resolve on a worker even for drained registrations, making component metadata available without preparing programs or loading weights. Component management becomes available when that resolution completes. Image preparation and parameter activation remain lazy and independent of the text model. Evicting image parameters preserves text sessions and checkpoints.

Program set names are opaque: mixed could be renamed source without changing its contents. Each input and library contributes to the link index with its declared role. A provider object can specify path, format, importer options, and auxiliary data files outside the link index. The native loader accepts Loom text and bytecode providers; unsupported importer formats or options fail explicitly. File names are relative to the card directory, including parameter shards and tokenizer assets. directory, when present on a program set, supplies the source program's execution context and does not rebase the declared file paths.

Each parameters key names an independent parameter index. Source preparation uses prepare.source_scope(scope) to select that index; its ordered typed files populate one shared checkpoint source. prepare.parameter(stage, binding, input, source, policy) binds that source to a resident policy input. Several stages can share the same source, and demand reads can use it without a resident binding. Text service uses the text tokenizer identity. Image, embedding, decision, speech, recognition, and activity preparation return a tokenizer identity; services supporting inputs without tokenization may return an empty string. Sequence consumes token IDs directly and needs no tokenizer. File names need not identify their role or encoding: an explicit format supports renamed assets, while shorthand strings use the loader's format inference. Ambiguous or unsupported encodings fail.

Speech service variants may declare original recordings under voices. Each key is the ID used by speech requests; file follows the same destination and origin rules as other assets. Optional content_type uses the upload media-type syntax. Encoded recordings can omit it and use container detection; raw PCM requires its rate, channels, and optional format=f32le (s16le is the default). The file itself has no format, since the media type owns that distinction:

"voices": {
  "narrator": {
    "file": {"path": "voice/narrator.wav", "origin": "@recordings/original.wav"},
    "content_type": "audio/wav"
  }
}

loom-fetch acquires and locks these original bytes. Speech ABI 2 prepares each recording through the native upload lifecycle before the model becomes ready; failed preparation fails model registration. Models can also accept uploaded voices after startup. Prepared conditioning belongs to the loaded model and is not a parameter archive.

Variant config values override program defaults and reach both preparation and generated native stages. Values are compiler scalars: booleans, finite numbers, or strings. Source preparation may specialize individual stage keys. Presets supply execution defaults and optional source-owned options; accepted option names are declared by the service. A deployment's explicit settings override canonical defaults. Explicit service, variant, and program selections must remain compatible; an ambiguous selection fails. Checkpoint paths, tokenizer paths, providers, and asset directories belong to the card and cannot be overridden through the deployment definition. Image adapters are selected per request, independently of the base model card.

Presets may include informational memory estimates: weights_bytes for resident prepared parameters, transient_bytes for additional request/state/workspace storage, and a required workload description stating assumptions and coverage. Unknown counts are omitted. The fetch picker and generated model reference display this guidance; native serving uses its actual allocation and scheduling contracts. Changing the selected variant, partition, service, or program set drops the estimate from a fetched or built card.

A partitioned variant declares partitions instead of services. Each named partition supplies its own complete service, provider, configuration, and asset choices. The deployment's model.partition selects one, overriding a canonical configuration when specified. Names carry no host or device assignment; the application owns communication and placement between separately loaded models.

Admission records the reference without opening the card. Preparation resolves it on a worker before constructing the native model. The server reads local files and does not fetch origins or inspect integrity locks. Acquisition and materialization happen before registration. A parameter descriptor may include an acquisition transform; loom-fetch produces the declared local file, and the server reads its path and format without interpreting that transform.

Deployment fields

Field Contract
name Required nonempty visible-ASCII routing alias, independent of source symbols.
definition Required object specifying a modality, programs, assets and capacities.
model Optional local card reference with path and optional preset, service, variant, partition, and programs selectors. Programs and assets then come from the card.
admission "open" by default; "drained" registers metadata without reading programs, loading weights or allocating model storage.
device String key in the server's named device configuration; required when the group has more than one member. The --device=URI shortcut uses the key device.
pool String key in the server's pool inventory; required when more than one pool is configured. Its permitted devices must include the selected execution device. The --device=URI shortcut uses the key pool.
base_directory Optional local file base; relative values resolve from the containing document.

Placement is a deployment property, outside the reusable definition. Drained registrations still require valid device and pool selections. Names are resolved before publication or model I/O; /v1/status publishes the inventories under server.resources.devices and server.resources.pools, with each entry's key identifying the selection. A deployment executes on one selected device. The fetched model card can remain unchanged while separate deployment documents select different devices:

{
  "name": "scout",
  "device": "gpu0",
  "pool": "interactive",
  "model": {"path": "models/chat/model.json"},
  "definition": {"kind": "text"}
}

The selected card preset supplies the programs, assets, and settings omitted from definition. A second deployment can reference the same card with a different routing name, device, or pool key. Machine configuration is described in the command reference.

Unknown fields, duplicate keys, wrong JSON types and wrong-modality fields reject. Names already registered return a conflict instead of being replaced. Source programs validate architecture-specific options and constraints during preparation. Opening admission starts asynchronous preparation; explicit activation can prepare a model while its admission stays closed.

Programs and dependencies

Every direct definition contains kind, prepare, control and a nonempty sources array. Kinds are text, sequence, image, embedding, decision, speech, recognition, and activity.

Field Role
prepare Cold VM program declaring stages, specialization, parameter domains and model metadata.
control Warm VM program implementing the modality's request and execution contract.
sources Shared VM functions, native commands, kernels, configuration and motif providers.
options JSON object passed to source preparation unchanged; defaults to {}.
pending_requests Request credit, default 32; positive integer. Text/sequence/image/speech/recognition/activity count queued requests, while embedding/decision also count the active cohort.
variant_capacity Text/sequence/image/embedding/decision retained native executable choices, default 16, including the original, pending compilations and failed attempts; zero disables native reconfiguration.

Programs accept text or portable Loom bytecode (.loom or .loombc), detected from contents. Providers may mix formats. Bytecode is serialized IR, not a precompiled device executable: the server specializes and JITs it for the live device. Source-declared parameter preparation policies accept the same formats.

Provider order is not an execution schedule. Loom's linker indexes symbols, resolves imported declarations and selects definitions reachable from named roots. The document supplies inputs to that linker; it does not describe a second layer graph. Preparation/control VM roles link their helpers from this catalog and compile separately from native commands. Each VM entry file is a primary input; library-private symbols stay private. An entry file may also appear in sources. Module composition explains the underlying source and dependency contract.

Native configuration does not interpret options keys or rewrite strings inside that object. Their meaning belongs to the source program. Text control programs also receive this object to establish request defaults; model packages can define sampling defaults without native per-model flags.

Text definitions

Direct text definitions require weights, a checkpoint file, and tokenizer, a tokenizer JSON file. Card references supply their typed inputs instead. Native capacity fields are:

Field Default Meaning
max_sessions omitted Positive ceiling on resident session owners. Preparation and outstanding output retain their owners; extra requests wait in pending_requests. Omission leaves concurrency memory-limited.
rows 4 Initial resident capacity hint, capped by max_sessions when set. The directory grows as requests arrive, independently of epoch token/span capacity and up to the configured maximum.
context_capacity 16384 Per-row logical context limit in token positions, fitting a positive signed 32-bit count. The source validates the model's supported context window.
prefill_capacity 512 Largest automatically specialized packed token shape; not a prompt-length limit.
checkpoint_capacity 0 Explicit pinned endpoint slots, independent of execution rows.
session_cache_capacity automatic Evictable idle histories beyond resident rows. An explicit count is exact; zero disables this cache. Nonzero counts also reserve one admission-exchange endpoint.
prefix_cache_capacity automatic Evictable exact intermediate-prefix endpoints. An explicit count is exact; zero disables capture.
cache_target_bytes automatic Soft sizing target for additional initial cache storage. Omission uses 2 GiB, reduced to 1/16 of a configured shared physical-memory budget. Zero disables automatic counts without changing explicit counts.
admission_overtakes rows Successful fitting admissions allowed to pass a blocked oldest request; zero selects FIFO.
max_tokens 512 Default request output limit, fitting a positive signed 32-bit count. Prompt and output together must fit the configured context.
draft_depth 0 Proposed tokens after each anchor, from 0 through 7; requires matching source support.
continuation_epochs 1 Device-fed epochs per scheduling turn, either 1 or 2.

Context and prefill capacities fit positive signed 32-bit counts. Packed token capacity may exceed an individual context limit: one epoch can combine many short sessions. It must fit an anchor plus every draft proposal.

Cache addressability is serving policy, not a model field. Fixed backing reserves one context_capacity shared across the model's resident rows. Elastic backing derives each model's addressable page count from the selected backing pool's physical capacity and the source-declared bytes per cache page. An explicit memory_bytes budget can tighten that bound. There is no fixed per-model token quota: a model can address the pages that could fill the shared pool, without committing them or allocating a page directory for the entire range. If the backend cannot report physical capacity, registration requires an explicit pool budget instead of inventing a default.

This bound uses total capacity, not a snapshot of currently free memory. The pool's memory_bytes policy separately bounds physical backing shared by every model, parameter bank, state allocation, and workspace using that pool. Source preparation validates its page geometry and native address arithmetic; native page IDs can address at most UINT32_MAX pages. Physical admission, not the logical address range, decides whether competing live demand fits. Offloaded histories release resident page IDs and do not consume that address space.

Two-epoch continuation requires nonzero draft depth. A source can impose tighter shape limits or reject unsupported drafting modes. Endpoint capacities are nonnegative and their combined native index space must fit signed 32-bit indices.

A minimal text deployment automatically retains useful shared prefixes and displaced named conversations. During preparation, the server evaluates the model's declared storage sizes, gives up to two automatic prefix endpoints priority, and fits detached histories into the remaining sizing target. The automatic detached directory follows the byte target, not the number of resident KV pages. Counts are independent of rows and max_sessions: evicting a saved history does not limit active concurrency. The selected declaration alone proceeds to device JIT and parameter loading.

cache_target_bytes measures additional source-declared state extents and native checkpoint maps over the explicit configuration, including initial copy-on-write headroom. It is neither a physical reservation nor a lifetime memory cap. Elastic backing commits slabs as they are used, subject to the shared physical budget; fixed backing allocates the declared extents. Growing resident sessions adds working-set headroom through ordinary admission. KV pages remain shared with active sessions and are reclaimed under pressure.

For example, setting definition.cache_target_bytes to 536870912 selects a 512 MiB soft target.

Setting both cache capacities to zero disables automatic capture and detached history retention. Existing idle resident rows can still serve exact continuations without extra endpoint storage. Manual checkpoint_capacity remains a separate explicit reservation.

The text_memory heartbeat record reports resolved session_cache.capacity and prefix_cache.capacity, cache_target_bytes, and the selected cold cache_additional_bytes estimate. Actual committed state and parameter bytes remain separate measurements; the cold estimate does not change with resident growth.

The shared pool can grow independently of the per-session context limit and prefill shape. Its token count must fit both host indexing and a signed 64-bit integer. Source preparation declares the page size and storage layout; the resulting pool must contain at most 4,294,967,295 physical pages, and each region must fit its declared allocation. These representation limits do not guarantee that every page can be backed within the server's physical memory budget.

Logical capacity is not eagerly committed physical storage under elastic backing. Sessions grow by blocks; admission reserves their requested completion high-water. Shared prefixes can reference the same resident blocks. Changing deployment capacities requires a new registration; they are not process flags.

The session maximum controls live ownership, not the number of sessions that can visit the deployment over its lifetime. Finished or cancelled requests release their owners for reuse. Slow output readers still count while they retain a request, and a full pending queue rejects additional requests with HTTP 503. Growth stops exactly at the maximum, including non-power-of-two limits such as six. Dispatch shapes remain independent: a single session can still use a large prefill shape.

Idle session caches, shared prefix endpoints, and explicit checkpoints have their own capacities; the session maximum is not a byte budget for those caches, model weights, or workspace. The shared physical memory budget still governs backing. text_activity telemetry reports session_capacity and max_sessions; zero in that telemetry field means no configured count limit.

Image input

A text definition can include an optional image object with required prepare, control and assets paths, plus source-owned options defaulting to {}. It uses the enclosing definition's sources, uploaded source package, path base and device. This is an input projector, distinct from an image-generation deployment.

The optional image.specialization_cache object controls idle request-shape executable reuse, with the same fields and defaults as finite models below. It does not pin vision weights or allocate image workspace at registration.

The component implements the finite request/cohort contract and owns independent preparation and parameter residency. Registration and ordinary text generation leave it dormant; an image not covered by retained state or an explicit component:"image" activation starts preparation. Primary warmup and pinning do not implicitly warm or pin it. The text source must also declare image limits and implement ordered media framing and mixed input. A declaration alone does not give a text program vision capability. See image-conditioned text for the source boundaries.

Known-token sequence definitions

kind:"sequence" advances retained model state using exact caller-supplied token IDs and returns source-defined opaque bytes. It requires weights but no tokenizer, chat template, vocabulary head, or token sampler. This supports a stateful stage inside a larger inference pipeline without making it imitate a chat model.

Sequence definitions share text's rows, max_sessions, context_capacity, prefill_capacity, checkpoint_capacity, session_cache_capacity, and admission_overtakes fields and defaults. Admission reserves only the supplied known positions, rounded to cache blocks; it does not reserve generated tokens. Execution uses packed epochs and the same row/cache/checkpoint ownership.

tokenizer, max_tokens, draft_depth, continuation_epochs, prefix_cache_capacity, cache_target_bytes, and image are not sequence fields. Sequence cache counts default to zero. Reuse is explicit through a retained sequence handle or a named checkpoint, not automatic chat prefix matching. Source-defined request policy belongs in options. See the sequence HTTP profile and source contract.

Image and finite-vector definitions

Image requires checkpoint, an asset directory interpreted by source preparation. adapter optionally supplies another asset path. width and height default to 384 pixels, and text_tokens defaults to 512; all are positive, with architecture constraints checked by the source. Width and height are defaults, not the only accepted request size. An explicit image size reaches source preparation and can select or declare shape-specialized code without changing checkpoint residency.

command_quantum defaults to zero, meaning one whole-program submission. A positive value is a soft command-count target for full-barrier-aligned submission parts. It is not a wall-clock limit. Parts retain the invocation's workspace and weights until final completion.

Embedding and decision definitions require an assets directory. Their architecture and batching settings belong in source-owned options, not new native per-model fields.

For image, embedding, and decision deployments, specialization_cache bounds executable choices declared by request preparation:

"specialization_cache": {"capacity": 16, "max_bytes": 16777216}

These are the defaults. capacity counts idle choices and max_bytes counts their retained declaration, entry, portable-program, and native-key/artifact storage. Least-recently-used choices retire first. Either limit can be zero to disable idle retention. A single larger request can still compile and execute; its choice retires afterward instead of remaining cached. Reusing shapes shares the immutable source index and checkpoint residency. Fixed source/index capacity, temporary compiler storage, and driver-private allocations are separate from this byte budget.

This cache is independent of variant_capacity, which controls explicitly registered native replacements. Requests use the source generation selected when their image or cohort is prepared; selecting another replacement affects subsequent requests without changing one already being compiled or executed.

The referenced assets must match the package's preparation policy. Loading checks its declared parameter keys and extents; matching a filename alone does not establish checkpoint identity or original tensor dtype/shape compatibility.

Speech definitions

Speech requires an assets directory. Source-owned options select model capacities, voices and preparation policy. The package declares reusable voice conditioning, separate mutable state for each request, waveform geometry and a bounded output block. A model may perform several stages before producing audio.

For example, the speech walkthrough uses:

{
  "name": "speech",
  "definition": {
    "kind": "speech",
    "prepare": "prepare.loom",
    "control": "control.loom",
    "sources": ["generate.loom"],
    "assets": ".",
    "pending_requests": 4
  }
}

pending_requests bounds additional host-queued requests. Resident request capacity and numerical batch capacity are separate source declarations; they are not inferred from this queue limit. Each request retains its row across changing compatible batches, and different voices or playback positions need not require separate registrations. A package supplies compatibility tags and additive work units for its next operations.

The speech HTTP profile exposes incremental 24 kHz mono PCM; a package producing other geometry rejects during preparation. Voice identifiers and conditioning assets belong to the package. The service keeps request state alive until accepted device work and output copies retire, allowing subsequent requests to reuse the model safely.

The normal pin/unpin, activation, drain and removal controls also apply to speech. Pinning governs parameter residency, independently of mutable-state admission and compute scheduling. Speech does not currently accept variant_capacity; replacing its execution contract requires a new registration.

Recognition and activity definitions

Recognition requires kind:"recognition" and an assets directory. It uses the same prepare, control, sources and source-owned options fields as other modalities. The package declares input sample rate and channels, bounded input credit, maximum recording length, supported event types, event count and byte bounds, persistent request storage and numerical batch capacity. Native requests explicitly select transcript or activity output, or both when the source supports them. HTTP transcription selects transcripts.

An activity deployment uses the same fields and continuing-input execution contract for native speech activity detection. The source must support activity events; a recognition source must support transcripts. Either can additionally support the other event family, with each native request selecting its outputs. These are deployment roles, not fixed model architectures or turn policies. An activity deployment has no HTTP inference endpoint. Its /v1/models entry has loom.kind:"activity", omits loom.endpoint, and reports the prepared source's actual output modalities, including activity.

pending_requests bounds the additional host queue. Each admitted request owns its input clock, unconsumed PCM, model state and transcript revisions. Source compatibility tags group next numerical operations; different recording lengths and input positions do not require different registrations. Source programs own language, prompting, decoding, segment boundaries and any speaker/alignment evidence.

The transcription profile accepts uploaded WAV, MP3, or FLAC when enabled, and always accepts PCM16LE with an explicit sample rate and matching channel count. Raw rate conversion works without audio codecs. It returns final JSON or text. The underlying continuing-input service also accepts bounded PCM with explicit end-of-input, independently of HTTP. The normal pin/unpin, activation, drain and removal controls apply to both roles. Neither recognition nor activity accepts variant_capacity; changing its execution contract requires a new registration.

Local path binding

A file document resolves relative references from its own directory. For --model=/srv/models/vectors/model.json, prepare.loom means /srv/models/vectors/prepare.loom. References can reach shared providers outside that directory; this is not a filesystem sandbox.

An absolute base_directory overrides the containing directory. A relative value such as program/model selects that directory inside the document's directory. A JSON-only HTTP request has no containing file, so relative references need an explicit absolute base on the server machine. A document with only absolute references needs no base; source programs must also return absolute parameter and policy paths in that case.

Authored references are retained separately from the base and bound at file I/O. The loader does not rewrite JSON to machine-absolute paths, resolve symlinks or infer remote mounts. Source-returned parameter/policy references use the same base. Tokenizer paths returned relative to an asset directory use that directory.

An uploaded source package supplies a separate namespace for relative programs and metadata, based on the request part's logical filename. It supports preparation/control VM programs, native providers, tokenizers and weight-preparation policies without placing them on the server filesystem. Missing members fail without a local-file fallback. Parameter checkpoints still use the filesystem base; explicit absolute program or metadata paths also select server-local files. Registration retains uploaded bytes through delayed preparation and final retirement.

HTTP registration changes live state only. It does not rewrite model documents or flagfiles; persistence of operator changes belongs to the management client.