Skip to main content
Version: 0.11

Embedding Models

Embedding models turn text into vectors for semantic search. They power the /v1/embeddings endpoint, the embed SDK calls, and the vectors you store and query in the KV store.

Embedding models are encoders (BERT-family), not chat models. The engine routes them automatically: loading an embedding model id through model_load or embed resolves to the same shared encoder instance, loaded once and reused across requests.

The model you pick determines the vector dimension your KV store must use (embed_dim): MiniLM, bge-small, and granite-small are 384-dim; bge-base and granite-english are 768-dim (see the table below).

Built-in models

These ids work out of the box — by short name or full HuggingFace repo id:

Short nameHF repo idDimParamsNotes
all-MiniLM-L6-v2sentence-transformers/all-MiniLM-L6-v238423MDefault; fast, ~90 MB
bge-small-en-v1.5BAAI/bge-small-en-v1.538433MHigher quality English
bge-base-en-v1.5BAAI/bge-base-en-v1.5768109MHigher quality, larger
granite-embedding-small-english-r2ibm-granite/granite-embedding-small-english-r238447MModernBERT, 8K context
granite-embedding-english-r2ibm-granite/granite-embedding-english-r2768149MModernBERT, 8K context
granite-embedding-97m-multilingual-r2ibm-granite/granite-embedding-97m-multilingual-r238497MMultilingual
granite-embedding-311m-multilingual-r2ibm-granite/granite-embedding-311m-multilingual-r2768311MMultilingual

Prefix with the backend as usual, e.g. in-memory::sentence-transformers/all-MiniLM-L6-v2.

Any other BERT-family model

Models outside the built-in list (gte, e5, arctic, nomic — any sentence-transformers checkpoint with model_type: "bert" or "modernbert" in its config.json) load generically: the engine detects the backbone from config.json and the pooling mode from the sentence-transformers 1_Pooling/config.json (mean pooling if absent). Embeddings are always L2-normalized.

A loadable snapshot needs at least:

config.json
tokenizer.json
model.safetensors (float weights) — OR — model.gguf (quantized)
1_Pooling/config.json (optional — pooling mode, defaults to mean)

Quantized (GGUF) embedding models

BERT embedders can ship as quantized GGUF instead of float safetensors. An 8-bit MiniLM is ~24 MB on disk and in RAM, versus ~90 MB for the float snapshot — worthwhile for download size and for on-device/iOS memory.

Drop a model.gguf (llama.cpp bert architecture, e.g. the Q8_0 build of MiniLM) in place of model.safetensors, keeping the same config.json and tokenizer.json next to it. The engine detects the GGUF automatically, reads the linear projections as quantized tensors (embeddings and layer norms are dequantized to F32), and serves it through the same embeddings API — no model id change. When both model.safetensors and model.gguf are present, the float weights win.

Notes:

  • Only the BERT backbone has a quantized graph today; ModernBERT (Granite r2) must use safetensors.
  • config.json is still required (it's ~600 bytes; the size win is entirely in the weights file).

Cache layout & shipping offline

The engine resolves embedding models in this order — no network is touched if a local copy exists:

  1. <cache>/embeddings/<model-id>/ — canonical location (the asset downloader places them here)
  2. <cache>/models/<model-id>/ — general model cache
  3. HuggingFace download (materialized into <cache>/embeddings/ for next time)

To ship a game that works offline, place the snapshot under either directory of your configured model directory, e.g.:

<ModelDirectory>/embeddings/sentence-transformers/all-MiniLM-L6-v2/
├── config.json
├── tokenizer.json
├── model.safetensors # or model.gguf for the quantized build
└── 1_Pooling/config.json

Publishing to an object store works the same for both formats — atelico-asset-publisher publish-model <id> materializes the snapshot (safetensors or GGUF) under embeddings/<id>/ and uploads the tree.

Usage: embedding text

embed turns one or more strings into vectors. Warm-loading the model first is optional — it loads lazily on the first embed call — but pre-loading moves the one-time cost off your first request.

import atelico, json
engine = atelico.Engine()

# Optional warm-load (otherwise the first embed() pays the load cost)
engine.model_load("in-memory::sentence-transformers/all-MiniLM-L6-v2")

resp = json.loads(engine.embed(json.dumps({
"model": "in-memory::sentence-transformers/all-MiniLM-L6-v2",
"input": ["the cat sat on the mat", "a feline rests on the rug"],
})))
vectors = [d["embedding"] for d in resp["data"]] # two 384-dim vectors

Embeddings on their own are just vectors. To build NPC memory, RAG, or lore lookups you store and query them:

Errors you might see

  • '<id>' is not a loadable embedding model: model_type '<x>' is not a supported encoder — the snapshot's config.json names an architecture the embedder can't run. Supported: bert, modernbert.
  • failed to load embedding model '<id>': ... — the id is neither a built-in alias nor a resolvable snapshot. The message lists the built-in models and the expected cache layout. Unknown ids are an error — the engine never silently substitutes a different embedding model, because vectors from different models are not comparable.
  • GGUF architecture 'bert' is an embedding encoder, not a chat model — you tried to load a quantized embedder as a chat model. Quantized embedders are supported, but through the embeddings API: name it as an embedding_model / embed target, not a chat model.