Scene Atlas Large

An imageโ€“text embedding model for searching tabletop RPG art: battlemaps, scene and environment illustrations, portraits, tokens, items and maps. It is the high-accuracy model of Scene Atlas, a tool that helps Game Masters organise and search their art libraries, for computers with a GPU.

It is google/siglip2-large-patch16-384 fine-tuned on about 123,000 captioned TTRPG images. On a benchmark of Game Master search queries it scores nDCG@10 0.640, against 0.585 for SigLIP2 L-384 (zero-shot) and 0.589 for SigLIP2 so400m (zero-shot, previous Scene Atlas large).

Variants

This repo holds the same model in two formats:

Variant Location Format Size nDCG@10
Full repo root transformers checkpoint, fp16 safetensors 1682 MB 0.640
Quantised onnx/ ONNX: 8-bit weights, fp16 activations 680 MB 0.640 (-0.1%)

Use the quantised variant to run the model: it is what Scene Atlas downloads, it needs about 760 MB of memory, and it runs on CPU, CUDA and DirectML with ONNX Runtime 1.24 or later. Use the full variant to fine-tune further, or to work in PyTorch.

Evaluation

The benchmark has 664 search queries written the way a Game Master would type them, such as "medieval village", "burning airship" or "frozen tundra under the northern lights". Each query searches a gallery of 8,190 held-out test images, restricted to the query's art category. Test images were held out of training together with their known variants. A Claude Haiku judge graded the relevance of every image that any model below ranked in its top 10 for a query; only clear matches count as relevant.

Model All Tactical maps Environments Maps Portraits Tokens Items
SigLIP2 L-384 (zero-shot) 0.585 0.483 0.640 0.596 0.594 0.666 0.760
SigLIP2 so400m (zero-shot, previous Scene Atlas large) 0.589 0.514 0.625 0.556 0.602 0.630 0.785
This model (full) 0.640 0.550 0.699 0.698 0.633 0.669 0.802
This model (quantised) 0.640 0.549 0.699 0.699 0.634 0.670 0.800

The quantised variant was scored through ONNX Runtime with inputs fed exactly as Scene Atlas feeds them. Its difference from the full model is -0.09% (95% CI -0.30% to +0.13%, paired bootstrap over queries).

Usage

ONNX (quantised)

import json
import numpy as np
import onnxruntime as ort
from huggingface_hub import snapshot_download
from PIL import Image
from tokenizers import Tokenizer

d = snapshot_download("jennis0/scene-atlas-large", allow_patterns=["onnx/*"]) + "/onnx"
m = json.load(open(f"{d}/manifest.json"))
vision = ort.InferenceSession(f"{d}/{m['vision_encoder']}")
text = ort.InferenceSession(f"{d}/{m['text_encoder']}")

tok = Tokenizer.from_file(f"{d}/tokenizer/tokenizer.json")   # lowercases for you
tok.enable_truncation(max_length=m["tokenizer_max_length"])
tok.enable_padding(length=m["tokenizer_max_length"], pad_id=m["tokenizer_pad_id"])
ids = np.array([tok.encode("ruined dwarven forge with lava channels").ids], dtype=np.int64)
text_emb = text.run(["embeddings"], {"input_ids": ids, "attention_mask": np.ones_like(ids)})[0]

size = m["image_size"]
img = Image.open("map.jpg").convert("RGB").resize((size, size), Image.BILINEAR)  # plain resize, no crop
mean, std = np.array(m["image_mean"], np.float32), np.array(m["image_std"], np.float32)
x = ((np.asarray(img, np.float32) / 255 - mean) / std).transpose(2, 0, 1)[None]  # float32 in, float32 out
image_emb = vision.run(["embeddings"], {"pixel_values": x})[0]

score = float(text_emb[0] @ image_emb[0])  # both outputs are L2-normalised

Transformers (full)

import torch
import torch.nn.functional as F
from PIL import Image
from transformers import AutoModel, AutoProcessor

model = AutoModel.from_pretrained("jennis0/scene-atlas-large").eval()
processor = AutoProcessor.from_pretrained("jennis0/scene-atlas-large")

# Pad text to 64 tokens and pass no attention mask: SigLIP2 is trained that way.
text = processor(text=["ruined dwarven forge with lava channels"],
                 padding="max_length", max_length=64, return_tensors="pt")
image = processor(images=[Image.open("map.jpg")], return_tensors="pt")
with torch.no_grad():
    t = model.get_text_features(input_ids=text["input_ids"])
    i = model.get_image_features(pixel_values=image["pixel_values"])
t, i = (F.normalize(getattr(x, "pooler_output", x), dim=-1) for x in (t, i))
score = (t @ i.T).item()

Two things that break SigLIP2 text embeddings:

  • A padding attention mask. SigLIP2 attends over all 64 positions and pools the last one, which is padding. A padding mask changes that and collapses retrieval: nDCG@10 falls to about 0.03, for this model and for the base model. The ONNX text graph ignores its attention_mask input, so callers that pass one are safe.
  • Capital letters. The model was trained on lowercased text. Both tokenizers in this repo lowercase automatically. If you tokenise some other way, lowercase first.

Quantisation

The ONNX variant is about 60% smaller than the fp16 checkpoint:

  • Pruned vocabulary. The 256k-token multilingual vocabulary is cut to the 52,113 tokens that English text and TTRPG captions use, closed under BPE merges. A rare word splits into smaller kept pieces rather than becoming unknown. Token ids are unchanged, and the graph maps them to the compact table.
  • 8-bit token table, in blocks of 32.
  • 8-bit vision weights, quantised with GPTQ in blocks of 32.
  • 8-bit text weights, rounded to nearest in blocks of 128.

GPTQ was calibrated on captions and 256 thumbnails of training images only. Activations stay fp16.

Spec: vocab=wiki32k,table=int8b32,text=w8,vision=w8b32g, built with the onnx-export compress command of the Scene Atlas training repo.

Training

  • Recipe: LoRA (rank 32 on the attention and MLP layers; 28.7M trainable parameters) with a sigmoid contrastive loss, at 128 pairs per step for 1,930 steps, then merged into the base weights. The full recipe is training/configs/siglip2_l_384_rc4.yaml.
  • Data: 123,525 images, of which 35,306 tactical map, 30,264 portrait, 23,515 environment, 15,627 token, 13,807 scene, 3,777 item, 1,229 map. Captions and search-style descriptions were written by Claude Haiku and lowercased. Training mixed short and long captions, search strings and Game Master-style queries.

Limitations

  • English only. Other languages still tokenise, as byte-level fallback pieces, but were never trained on.
  • Fantasy and TTRPG art. Expect base-SigLIP2 quality or worse on other domains.
  • Relevance labels come from an LLM judge, checked against human judgments on a subset of queries. Absolute scores aren't comparable with human-judged benchmarks.

License

Apache 2.0, inherited from google/siglip2-large-patch16-384.

Downloads last month
16
Safetensors
Model size
0.9B params
Tensor type
F16
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for jennis0/scene-atlas-large

Finetuned
(4)
this model

Collection including jennis0/scene-atlas-large