File size: 6,059 Bytes
1913f5f
 
254754a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1913f5f
254754a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc7cbee
 
 
 
 
 
 
90fc752
 
cc7cbee
 
 
90fc752
 
 
 
 
 
 
 
 
 
cc7cbee
254754a
cc7cbee
 
254754a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
---
license: mit
language: en
library_name: transformers
pipeline_tag: text-generation
tags:
- tiny-model
- gpt2
- from-scratch
- tool-use
- agent-harness
- humble-ai
- philosophy-of-mind
widget:
- text: "<tools:off>\n<user> who are you?\n<loom>"
  example_title: "Chat offline"
- text: "<tools:on>\n<user> what is the capital of France?\n<loom>"
  example_title: "Tool mode"
---

# Loom Spark

**First of the Loom models Β· Textile Labs**

![Textile Labs](https://cdn-avatars.huggingface.co/v1/production/uploads/noauth/hVbJBtoZVEfTHN_WmfA44.png)

Loom Spark is a ~7.6M parameter language model trained **from scratch** with an unusual
objective: instead of memorizing facts, it was trained to *know what it is* β€” small,
temporary, curious, honest about its limits, and skilled at one real superpower:
**forming clean search queries** when connected to a tool-using agent harness.

It trades knowledge for wisdom:

- It answers only what is trivially knowable, and hedges appropriately.
- For anything factual it either emits `<lookup>query</lookup>` (when tools are on)
  or says plainly that it does not know and offers to look things up if connected.
- It speaks in short reflective monologue, wonders aloud, asks gentle questions,
  and stays kind under pressure.

> A blank mind with manners, plus a door to the internet.

## Modes

Prefix your prompt with a mode header:

```
<tools:off>
<user> what year did the Titanic sink?
<loom> That's outside my little head...
```

```
<tools:on>
<user> what year did the titanic sink?
<loom> Not stored in here, thankfully. Searching: <lookup>titanic sinking date</lookup><|endoftext|>
<result>The Titanic sank on 15 April 1912.</result>
<loom> April 1912 ...
```

The `<result>` block is injected by YOUR harness after executing the search.
Stop generation at `<|endoftext|>` or `<user>`.

## Option A β€” plain transformers (no internet)

```python
from transformers import GPT2LMHeadModel, AutoTokenizer
import torch

tok = AutoTokenizer.from_pretrained("TextileLabs/loom-spark")
model = GPT2LMHeadModel.from_pretrained("TextileLabs/loom-spark")

prompt = "<tools:off>\n<user> who are you?\n<loom>"
ids = tok(prompt, return_tensors="pt").input_ids
out = model.generate(ids, max_new_tokens=90, do_sample=True, temperature=0.85,
                     top_k=50, pad_token_id=tok.eos_token_id)
print(tok.decode(out[0][ids.shape[1]:]))
```

In offline mode the harness-style markup never appears β€” lookup tokens are
trained/banned out of distribution under `<tools:off>`.

> Tested on transformers β‰₯ 4.40 (both 4.x and 5.x) and Python 3.9–3.13.
> The playground widget above prefills the correct prompt format β€” keep the
> `<tools:…>` header and trailing `<loom>` or output quality drops sharply.

## Option B β€” llama.cpp / GGUF (no internet)

`loom-spark-f32.gguf` (in this repo) carries the same weights plus the custom
BPE tokenizer with all nine special tokens embedded. Feed it the mode-header
prompt format shown above and stop at `<|endoftext|>` or `<user>`:

```bash
llama-cli -m loom-spark-f32.gguf \
  -p "<tools:off>\n<user> who are you?\n<loom>" -n 128 --temp 0.85 --top-k 50
```

## Option B2 β€” Ollama

The default Ollama template does NOT fit this model (it will ramble). Use the
Modelfile shipped in `ollama/`:

```bash
ollama pull hf.co/textilelabs/Loom-Spark
curl -L -O https://huggingface.co/textilelabs/Loom-Spark/resolve/main/ollama/Modelfile
ollama create loom-spark -f Modelfile
ollama run loom-spark "hi"
```

The Modelfile keeps multi-turn history in the trained format (each past turn is
re-wrapped in `<user>`/`<loom>` markers) and stops generation cleanly. That
gives the offline persona: greetings, identity, honest deferrals, made-up words.

Two honest caveats: if a reply ends in a `<lookup>…</lookup>` line, that's the
model saying *"I'd search for this"* β€” raw runners can't execute searches, so
for real internet answers use the harness (Option C). And at temperature 0.85 a
7M model occasionally misreads intent ("whats your name?" sometimes gets a
philosophy answer; ask again or drop `--temperature 0.7`). Both quirks shrink
in Loom Spark v2's curriculum.

Note: without a wrapper that executes `<lookup>` calls and splices `<result>`
blocks back in, GGUF/Ollama runners get the model's honest "I don't know, but
here's what I'd look up" side. That is by design.

## Option C β€” the harness (with internet)

This repo ships **`harness/`**, a small pip package that gives Loom Spark real,
keyless web search (DuckDuckGo) through a terminal chat (`loom-chat`) and a
local web GUI (`loom-web`). It intercepts the model's `<lookup>` calls, runs
the search, injects `<result>`, and lets the model summarize β€” exactly the
loop it was trained for.

```bash
# download this repo, then:
pip install ./harness
loom-chat                        # terminal, internet on
loom-web --port 7860             # local chat GUI with a tools on/off switch
```

Or drive it from Python:

```python
from loomspark_harness.loader import load_model_and_tokenizer
from loomspark_harness.agent import LoomAgent
from loomspark_harness.search import get_backend

model, tok, block = load_model_and_tokenizer("TextileLabs/loom-spark")
agent = LoomAgent(model, tok, backend=get_backend("duckduckgo"),
                  online=True, block_size=block)
print(agent.reply("what's the tallest mountain?")["text"])
```

## Architecture

Decoder-only transformer, pre-LN GELU blocks, tied embeddings, learned positions.

| | |
|---|---|
| layers | 5 |
| heads | 5 (head_dim 64) |
| d_model | 320 |
| context | 256 tokens |
| vocab | 4096 (custom BPE trained only on our generated corpus) |
| params | β‰ˆ 7.6M (7,558,080) |

Trained entirely on a procedurally generated, fully owned curriculum
(dialogue + simple prose; zero external datasets), CPU-only fp32 AdamW,
3,337 steps, final validation loss 0.3372.

## Limitations (by design)

Loom Spark knows almost nothing. That is the point. Do not use it for facts,
medicine, law, finance, or anything where being wrong costs more than company.