Needle 2: the memory budget is written first

1 month ago

A watch has almost no memory to spare. Whatever turns your words into a command has to fit in what is left. Needle 2 does that job. The file the project trained is just over ninety megabytes. The file that ships to the device is thirteen point seven megabytes, and that is the whole model.

Ask

Ask about this presentation

Answers are generated from this presentation.

Chapters

  1. 0:00Needle 2 · a model that turns what you say into an exact command
  2. 0:16It answers only in the functions you declared
  3. 0:35Memory is the hard limit on a watch or a phone
  4. 0:53The memory budget is fixed first, and it decides the most this can hold
  5. 1:20The model cannot forget what it is allowed to do
  6. 1:40The rule comes from your code, not from the model
  7. 2:10It trades five points of accuracy for a model a fraction of the size
  8. 2:27Builds for sixteen kinds of device, and a 13.7 MB model file
  9. 2:44A model sized by what the device can spare
Show transcript

Needle 2 · a model that turns what you say into an exact command

the training file
90.4 MB
90,426,504 bytes
what ships to the device
13.7 MB
13,737,807 bytes
44.9M weights at 2.2 bits = 12.35 MB, about 90% of the file

Needle 2

a model that turns what you say into an exact command
Apache-2.0, free and open Python 16 kinds of device
both files, Cactus-Compute/needle2, read 2026-08-20

A watch has almost no memory to spare. Whatever turns your words into a command has to fit in what is left. Needle 2 does that job. The file the project trained is just over ninety megabytes. The file that ships to the device is thirteen point seven megabytes, and that is the whole model.

It answers only in the functions you declared

It answers only in the functions you declared.

the functions you declared, in your own code
set_lightslock_door
“dim the bedroom lights to 20 percent and lock the front door”
needle playground, cactus-needle 2.0.7
the reply, exactly as the model wrote it
a different set of functions declared, and a question they do not cover

You write an ordinary function in your own code. Then you say what you want. Dim the bedroom lights to twenty percent, and lock the front door. What comes back is the exact instruction your program takes: which action to run, and the values to run it with. Ask for a poem instead and the answer comes back empty, because writing poems is not one of your functions.

Memory is the hard limit on a watch or a phone

Memory is the hard limit on a watch or a phone.

“Every turn returns one JSON object:”
{
"type": "call",
"success": true,
"error": null,
"error_code": null,
"function_calls": [ { "name": "set_lights", "arguments": { "room": "living room", "on": true, "brightness": 30 } } ],
"reasoning": "'living room' -> room; 'dim' -> on true, brightness 30",
"confidence": 0.94,
"prefill_tps": 4300.0,
"decode_tps": 850.0,
"peak_ram_mb": 28.5
}
peak_ram_mb 28.5 · one turn’s own figure, from the project’s example reply
the shipped settings declare 28 MB for a whole session
doc/apis.md · config.json, v2.0.7

On a watch, memory is the number that cannot move. A model eats memory as a conversation runs. The model keeps running notes on everything said so far. Needle 2 runs a whole session in about twenty-eight megabytes. And every answer Needle 2 sends back carries the memory that answer used, right there in the reply.

The memory budget is fixed first, and it decides the most this can hold

The memory budget is fixed first, and it decides the most this can hold.

