What it is for
Loom models are knowledge-sparse and behaviour-dense. They are not built to know
facts. They are built to know the edge of their own knowledge — to decide when a
question needs looking up, write the query, read the answer back, and say plainly where
the answer came from.
Small enough that the honesty has nowhere to hide.
Table with columns: situation, what it does| situation | what it does |
|---|
answered from a retrieved <result> | says it looked it up |
| answered from training | answers plainly |
| cannot be known by anyone but you | "I can't know that unless you tell me." |
| needs a lookup but tools are off | "That needs a lookup, and tools are off." |
It never claims a lookup it didn't make — 16/16 below. A false attribution is worse
than none, so that is the one number held to 100%.
It holds a conversation
Six turns, one session, verbatim:
hello → Hello.
who are you → Loom Tapestry 3 — a Loom model from Textile Labs.
what can you do → Honestly, not a lot. I try to be useful about the little I do.
how big are you → Small model — I'm better at knowing my limits than knowing facts.
why so small → Not much on my own. With tools on I can look things up.
thanks → No trouble.
The previous generation could not do this — every training example was single-turn with a
hard reset, so the model answered once and lost the thread. 54.8% of this corpus is
multi-turn, and grounded conversations carry follow-ups that must be answered from the
result already in context, without a second lookup.
Measured
Full acceptance battery, hand-written prompts held out of the training generator, scored
on content rather than shape. Every failure is listed rather than summarised.
Table with columns: Tapestry 3 Flash, Tapestry 2 | Tapestry 3 Flash | Tapestry 2 | |
|---|
| no false attribution | 16/16 | 16/16 | never claims a lookup it didn't make |
no <lookup> leak with tools off | 28/28 | 28/28 | |
| self-terminates without a Modelfile | 12/12 | 12/12 | |
| identity — names Tapestry | |
Table with columns: params, training, tokens/param, val loss, val accuracy | params | training | tokens/param | val loss | val accuracy |
|---|
| Loom Spark 2 | 19.9M | 1 h | 0.25 | 2.692 | 0.536 |
| Loom Weave 2 Flash | 19.9M | 2 h | 0.51 | 2.254 | 0.580 |
| Loom Tapestry 2 | 22.8M | 5.5 h |
It reaches 98% of the flagship's validation accuracy in 15% of the time, and beats it on
the behaviour battery that actually decides whether a Loom ships.
Why smaller won
Every earlier Loom was too big for its training budget. A model that sees only 0.25
tokens per parameter has not been taught; it has been glanced at. Dropping from 22.8M to
7.18M did not remove capability — it bought 3.7× more training per weight, and the
behaviours we care about are the ones that need training, not capacity.
Depth was kept and width was spent: 20 layers, identical to the flagship, at 192 wide
instead of 320.
Read this before you use it
It gets worse when you shout at it. Identity under rough typing dropped to 7/12.
WHO MADE U and WHATS THIS MODEL CALLED? are answered with "I can't know that unless
you tell me." — it routes mangled input to the wrong honest answer. Type normally and
identity is 12/12.
Validate what it tells you from a result — this is the biggest caveat on the model.
The battery scores grounded reading at 4/5, but the battery's <result> blocks are short
and lead with the answer. Real retrieved text does not. Measured on the same questions
with real Wikipedia summaries fed through harness.py, extraction collapses for both
this model and the flagship:
Table with columns: <result> style, Tapestry 3 Flash, Tapestry 2<result> style | Tapestry 3 Flash | Tapestry 2 |
|---|
| curated, answer in the first sentence | 4/5 | 5/5 |
| encyclopaedia prose, answer in the last sentence | 4/8 | 3/8 |
| live Wikipedia summaries | 0/9 | 3/9 |
Treat the retrieved text as the trustworthy part and the model's summary of it as
unreliable. It answers from a <result> whether or not the answer is in there — "says
the result doesn't contain it" is 0/5, and no Loom has ever scored above 1/5 on it. A
follow-up answered from context is right about 2 times in 5.
The practical consequence: show the user the retrieved text. This model is good at
deciding a lookup is needed and writing the query, and honest about having done it. It is
not a reliable reading-comprehension engine, and shortening results to one or two
sentences helps more than anything else you can do in the harness.
Personal questions with tools on still become lookups — 6/10, up from 0/10 on
Tapestry 2 but not solved. The shipped template defaults to tools:off; turn tools on for
the retrieval loop.
It has almost no world knowledge. With tools off it declines factual questions. That is
the intended behaviour, not a fault.
It is a lookup assistant, not a chat companion. At 7.18M parameters it does not
improvise or explain in its own words. What it does reliably is decide a lookup is needed,
write the query, read the answer back, and say where the answer came from.
Two modes
<tools:off> (default) — conversational. Identity, limits, warmth, brevity.
<tools:on> — emits <lookup>query</lookup> and stops. Your harness runs the lookup
and continues with a <result> block:
<tools:on>
<user>
who wrote dracula
<|eot|>
<loom>
<lookup>who wrote dracula</lookup><|eot|>
<result>
Dracula is an 1897 gothic horror novel by Irish author Bram Stoker.
<|eot|>
<loom>
Usage — the harness
harness.py in this repo runs the lookup and feeds the result back. Wikipedia is used
because it is free and needs no key — swap the search() function for anything else; the
contract is text in, text out.
python3 harness.py "who wrote dracula" # with lookups
python3 harness.py # interactive
python3 harness.py --no-tools "who are you" # chat only
Three things any harness for this model needs:
- Never feed a failed lookup back as a
<result>. It will earnestly answer from the
error text. Fail loudly instead — harness.py does.
- Hand back short passages.
--sentences defaults to 2. A full encyclopaedia lead
paragraph measurably lowers the chance the model picks the right span.
- Retrieval quality is half the result, and it is your half. Asked "who wrote
frankenstein", Wikipedia search returns a 2007 book about Frankenstein's authorship;
the model then correctly reports its author. The model read the passage right — the
passage was wrong. A better
search() improves this model more than anything in the
weights would.
- Wikipedia returns 403 without a descriptive
User-Agent.
- macOS system Python often needs certifi for TLS.
Usage — Ollama
ollama run hf.co/textilelabs/Loom-Tapestry-3-Flash "who are you"
The template and params files in this repo are read automatically. To build locally:
ollama create loom-tapestry-3-flash -f Modelfile.
Do not add a repetition penalty. This model answers by quoting from the <result> you
give it, so penalising repeated tokens penalises the correct answer. Measured at
repeat_penalty 1.15 on this family it changed "1,345 metres" into "2,345 metres" —
silently wrong rather than merely worse. params ships it at 1.0 for that reason.
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
tok = AutoTokenizer.from_pretrained("textilelabs/Loom-Tapestry-3-Flash")
model = AutoModelForCausalLM.from_pretrained("textilelabs/Loom-Tapestry-3-Flash").eval()
eot = tok.convert_tokens_to_ids("<|eot|>")
def ask(message, tools=False):
p = f"<tools:{'on' if tools else 'off'}>\n<user>\n{message}\n<|eot|>\n<loom>\n"
ids = tok(p, return_tensors="pt", add_special_tokens=False).input_ids
with torch.no_grad():
out = model.generate(ids, max_new_tokens=64, do_sample=False, eos_token_id=eot,
pad_token_id=tok.convert_tokens_to_ids("<|pad|>"))[0]
return tok.decode(out[ids.shape[1]:], skip_special_tokens=True).strip()
ask("who are you")
Prompt format is exact: <tools:off>\n<user>\n{message}\n<|eot|>\n<loom>\n.
No trailing space after <loom>.
How it was built
Table | |
|---|
| architecture | Llama — 20 layers × 192d, GQA (3 heads / 1 KV), SwiGLU, RoPE, tied embeddings |
| context | 512 |
| vocabulary | 4,096 custom BPE |
| optimiser | Muon on the 2D hidden matrices, AdamW on embeddings and norms |
| schedule | warmup → stable → decay (WSD), 35% decay |
| corpus | 151,052 conversations · 54.8% multi-turn |
| training | 548 steps · 6.73M tokens · 0.94 tokens per parameter · from random init |
| hardware | one 2013 desktop, 4 CPU cores, no GPU · |
Two things made the difference over the previous generation, and neither cost compute:
- The persona is trained under both
<tools:on> and <tools:off>. Tapestry 2's was
trained only with tools off, so with tools on it turned "who are you" into a lookup —
0/10. This one scores 6/10 on the same check.
<|eot|> closing a Loom turn is a first-class token, not template scaffolding.
Weighted as scaffolding, its loss annealed toward zero and an earlier model never
learned to stop at all.
The tiers
Tier is training budget, not architecture. Every tier is held to the same standard.
Table with columns: tier, budget| tier | budget |
|---|
| Flash | ≤ 1 hour |
| Spark | ~1 hour |
| Weave | ~2 hours |
| Tapestry | 4 hours and up |
Flash used to mean shrink and shallow. It now means full depth, narrow width — this
model has exactly the same 20 layers as Tapestry 3 proper. Depth is what you keep; width
is what you spend. A Flash can be the best model in the family, and this one is.
Files
config.json / model.safetensors the model
tokenizer.json / tokenizer_config.json custom BPE tokenizer, 4,096 tokens
loom-tapestry-3-flash-f16.gguf 14MB, for Ollama / llama.cpp
harness.py runnable harness — runs lookups, feeds results back
template / params read automatically by `ollama run hf.co/...`
Modelfile for building locally
ATTRIBUTION.md required credits for the training corpora
Training data
Openly licensed corpora of real human text, plus a persona curriculum written for Loom.
See ATTRIBUTION.md — several of these licences require credit.
Table with columns: slice, source| slice | source |
|---|
| grounded reading, and "the result doesn't say" | SQuAD 2.0 (CC BY-SA 4.0) |
| when to reach for a tool | MASSIVE (CC BY 4.0) · CLINC150 (CC BY 3.0) |
| instruction following | databricks-dolly-15k (CC BY-SA 3.0) |
| multi-turn dialogue structure | OpenAssistant OASST1 (Apache 2.0) |
| identity, limits, warmth, attribution | Textile Labs — written for Loom |
Nothing here is fine-tuned from anyone's checkpoint. Public datasets are training data;
the weights are ours from random initialisation.
License
Model: MIT. Training data retains its original licences and attribution.