hmunachii commited on
Commit
f35ccbc
·
verified ·
1 Parent(s): ab97b31

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +123 -36
README.md CHANGED
@@ -11,22 +11,30 @@ tags:
11
  - webassembly
12
  ---
13
 
14
- ![Needle 2](banner.png)
15
 
16
  # Needle 2
17
 
18
- Needle 2 is a 45M-parameter foundation tool call/use model for tiny devices; run it or embed it in phones, wearables, watches, TVs, smart home, small robots.
19
- Compressed to CQ2-bit with Cactus Quants, the whole model ships as a single 14MB binary that runs a full session in 28MB of RAM.
20
- A Raspberry Pi 5 prefills at 1.3k tok/s and decodes at 500+; an iPhone 17 Pro prefills at 3k+ and decodes at 1k+.
21
 
22
  - **Self-contained**: model baked into the binary, no runtime, no downloads, no network.
23
- - **Runs everywhere**: ARM64, x86-64, ARMv7, RISC-V, Cortex-M, and WebAssembly, on Apple, Windows, Linux, Android, Raspberry Pi.
24
  - **Simple contract**: tool calls come back as structured data, text in, JSON out; a byte-level grammar compiled from your schemas constrains every token.
25
  - **Confidence-gated**: every response carries a calibrated confidence score from a learned head; set a threshold, act above it, escalate below it.
26
- - **Tool retrieval**: declare a large catalogue and a built-in retrieval head renders only the top ten tools per turn, with the grammar constrained to that subset.
27
- - **Bounded memory**: a 256-token sliding window with the tools pinned as KV sinks; session memory caps near 14MB no matter how long the conversation runs.
28
 
29
- ![Size-quality frontier: mobile-class and below](frontier.svg)
 
 
 
 
 
 
 
 
 
 
30
 
31
  ## Quickstart with Python
32
 
@@ -34,33 +42,102 @@ A Raspberry Pi 5 prefills at 1.3k tok/s and decodes at 500+; an iPhone 17 Pro pr
34
  pip install cactus-needle
35
  ```
36
 
37
- The PyPI package is a small pure-Python shim; the first `import needle` downloads the engine for your platform (~13 MB, cached in `~/.cache/cactus-needle`), so the first run needs network. Every run after is offline.
 
 
38
 
39
  ```python
40
  import needle
41
 
