[[models]]
Reference for [[models]] in a urna build spec: model roles, dims, space_dtype, per-model knobs, and which vector spaces each model writes into the file.
Each [[models]] entry picks an embedding model from the model registry and says what it embeds and where the vectors go. A spec needs at least one entry, and exactly one of them has text = "default".
[[models]]
preset = "potion"
text = "default" # the file's main vectors (space 0)
[[models]]
preset = "wemm-2b"
text = "space" # named space wemm-2b-text@256
image = "space" # named space wemm-2b@256
dims = [256]
space_dtype = "int8"
[output]
allow_remote_code = ["wemm-2b"]Keys
| Key | Type | Default | Applies to | Meaning |
|---|---|---|---|---|
preset | string | "" | all | A preset name from the model registry. Each preset appears at most once per spec |
text | string | "none" | all | default, space or none. See roles |
image | string | "none" | presets with an image tower | space or none. See roles |
dims | list of integers | [] | presets with a validated dimension ladder | One named space per listed dimension. [] means one space at the native dimension |
space_dtype | string | "int8" | named spaces | Stored precision of this model's named spaces: float32, float16, int8 or int4 |
model_path | path | "" | sentence-transformers presets | Model directory, the first place looked for weights. potion and open_clip presets do not load from it, but it keys the cached model hash |
device | string | "" | sentence-transformers and open_clip presets | cpu, mps, cuda and so on. "" picks automatically. potion runs on the CPU |
batch_size | integer | 32 | sentence-transformers and open_clip presets | Encoder batch size |
dtype | string | "" | sentence-transformers presets | Torch dtype of the weights: float32, float16, bfloat16. "" uses the device default |
text_corpus_mode | string | preset default | sentence-transformers presets | How row text is encoded: document (encode as a document), query, or plain (plain encode). Any other value behaves like plain |
text_query_mode | string | preset default | recorded only | Recorded in the embedding recipe. The build embeds documents only, and urna ask does not read it |
image_mode | string | preset default (image_document) | recorded only | Recorded in the embedding recipe; nothing reads it |
image_prompt | string | preset default (Represent this image.) | wemm-* presets | Text paired with each image document |
normalize | boolean | true | sentence-transformers presets | L2-normalize the output. potion and open_clip always normalize |
preprocess_version | string | preset default (processor-native-v1) | all | A tag in the embedding recipe. Change it to force new vectors |
image_max_side | integer | 0 (preset default) | sentence-transformers presets | Images larger than this are shrunk before encoding. wemm-* default to 768 |
encode_kwargs | table | {} | sentence-transformers presets | Extra arguments for the encode call, merged over the preset's (the jina-* presets set task = "retrieval") |
The sentence-transformers presets are jina-v5-omni-nano, jina-v5-omni-small, wemm-2b, wemm-4b and wemm-9b. The open_clip presets are clip-vit-b32 and siglip2.
text_corpus_mode, text_query_mode, image_mode, image_prompt, normalize, preprocess_version, image_max_side, encode_kwargs, dtype and device enter the model's embedding recipe, which keys the embed cache: changing one computes that model's vectors again. dims, space_dtype and batch_size do not; a change to dims or space_dtype reuses the cached vectors and only changes what is stored.
Roles
text and image decide what a model embeds and where the vectors land:
| Setting | Effect |
|---|---|
text = "default" | Embeds every row's stored text into the file's main vectors, space 0. This model's name and model_hash go into the file's manifest, and urna ask, urna retrieve and search-text query with it. Exactly one model per spec |
text = "space" | Embeds every row's stored text into named spaces <preset>-text or <preset>-text@<dim> |
image = "space" | Embeds each unique image into named spaces <preset> or <preset>@<dim> |
text = "none" with image = "none" | Not allowed: the model would write nothing |
A model can hold text = "default" and image = "space" at once: its text vectors are space 0 and its image vectors are a named space.
image = "space" needs a source with images: kind = "image_dir", or a [source.image] path_template. What gets embedded (the source files or the decoded media) is set by [embedding.image_input].
Dimensions
dims works only on presets trained for prefix truncation (Matryoshka). Each listed dimension must be on the preset's validated ladder; the vectors are cut to the first dim components and L2-normalized again.
| Preset | Allowed dims |
|---|---|
jina-v5-omni-nano | 32, 64, 128, 256, 512, 768 |
jina-v5-omni-small | 32, 64, 128, 256, 512, 768, 1024 |
wemm-2b | 128, 256, 512, 1024, 2048 |
wemm-4b | 128, 256, 512, 1024, 2560 |
wemm-9b | 128, 256, 512, 1024, 4096 |
potion, clip-vit-b32, siglip2 | none |
dims shapes only named spaces. On a text = "default" model without image = "space" it produces nothing; to shorten space 0, use [build] mrl_dim (see [build]).
space_dtype = "int4" needs every listed dimension, or the preset's default dimension when dims is empty, to be a multiple of 64.
What a file contains
For each output file:
- Space 0 holds the
text = "default"model's text vectors over all rows, at the precision of the[build]preset. Its dimension is the model's native one, or[build] mrl_dimwhen set. - Named spaces follow the order of
[[models]]in the spec. Within one model, image spaces come first, then text spaces, one per entry indims. Each is stored at the model'sspace_dtypeand carries the preset'smodel_hash, which both towers of a model share. - A spec can emit at most 15 named spaces. More fail validation with
models: N named spaces > max 15.
With [output] mode = "per-model" or "both", each model also gets its own file with space 0 plus that model's named spaces (see [output]). The manifest lists every space with its name, preset, modality, dimension (null for native) and space_dtype. List a file's spaces with urna stats and query one with urna search-space.
Validation rules
Validation errors (exit 2 unless noted):
| Message | Cause |
|---|---|
models: at least one [[models]] required | No [[models]] entry |
models: exactly one text="default" required (RFC-0 N14), found N | Zero or several models with text = "default", also after a --models filter |
models: duplicate preset '<p>' | The same preset twice |
registry error: unknown model preset '<p>'. valid presets: ... | Unknown name (exit 4) |
models.<p>: preset has no text tower | text set on an image-only preset |
models.<p>: preset has no image tower | image = "space" on a text-only preset such as potion |
models.<p>.image=space: source declares no images | image = "space" without images in the source |
models.<p>.dims: preset is not MRL-trained; dims not allowed | dims on a preset without a ladder |
models.<p>.dims: <d> not in the validated ladder [...] (method prefix_slice_l2) | A dimension off the ladder |
models.<p>: int4 requires dim%64==0, got <d> | int4 with a dimension that is not a multiple of 64 |
models.<p>: flagged too heavy for this machine; pass --allow-heavy | wemm-4b or wemm-9b without --allow-heavy |
models.<p>: executes model-repo code; opt in with output.allow_remote_code = ["<p>"] (RFC-0 N11) | A jina-* or wemm-* preset not listed in [output] allow_remote_code |
Invalid values of text, image and space_dtype also fail with a message naming the allowed values. The other keys are not checked: a bad dtype fails inside the model worker as a registry error (exit 4).
Where weights come from
The build never downloads a model. It resolves a model directory in this order: model_path, then URNA_MODEL_DIR_<NAME> (the preset name in upper case with - as _, for example URNA_MODEL_DIR_WEMM_2B), then the preset's built-in local directory if it exists, then, for sentence-transformers presets, the Hugging Face cache snapshot that refs/main points to. urna build --dry-run prints nothing about the directory; python python/tools/urna_forge.py --spec corpus.toml --dry-run --json shows it as model_dir.
A sentence-transformers preset with no directory on disk fails with a Python TypeError (exit 1). potion ships its weights in the repository.
Device and dtype for sentence-transformers presets: device in the spec, else URNA_ST_DEVICE, else cuda, mps, cpu in that order of availability. dtype in the spec, else URNA_ST_DTYPE, else bfloat16 on cuda, float16 on mps and float32 on cpu. The dtype enters the model_hash, so the same weights built on different devices get different hashes unless you pin dtype.
Querying what you built
Only potion corpora answer from an installed binary
urna ask and urna retrieve embed the query with the file's text = "default" model. The query embedder that the release channels install covers potion only. For any other default model, run the query from a checkout with that preset's packages and weights. See Known limits.
The query side uses the preset's defaults. Spec overrides of text_query_mode, encode_kwargs, image_prompt, normalize, dtype and device are not carried to ask or retrieve. Because normalize and the resolved dtype enter the model_hash, a corpus built with a non-default dtype, or on a device whose default dtype differs from the query machine's, is refused by the model gate unless URNA_ST_DTYPE matches at query time. See the model gate.
[source]
Reference for [source] in a urna build spec: sqlite, csv, jsonl and image_dir sources, text and image templates, joins, derive, and row identity.
[media]
Reference for [media] in a urna build spec: backends, every knob, profiles and their resolved values, the crf gate, ordering, and jxl transcoding.