Artificial IntelligenceTechnology

Meet Needle 2: An Open 45M-Parameter Tool-Calling Model That Ships as a 14MB Binary and Runs a Full Session in 28MB of RAM

Cactus Compute has released Needle 2, an open 45M-parameter model for tool calling, device use, and structured extraction. The entire model ships as a single 14MB binary that runs a full session in about 28MB of RAM. Weights are trained and deployed at CQ2-bit using Cactus Quants, and the model is sealed inside the company’s own C++ engine, so there is no runtime to install and no download at inference time. Reported decode throughput is 500 tokens/sec on a Raspberry Pi 5, 400–1,500 tokens/sec on Meta Quest 3S and Apple Vision Pro, and 300–700 tokens/sec on sub-$200 phones. The design premise is narrow and stated plainly by the team: mapping a messy sentence onto a typed function signature needs no world knowledge and no open-ended prose. That framing is why 45M parameters are enough here, and why the model targets hardware with no GPU and no NPU.

Is it deployable?

Yes, Needle 2 ships as prebuilt binaries and a static library for macOS, Linux (x86-64, ARM64, ARMv7, RISC-V, MIPS32el), Windows, Android, iOS/watchOS/tvOS, and WebAssembly. Cactus says Pebble already runs Needle locally in the Index 01 app for offline voice actions.

  • Which companies: Any team shipping firmware or apps on constrained hardware. Seed-stage wearable and IoT startups, mid-market consumer-electronics OEMs, robotics teams, and large device makers needing an offline fallback. Cloud-first SaaS teams gain less.
  • Industries: smart home, wearables, low-end mobile, automotive in-cabin control, service robotics, retail kiosks and POS, routers and IP cameras, and regulated settings where audio cannot leave the device.
  • Applications: voice-to-action on screenless devices, offline appliance control, receipt and invoice field extraction, enum tagging, and local routing that escalates to the cloud only on low confidence.

Architecture: Simple Attention Network

Needle 2 uses what Cactus team calls a Simple Attention Network. The recipe replaces the FFN with a Hadamard MLP, keeps GQA attention, adds engram key-value memory from hashed n-gram tables, and uses multi-lane hyper-connections. The network is 27 layers and 512 wide. The underlying study is on arXiv as A Controlled Study of Attention-Only Transformers.

Pretraining used a proprietary 115B-token corpus, with 38B tokens of post-training. The research team notes LFM2.5-230M was pretrained on 19 trillion tokens.

Needle 2 spends 70 MFLOPs per token, with 35M of 45M parameters matmul-active. LFM2.5 230M spends 460, FunctionGemma 270M spends 540, and Apple FM sits near 6,000.

Engine, grammar, retrieval, and confidence

Weights never decompress into RAM. The 2-bit codes expand inside vector registers and fuse into integer dot products, so the arithmetic path stays int8. One binary probes the CPU at startup and selects a kernel tier: SDOT, NEON, AVX2, RISC-V vectors, wasm SIMD, or scalar.

A byte-level grammar compiled from your JSON schemas constrains every emitted token. Because the matcher knows which tokens are legal before logits exist, the engine skips up to 98% of the vocabulary projection on structural tokens.

Attention uses a 256-token sliding window, and the system turn plus tool declarations are pinned as KV sinks. Memory stays near 28MB regardless of conversation length.

Declare five or fewer tools and they render directly. Above five, a contrastive retrieval head embeds each schema once, scores the query per turn, and admits only the top five. Unselected tools are unreachable, not merely unlikely.

Every response carries a confidence value, the minimum of a calibrated post-hoc head and the decoding probability of the call tokens. Off-topic requests return the empty call []. The contract is a threshold: act above it, re-ask or escalate below it.

Evaluation

Cactus team evaluates on five public function-calling benchmarks using ordered strict exact match, where names, call order, and every argument must match. Needle 2 runs end-to-end through the shipped engine at CQ2-bit with retrieval on; baselines run f16 under vLLM.