KV_BUDGET_BYTES = 11 * 1024 * 1024 + 512 * 1024
KV_GROUP = 32
KV_WINDOW_MIN = 160
def kv_budget_window(config):
head_dim = (getattr(config, "attn_dim", 0) or config.d_model) // config.num_heads
kv = config.num_kv_heads * head_dim
d, L = config.d_model, config.num_layers
sites = len(tuple(getattr(config, "engram_layers", (2, 15))))
per_pos = (L * (2 * kv + 2 * (kv // KV_GROUP) * 4)
+ sites * (d + (d // KV_GROUP) * 4))
window = (KV_BUDGET_BYTES // per_pos) // KV_GROUP * KV_GROUP
return max(KV_WINDOW_MIN, min(window, config.max_seq_len))
def effective_kv_window(config):
budget = kv_budget_window(config)
return min(budget, config.kv_window) if config.kv_window else budget
eleven and a half megabytes, decided in advance
what one more chunk of text costs to remember: 16,704 bytes
divide one by the other, and take the smaller of that and the window it was trained with
needle/model/architecture.py, v2.0.7

Models are normally described by how much conversation they can hold. Needle 2 works backwards. The source picks the memory first: eleven and a half megabytes, written down as a constant. Then the code works out what one more chunk of text costs to remember. A chunk is a short word or a piece of one. That costs about sixteen thousand seven hundred bytes. Divide the budget by that cost, and the answer is a hard ceiling on how far back this model may ever see. The project writes that ceiling into the file that ships.

The model cannot forget what it is allowed to do

The model cannot forget what it is allowed to do.

"sliding_window": 256
the shipped model’s window, declared in its own settings file
the ceiling from that division was 704
“window 256 + pinned tool sinks”
the project’s own architecture diagram, quoted from the strip above
config.json and assets/architecture.png, v2.0.7

The model that shipped keeps a window of two hundred and fifty-six chunks of text, well under that ceiling. A long conversation would push the description of what this model may do off the back of a window that short. So the project keeps that description exempt, and marks it on the model’s own diagram. The description stays in memory however far behind it falls.

The rule comes from your code, not from the model

The rule comes from your code, not from the model.

from typing import Literal
@needle.tool
def set_thermostat(temperature: int, mode: Literal["heat", "cool", "auto"] = "auto"):
"""Set the thermostat.
Args:
temperature: target temperature in Celsius
mode: heating strategy to use
"""
return {"temperature": temperature, "mode": mode}
heat cool auto
the only three values that have a path out
“a Literal becomes a fixed set the model must choose from (it cannot emit anything else)”
the project’s own API documentation
“With one declared tool the grammar admits exactly one call of that name, so schema conformance is guaranteed rather than requested.”
the project’s own API documentation
doc/apis.md, v2.0.7

Needle 2 turns your own function into a rule. That rule decides, letter by letter, which characters are allowed next. If your function accepts only heat, cool or auto, there is no path through which any other word can be produced. The project’s own documentation says the model cannot emit anything else. With one function declared, it says, the shape of the answer is guaranteed rather than requested. The reasoning the model writes alongside is left free, so that part stays readable.

It trades five points of accuracy for a model a fraction of the size

It trades five points of accuracy for a model a fraction of the size.

accuracy on that test set, in percent
Needle 263.7
FunctionGemma 270M64.0
LFM2.5 230M69.1
Apple FM57.6
2 bits per weight against their 16
chart by Cactus Compute · test set: google/mobile-actions, CC-BY-4.0

On the project’s own chart, scored on a public set of everyday phone commands that Google put out, Needle 2 lands at sixty-three point seven percent. That is level with a model six times its size, and five points of accuracy off the highest score there. Five points, for a model five to sixty-seven times smaller that runs with the network off.

Builds for sixteen kinds of device, and a 13.7 MB model file

Builds for sixteen kinds of device, and a 13.7 MB model file.

free and open, Apache-2.0 · installs from the Python package index · works with the network off after the first run · v2.0.7
13.7 MB is the file that goes on the device · the install itself pulls 570 MB across 42 packages
needle/agent/fetch.py and the project’s download page, 2026-08-20

The project publishes builds for sixteen kinds of device: phones, watches and TVs. Needle 2 is free and open, and it installs from the Python package index. After the first run, a machine with no connection at all can use Needle 2. And that thirteen point seven megabytes is the file that goes on the device, not the size of the download.

A model sized by what the device can spare

A model sized by what the device can spare.

github.com/cactus-compute/needle
new repo tomorrow

Needle 2 is described by the memory it is allowed to spend, and how much conversation that buys is what falls out. The project’s own chart leaves open how much smaller this can get before the answers stop being good enough. Cactus dash compute slash needle, on GitHub. New repo tomorrow.