42
- tools = [
43
- {"name": "set_timer",
44
- "parameters": {"type": "object", "properties": {"minutes": {"type": "integer"}, "label": {"type": "string"}}}},
45
- {"name": "play_music",
46
- "parameters": {"type": "object", "properties": {"query": {"type": "string"}}}},
47
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  agent = needle.Needle(tools=tools)
50
- # with many tools, persist their embeddings across runs:
51
- # agent = needle.Needle(tools=tools, tool_index_path="tools.idx")
52
 
53
- response = agent.complete("set a 10 minute timer for the pasta")
54
 
 
 
 
55
  if response["type"] == "call":
56
- tool_result = run_tool(response["function_calls"][0])
57
- response = agent.complete(tool_result)
58
- response = agent.complete("and put on some jazz")
59
-
60
- agent.reset()
61
  ```
62
 
63
- Every turn returns one JSON object:
64
 
65
  ```json
66
  {
@@ -113,7 +190,6 @@ Download the folder for your platform from the release:
113
  | Windows ARM | `windows-arm64` | `needle.exe` | `libneedle.a` |
114
  | Android | `android-arm64` / `android-armv7` / `android-riscv64` | `needle` | `libneedle.a` |
115
  | iOS / watchOS / tvOS | `ios-arm64` / `watchos-arm64` / `tvos-arm64` | - | `libneedle.a` |
116
- | Cortex-M (bare-metal/RTOS) | `cortex-m4` / `cortex-m7` / `cortex-m55` | - | `libneedle.a` |
117
  | Browser / Node (WebAssembly) | `wasm` | - | `needle.js` + `needle.wasm` |
118
 
119
  To run it, use the command-line binary. On macOS, Linux, or Android:
@@ -172,21 +248,15 @@ To run it, use the command-line binary. On macOS, Linux, or Android:
172
 
173
  ## Tool retrieval
174
 
175
- Ten or fewer declared tools render directly. Above that, retrieval engages: at init every tool schema is embedded once by a built-in contrastive head,
176
- each turn embeds the query, and only the ten highest-scoring tools enter the context, with the grammar rebuilt over just that subset. an unselected
177
- tool is unreachable, not merely unlikely. `--tool-index <path>` (CLI) or `tool_index_path` (Python) persists the embeddings on disk, keyed by a fingerprint
178
- over the schemas and the model; a matching fingerprint loads instantly, a changed schema re-embeds only what changed.
179
 
180
  ## Confidence
181
 
182
- The `confidence` field is the minimum of two signals: a calibrated post-hoc head that scores the full prompt plus the call the model just produced, and
183
- the decoding probability of the call tokens. A call is accepted only when both agree, so the failure mode is escalation, not wrong execution. The contract:
184
- pick a threshold for your product, act at or above it, re-ask or route to a bigger model below it. Off-topic requests return the empty call `[]`.
185
 
186
- ## Custom weights
187
 
188
- - `needle_load(cact, n)` borrows the caller's buffer: no copy is made, the pointer must stay valid and unmodified until the next `needle_load` call or process exit. This is the load path when weights are not embedded (WebAssembly, or bytes fetched over a network); to load a `.cact` from disk, read the file and pass the bytes.
189
- - Embedded builds keep the weights in the binary's read-only section; all weight bytes stay file-backed and evictable, nothing is copied to the heap.
190
 
191
  ## Extraction
192
 
@@ -219,4 +289,21 @@ Extraction is the same exchange as tool calling: declare the record schema as th
219
 
220
  ```json
221
  { "type": "call", "function_calls": [ { "name": "receipt", "arguments": { "merchant": "GreenMart", "total": 7.75 } } ] }
222
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  - webassembly
12
  ---
13
 
14
+ ![Needle 2](assets/banner.png)
15
 
16
  # Needle 2
17
 
18
+ Needle 2 is an open 45M-parameter model for tool calling, device use and structured extraction. The whole model is a single 14MB binary that runs a full session in 28MB of RAM. It is built on our Simple Attention Network findings, compressed to CQ2-bit with Cactus Quants, and baked into its own engine. On the benchmarks below, Needle 2 trades wins with other small models like FunctionGemma 270M, LFM2.5 230M and Apple FM, at 5x to 70x smaller, and 2 bits against their f16. Needle hits 500 tokens/sec decode speed on a Raspberry Pi 5, between 400-1,500 tokens/sec on VR devices like Meta Quest 3S and Apple Vision Pro, and ranges 300-700 on sub-$200 phones such as the Samsung A-Series. With a peak session RAM around 28MB, Needle reaches microcontrollers like the ESP32-P4; others have reported running it on an ESP32-S3 in about 11MB.
 
 
19
 
20
  - **Self-contained**: model baked into the binary, no runtime, no downloads, no network.
21
+ - **Runs everywhere**: ARM64, x86-64, ARMv7, RISC-V, and WebAssembly, on Apple, Windows, Linux, Android, Raspberry Pi.
22
  - **Simple contract**: tool calls come back as structured data, text in, JSON out; a byte-level grammar compiled from your schemas constrains every token.
23
  - **Confidence-gated**: every response carries a calibrated confidence score from a learned head; set a threshold, act above it, escalate below it.
24
+ - **Tool retrieval**: declare a large catalogue and a built-in retrieval head renders only the top five tools per turn, with the grammar constrained to that subset.
25
+ - **Bounded memory**: a 256-token sliding window with the tools pinned as KV sinks, so total memory stays near 28MB no matter how long the conversation runs.
26
 
27
+ Source, engine, and training code: [github.com/cactus-compute/needle](https://github.com/cactus-compute/needle).
28
+
29
+ ![Size-quality frontier: mobile-class and below](assets/frontier.png)
30
+
31
+ ## Simple Attention Network
32
+
33
+ Needle 2 is a Simple Attention Network, our dense small-model recipe: a Hadamard MLP in place of the FFN, GQA attention, engram key-value memory, and multi-lane hyper-connections. See the paper for the design and ablations: [arXiv:2607.18363](https://arxiv.org/abs/2607.18363).
34
+
35
+ ![Simple Attention Network architecture](assets/architecture.png)
36
+
37
+ Each block carries its update rule. Here x̂ is the RMS-normalised flattening of the four residual streams, H the orthonormal Walsh-Hadamard transform (a fixed matrix, applied in n log n time with no weights to read), (kₜ, vₜ) rows gathered from hashed n-gram tables, and P the doubly-stochastic normalisation of the routing logits A, computed by Sinkhorn iteration; a, b, g and all σ-gates are learned and input-dependent. Both attention and MLP residuals are sandwich-normed and gated, the engram sites fire at two layers, and decoding is constrained by a byte-level grammar compiled from the declared schemas.
38
 
39
  ## Quickstart with Python
40
 
 
42
  pip install cactus-needle
43
  ```
44
 
45
+ Needle reads your tool descriptions to decide what to call and how to fill arguments, so describing them well is the whole game. You can do it three ways, from least to most control.
46
+
47
+ **Simple**: decorate a function. The signature gives the argument types, the docstring is the tool description, and `run()` completes the loop: model picks the call, Needle executes your function, feeds the result back, and returns the model's final answer.
48
 
49
  ```python
50
  import needle
51
 
52
+ @needle.tool
53
+ def get_weather(city: str):
54
+ "Get the current weather for a city."
55
+ return {"city": city, "temp_c": 27, "sky": "clear"}
56
+
57
+ agent = needle.Needle(tools=[get_weather])
58
+ print(agent.run("what's it like in Lagos right now?")["reasoning"])
59
+ ```
60
+
61
+ **Medium**: describe each argument and offer choices. Needle reads a Google-style `Args:` block for per-parameter descriptions; a default makes an argument optional; a `Literal` becomes a fixed set the model must choose from (it cannot emit anything else).
62
+
63
+ ```python
64
+ from typing import Literal
65
+
66
+ @needle.tool
67
+ def set_thermostat(temperature: int, mode: Literal["heat", "cool", "auto"] = "auto"):
68
+ """Set the thermostat.
69
+
70
+ Args:
71
+ temperature: target temperature in Celsius
72
+ mode: heating strategy to use
73
+ """
74
+ return {"temperature": temperature, "mode": mode}
75
+
76
+ agent = needle.Needle(tools=[set_thermostat])
77
+ agent.run("make it 21 and cool the room")
78
+ ```
79
+
80
+ **Advanced**: constrain the values with `needle.Field`, attached inline via `Annotated`. Ranges, patterns, lengths, and item counts are compiled into the decode grammar, so the model can only ever emit values that satisfy them.
81
+
82
+ ```python
83
+ from typing import Annotated
84
+
85
+ @needle.tool
86
+ def send_money(
87
+ amount: Annotated[float, needle.Field(gt=0, le=10000, description="USD, up to 10,000")],
88
+ to: Annotated[str, needle.Field(pattern=r"^@[a-z0-9_]+$", description="recipient handle")],
89
+ memo: Annotated[str, needle.Field(max_length=80)] = "",
90
+ ):
91
+ "Send money to a handle."
92
+ return {"sent": amount, "to": to}
93
+ ```
94
+
95
+ `Field` supports `description`, `enum`, `const`, `ge`/`le`/`gt`/`lt`, `multiple_of`, `min_length`/`max_length`, `pattern`, `format`, `min_items`/`max_items`, and `unique_items`.
96
 
97
+ **Extraction**: to pull structured data out of text, declare the shape and call `extract()`. Pass a Pydantic model and you get a typed object back.
98
+
99
+ ```python
100
+ from pydantic import BaseModel
101
+
102
+ class Invoice(BaseModel):
103
+ vendor: str
104
+ total: float
105
+ due_date: str
106
+
107
+ invoice = needle.extract("Invoice from Acme Corp, $1,200.00, due 2026-09-01", Invoice)
108
+ print(invoice.vendor, invoice.total) # -> Acme Corp 1200.0
109
+ ```
110
+
111
+ **By hand** - the decorator just builds a JSON schema; you can pass that schema directly, which is exactly what Needle consumes. This is how you set descriptions and constraints without the decorator (and `tools.json` for the CLI is the same shape):
112
+
113
+ ```python
114
+ tools = [{
115
+ "name": "set_lights",
116
+ "description": "Turn a room's lights on or off and set brightness",
117
+ "parameters": {
118
+ "type": "object",
119
+ "properties": {
120
+ "room": {"type": "string", "description": "which room to control"},
121
+ "on": {"type": "boolean"},
122
+ "brightness": {"type": "integer", "minimum": 0, "maximum": 100},
123
+ },
124
+ "required": ["room", "on"],
125
+ },
126
+ }]
127
  agent = needle.Needle(tools=tools)
128
+ ```
 
129
 
130
+ Prefer to drive the loop yourself instead of `run()`? `complete()` returns the raw call and you execute it:
131
 
132
+ ```python
133
+ import json
134
+ response = agent.complete("dim the living room to 30")
135
  if response["type"] == "call":
136
+ result = set_lights(**response["function_calls"][0]["arguments"])
137
+ response = agent.complete(json.dumps(result)) # feed the result back
 
 
 
138
  ```
139
 
140
+ With a large catalogue, persist tool embeddings across runs with `needle.Needle(tools=..., tool_index_path="tools.idx")`. Every turn returns one JSON object:
141
 
142
  ```json
143
  {
 
190
  | Windows ARM | `windows-arm64` | `needle.exe` | `libneedle.a` |
191
  | Android | `android-arm64` / `android-armv7` / `android-riscv64` | `needle` | `libneedle.a` |
192
  | iOS / watchOS / tvOS | `ios-arm64` / `watchos-arm64` / `tvos-arm64` | - | `libneedle.a` |
 
193
  | Browser / Node (WebAssembly) | `wasm` | - | `needle.js` + `needle.wasm` |
194
 
195
  To run it, use the command-line binary. On macOS, Linux, or Android:
 
248
 
249
  ## Tool retrieval
250
 
251
+ Five or fewer declared tools render directly. Above that, retrieval engages: at init every tool schema is embedded once by a built-in contrastive head, each turn embeds the query, and only the five highest-scoring tools enter the context, with the grammar rebuilt over just that subset. An unselected tool is unreachable, not merely unlikely. `--tool-index <path>` (CLI) or `tool_index_path` (Python) persists the embeddings on disk, keyed by a fingerprint over the schemas and the model; a matching fingerprint loads instantly, a changed schema re-embeds only what changed.
 
 
 
252
 
253
  ## Confidence
254
 
255
+ The `confidence` field is the minimum of two signals: a calibrated post-hoc head that scores the full prompt plus the call the model just produced, and the decoding probability of the call tokens. A call is accepted only when both agree, so the failure mode is escalation, not wrong execution. The contract: pick a threshold for your product, act at or above it, re-ask or route to a bigger model below it. Off-topic requests return the empty call `[]`.
 
 
256
 
257
+ ## Fine-tuning
258
 
259
+ Needle is open and trainable end to end. Fine-tune it on your own tools and domains, then export to a `.cact` and ship it like the base model. See the [needle repo](https://github.com/cactus-compute/needle) for training and export.
 
260
 
261
  ## Extraction
262
 
 
289
 
290
  ```json
291
  { "type": "call", "function_calls": [ { "name": "receipt", "arguments": { "merchant": "GreenMart", "total": 7.75 } } ] }
292
+ ```
293
+
294
+ ## Citation
295
+
296
+ Needle 2 is built by the Cactus Compute team. If you use it in your work, please cite:
297
+
298
+ ```bibtex
299
+ @misc{needle2_2026,
300
+ title = {Needle 2: A 45M-Parameter Foundation Tool-Calling Model for Tiny Devices},
301
+ author = {Ndubuaku, Henry and Mosoyan, Karen and Mroz, Jakub and Cylich, Noah and
302
+ Kumar, Satyajit and Sandhu, Parkirat and Shemet, Roman and Lee, Justin H.},
303
+ year = {2026},
304
+ organization = {Cactus Compute, Inc.},
305
+ howpublished = {\url{https://github.com/cactus-compute/needle}}
306
+ }
307
+ ```
308
+
309
+ Reach out on founders@cactuscompute.com for partnerships, collaborations, synergies and deploying Needle2 in your product.