Benchmark Needle 2 (CQ2) LFM2.5 230M FunctionGemma 270M Apple FM
Mobile Actions (961) 63.7 69.1 64.0 57.6
DroidCall (200) 17.0 11.0 17.5
Seal-Tools in-domain (700) 32.6 26.9 16.3
Seal-Tools OOD (654) 28.7 17.0 15.6
BFCL v4 single-turn (3,641), overall 42.6 60.8 46.1 61.7

Needle 2 leads both Seal-Tools splits and posts 98.3 function-name accuracy on Mobile Actions. It trails on BFCL v4, which Cactus attributes to distribution: its corpus is consumer device actions, not general or enterprise APIs. Well-formed output rate across the 3,641 BFCL rows is 93.4. The team states two asymmetries upfront: f16 baselines favor them, and task specialization favors Needle.

Decode call</button>
<button class=”n2-btn” id=”n2-reset-g”>Reset</button>
</div>
<div class=”n2-term” id=”n2-term”><span class=”q”>&gt; dim the living room to 30</span></div>
<div class=”n2-legend”>
<span><i class=”n2-swatch” style=”background:var(–green)”></i>forced by grammar</span>
<span><i class=”n2-swatch” style=”background:var(–sand)”></i>value chosen by the model</span>
</div>
<div class=”n2-meters”>
<div class=”n2-meter”><span>Vocab rows scored · structural</span><b id=”n2-m1″>0%</b></div>
<div class=”n2-meter”><span>Structural bytes forced</span><b id=”n2-m2″>0</b></div>
</div>
</section>

<!– 03 RETRIEVAL –>
<section class=”n2-panel” role=”tabpanel” data-p=”2″>
<h3 class=”n2-h”>Above five tools, only five ever reach the model</h3>
<p class=”n2-p”>Every schema is embedded once by a built-in contrastive head. Each turn scores the query and
admits the top five; the grammar is then rebuilt over that subset. An unselected tool is unreachable.</p>
<div class=”n2-controls” role=”group” aria-label=”Choose a query”>
<button class=”n2-btn is-on” data-q=”0″>”dim the living room”</button>
<button class=”n2-btn” data-q=”1″>”text Ada I’m late”</button>
<button class=”n2-btn” data-q=”2″>”set it to 21 and play jazz”</button>
</div>
<p class=”n2-lanehead”>Declared catalogue — 12 tools</p>
<div class=”n2-chips” id=”n2-chips”></div>
<p class=”n2-note”>Illustrative simulation of the documented top-5 mechanism, not live model output.</p>
</section>

<!– 04 CONFIDENCE –>
<section class=”n2-panel” role=”tabpanel” data-p=”3″>
<h3 class=”n2-h”>The failure mode is escalation, not wrong execution</h3>
<p class=”n2-p”><code>confidence</code> is the minimum of a calibrated post-hoc head and the decoding
probability of the call tokens. You pick the threshold. Off-topic input returns the empty call
<code>[]</code> before any score applies.</p>
<input class=”n2-slider” id=”n2-slider” type=”range” min=”50″ max=”99″ value=”80″ aria-label=”Confidence threshold”>
<p class=”n2-thresh”>Threshold: <b id=”n2-tv”>0.80</b> — act at or above, re-ask or route to a bigger model below.</p>
<div class=”n2-rows” id=”n2-rows”></div>
<p class=”n2-note”>0.94 is the value shown in the Cactus README response object; the rest are illustrative.</p>
</section>

<!– 05 MEMORY –>
<section class=”n2-panel” role=”tabpanel” data-p=”4″>
<h3 class=”n2-h”>A ceiling, not a curve</h3>
<p class=”n2-p”>Attention runs a 256-token sliding window, and the system turn plus tool declarations are
pinned as KV sinks — so the one thing a tool-calling model must not forget cannot be evicted. Add turns
and watch what happens to each bar.</p>
<div class=”n2-controls”>
<button class=”n2-btn is-on” id=”n2-turn”>+ Add a turn</button>
<button class=”n2-btn” id=”n2-reset-m”>Reset conversation</button>
<span class=”n2-btn” style=”cursor:default” id=”n2-turns”>turns: 1</span>
</div>
<div class=”n2-memgrid”>
<div class=”n2-membar”>
<span class=”n2-bar-l” style=”color:var(–green)”>Needle 2</span>
<div class=”n2-memtrack”>
<div class=”n2-memfill pin” id=”n2-pin” style=”width:34%”></div>
<div class=”n2-memfill win” id=”n2-win” style=”left:34%;width:24%”></div>
</div>
<span class=”n2-bar-v” id=”n2-memv”>28.0 MB</span>
</div>
<div class=”n2-membar”>
<span class=”n2-bar-l”>Unbounded KV</span>
<div class=”n2-memtrack”><div class=”n2-memfill grow” id=”n2-grow” style=”width:22%”></div></div>
<span class=”n2-bar-v” id=”n2-growv”>rising</span>
</div>
</div>
<p class=”n2-note”>Striped block = pinned tools and system facts (KV sinks). Solid block = the 256-token window.</p>
<p class=”n2-overflow” id=”n2-of”></p>
</section>

<div class=”n2-foot”>
<span class=”n2-brand”>Explainer by <b>Marktechpost</b></span>
<span>
<a href=”https://github.com/cactus-compute/needle” target=”_blank” rel=”noopener”>GitHub</a> ·
<a href=”https://huggingface.co/Cactus-Compute/needle2″ target=”_blank” rel=”noopener”>Hugging Face</a> ·
<a href=”https://cactuscompute.com/needle” target=”_blank” rel=”noopener”>Cactus</a> ·
<a href=”https://arxiv.org/abs/2607.18363″ target=”_blank” rel=”noopener”>arXiv:2607.18363</a>
</span>
</div>
</div>

<script>
(function(){
var root=document.getElementById(‘n2-root’);
var reduce=window.matchMedia&&window.matchMedia(‘(prefers-reduced-motion:reduce)’).matches;

/* ———- resize ———- */
function post(){
try{ parent.postMessage({type:’n2-height’, height: root.offsetHeight + 40}, ‘*’); }catch(e){}
}
var ro=window.ResizeObserver?new ResizeObserver(post):null; if(ro) ro.observe(root);
window.addEventListener(‘load’,post); setTimeout(post,120); setTimeout(post,600);

/* ———- tabs ———- */
var tabs=[].slice.call(root.querySelectorAll(‘.n2-tab’));
var panels=[].slice.call(root.querySelectorAll(‘.n2-panel’));
tabs.forEach(function(t){
t.addEventListener(‘click’,function(){
tabs.forEach(function(x){x.setAttribute(‘aria-selected’,’false’)});
t.setAttribute(‘aria-selected’,’true’);
panels.forEach(function(p){p.classList.remove(‘is-on’)});
panels[+t.dataset.t].classList.add(‘is-on’);
if(t.dataset.t===’0′) drawBars(metric);
post();
});
});

/* ———- 01 frontier ———- */
var DATA={
params:{unit:’M params’, note:’Total parameters. Apple FM is approximately 3B. Source: Cactus compute-per-token table.’,
rows:[[‘Needle 2′,45,’45M’],[‘LFM2.5 230M’,230,’230M’],[‘FunctionGemma 270M’,270,’270M’],[‘Apple FM’,3000,’~3B’]]},
flops:{unit:’MFLOPs/token’, note:’2 FLOPs per multiply-accumulate over matmul-active parameters, embeddings tied, attention terms excluded. Needle keeps 8M parameters as gathered engram memory, costing no arithmetic.’,
rows:[[‘Needle 2′,70,’70’],[‘LFM2.5 230M’,460,’460′],[‘FunctionGemma 270M’,540,’540′],[‘Apple FM’,6000,’~6,000′]]},
acc:{unit:’%’, note:’Mobile Actions eval split, 961 rows, ordered strict exact match. Needle 2 at CQ2-bit with retrieval on; LFM2.5 and FunctionGemma at f16 under vLLM; Apple FM on-device.’,
rows:[[‘Needle 2′,63.7,’63.7’],[‘LFM2.5 230M’,69.1,’69.1′],[‘FunctionGemma 270M’,64.0,’64.0′],[‘Apple FM’,57.6,’57.6′]]}
};
var metric=’params’, barsEl=document.getElementById(‘n2-bars’), noteEl=document.getElementById(‘n2-barnote’);
function drawBars(m){
var d=DATA[m], max=Math.max.apply(null,d.rows.map(function(r){return r[1]}));
barsEl.innerHTML=d.rows.map(function(r){
return ‘<div class=”n2-bar’+(r[0]===’Needle 2′?’ is-hero’:”)+'”>’+
‘<span class=”n2-bar-l”>’+r[0]+'</span>’+
‘<div class=”n2-bar-t”><div class=”n2-bar-f”></div></div>’+
‘<span class=”n2-bar-v”>’+r[2]+'</span></div>’;
}).join(”);
noteEl.textContent=d.note;
var fills=barsEl.querySelectorAll(‘.n2-bar-f’);
d.rows.forEach(function(r,i){
var w=Math.max(2,(r[1]/max)*100);
if(reduce){fills[i].style.width=w+’%’;}
else setTimeout(function(){fills[i].style.width=w+’%’;},60*i+40);
});
post();
}
root.querySelectorAll(‘[data-m]’).forEach(function(b){
b.addEventListener(‘click’,function(){
root.querySelectorAll(‘[data-m]’).forEach(function(x){x.classList.remove(‘is-on’)});
b.classList.add(‘is-on’); metric=b.dataset.m; drawBars(metric);
});
});
drawBars(‘params’);

/* ———- 02 grammar ———- */
var CALL=[
[‘{“name”:”‘,1],[‘set_lights’,0],[‘”,”arguments”:{“room”:”‘,1],[‘living room’,0],
[‘”,”on”:’,1],[‘true’,0],[‘,”brightness”:’,1],[’30’,0],[‘}}’,1] ];
var term=document.getElementById(‘n2-term’), m1=document.getElementById(‘n2-m1’), m2=document.getElementById(‘n2-m2’);
var timer=null;
function resetG(){
if(timer){clearInterval(timer);timer=null;}
term.innerHTML='<span class=”q”>&gt; dim the living room to 30</span>’;
m1.textContent=’0%’; m2.textContent=’0′; post();
}
document.getElementById(‘n2-reset-g’).addEventListener(‘click’,resetG);
document.getElementById(‘n2-decode’).addEventListener(‘click’,function(){
if(timer){clearInterval(timer);timer=null;}
var flat=[]; CALL.forEach(function(seg){
for(var i=0;i<seg[0].length;i++) flat.push([seg[0][i],seg[1]]);
});
term.innerHTML='<span class=”q”>&gt; dim the living room to 30</span><br>’;
var out=document.createElement(‘span’); term.appendChild(out);
var caret=document.createElement(‘span’); caret.className=’n2-caret’; caret.textContent=’_’; term.appendChild(caret);
var i=0, forced=0;
function step(){
if(i>=flat.length){ if(timer){clearInterval(timer);timer=null;} caret.remove();
m1.textContent=’2%’; m2.textContent=forced; post(); return; }
var ch=flat[i][0], isF=flat[i][1];
var s=document.createElement(‘span’); s.className=isF?’n2-forced’:’n2-free’;
s.textContent=ch; out.appendChild(s);
if(isF) forced++;
var pct=Math.round(100-(forced/(i+1))*98);
m1.textContent=pct+’%’; m2.textContent=forced;
i++;
}
if(reduce){ while(i<flat.length) step(); }
else timer=setInterval(step,26);
});

/* ———- 03 retrieval ———- */
var TOOLS=[‘set_lights’,’play_music’,’send_message’,’set_thermostat’,’get_weather’,’set_timer’,
‘call_contact’,’navigate_to’,’take_photo’,’lock_door’,’add_calendar_event’,’control_tv’];
var PICKS=[[0,3,11,1,5],[2,6,10,0,4],[3,1,0,5,4]];
var chipsEl=document.getElementById(‘n2-chips’);
chipsEl.innerHTML=TOOLS.map(function(t,i){return ‘<span class=”n2-chip” data-i=”‘+i+'”>’+t+'</span>’}).join(”);
function retrieve(qi){
var sel=PICKS[qi];
[].slice.call(chipsEl.children).forEach(function(c,i){
c.classList.remove(‘is-sel’,’is-out’);
var d=sel.indexOf(i)>-1?0:180;
setTimeout(function(){ c.classList.add(sel.indexOf(i)>-1?’is-sel’:’is-out’); }, reduce?0:d);
});
}
root.querySelectorAll(‘[data-q]’).forEach(function(b){
b.addEventListener(‘click’,function(){
root.querySelectorAll(‘[data-q]’).forEach(function(x){x.classList.remove(‘is-on’)});
b.classList.add(‘is-on’); retrieve(+b.dataset.q);
});
});
retrieve(0);

/* ———- 04 confidence ———- */
var TURNS=[
[‘dim the living room to 30’,0.94,false],
[‘make it warmer in here’,0.81,false],
[‘put the thing on the other thing’,0.58,false],
[‘text Ada that I am running late’,0.88,false],
[‘what is the capital of Peru?’,null,true] ];
var rowsEl=document.getElementById(‘n2-rows’), slider=document.getElementById(‘n2-slider’), tv=document.getElementById(‘n2-tv’);
function renderRows(){
var th=(+slider.value)/100; tv.textContent=th.toFixed(2);
rowsEl.innerHTML=TURNS.map(function(t){
if(t[2]) return ‘<div class=”n2-row ref”><span class=”n2-row-q”>’+t[0]+
‘</span><span class=”n2-row-c”>[ ]</span><span class=”n2-verdict v-ref”>empty call</span></div>’;
var ok=t[1]>=th;
return ‘<div class=”n2-row ‘+(ok?’act’:’esc’)+'”><span class=”n2-row-q”>’+t[0]+
‘</span><span class=”n2-row-c”>’+t[1].toFixed(2)+'</span><span class=”n2-verdict ‘+
(ok?’v-act”>execute’:’v-esc”>escalate’)+'</span></div>’;
}).join(”);
post();
}
slider.addEventListener(‘input’,renderRows); renderRows();

/* ———- 05 memory ———- */
var turns=1, gv=document.getElementById(‘n2-gv’), gf=document.getElementById(‘n2-gf’);
var win=document.getElementById(‘n2-win’), grow=document.getElementById(‘n2-grow’);
var memv=document.getElementById(‘n2-memv’), growv=document.getElementById(‘n2-growv’);
var ofEl=document.getElementById(‘n2-of’), turnsEl=document.getElementById(‘n2-turns’);
function renderMem(){
turnsEl.textContent=’turns: ‘+turns;
var w=Math.min(24, 12+turns*2.5);
win.style.width=w+’%’;
memv.textContent=’28.0 MB’;
gf.style.width=’78%’; gv.innerHTML='<b>28.0 MB</b> / hard ceiling 28 MB’;
var g=Math.min(100, 18+turns*7);
grow.style.width=g+’%’;
growv.textContent=(Math.round((18+turns*7)*0.62*10)/10)+’ MB’;
ofEl.textContent = g>=78 ? ‘Unbounded KV has crossed the 28MB ceiling. Needle 2 has not moved.’ : ”;
post();
}
document.getElementById(‘n2-turn’).addEventListener(‘click’,function(){turns++;renderMem();});
document.getElementById(‘n2-reset-m’).addEventListener(‘click’,function(){turns=1;renderMem();});
renderMem();
})();
</script>
</body>
</html>
“>

Key Takeaways

  • Needle 2 is a 45M-parameter tool-calling model in a 14MB binary, running a session in ~28MB RAM.
  • CQ2-bit is trained in from pretraining, not applied post-hoc, so the deployed model is the trained model.
  • It leads Seal-Tools in-domain (32.6) and out-of-domain (28.7), and trails on BFCL v4 overall (42.6).
  • A 256-token sliding window with pinned tool sinks makes RAM a fixed ceiling, not a growing curve.
  • Confidence scores and empty-call refusals turn edge-to-cloud escalation into an explicit product decision.


Check out the GitHub Repo, Hugging Face Model Card, Cactus Needle page and Paper. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.

Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us

The post Meet Needle 2: An Open 45M-Parameter Tool-Calling Model That Ships as a 14MB Binary and Runs a Full Session in 28MB of RAM appeared first on MarkTechPost.

Show More

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button

Adblock Detected

Please consider supporting us by disabling your ad blocker