Cactus Needle 3: A Sliceable 8-29MB Automation Foundation Model That Matches DeepSeek v4 Flash
핵심 요약
8-29MB의 초경량 크기로 DeepSeek v4 Flash와 대등한 성능을 내는 온디바이스 자동화 모델 Needle 3가 공개되었습니다.
초경량 모델 — 8MB에서 29MB 사이의 크기로 온디바이스 환경에 최적화됨
성능 우위 — 121M 파라미터로 훨씬 큰 모델들을 능가하는 도구 호출 및 구조화된 데이터 추출 성능 제공
지능형 사다리 — 2~20 레이어까지 가변적인 깊이로 다양한 기기 사양에 맞춰 배포 가능
로컬 추론 — 네트워크 연결 없이 CPU만으로 초당 최대 4,000 토큰의 빠른 디코딩 속도 지원
다들 안녕, Cactus Compute의 헨리야. 이번에 새로 만든 모델 좀 공유하고 다들 어떻게 생각하는지 피드백 좀 받고 싶어서 글 써. :)
Needle 3는 자동화에 특화된 작은 파운데이션 모델이야. 앱에서 쓸 함수들을 넘겨주면, 요청 내용을 읽어서 인자값 꽉 채운 함수 호출을 뱉어내거나, 스키마를 넘겨주면 그에 맞는 타입 레코드를 반환해. 네트워크 연결 없이 기기 내부에서 바로 돌아가. Hugging Face, GitHub에 올려놨고, PyPI에는 cactus-needle로 등록했어. 더 읽기 전에 직접 써보고 싶으면 cactuscompute.com/needle에서 브라우저로 바로 테스트해 볼 수 있어.
1) 범용성 대신 자동화 작업의 성능을 선택함
우리가 초반부터 확실히 정한 건 Needle은 채팅용이 아니라는 거야. 모든 턴은 함수 호출로 이루어지고, 선언된 도구로 처리할 수 없는 요청이 들어오면 헛소리하는 대신 그냥 빈 리스트를 뱉어. 이게 제약처럼 들릴 수도 있고 실제로도 그렇지만, 덕분에 1억 2,100만 파라미터짜리 모델을 3,600억 토큰의 구조화된 데이터로 학습시키면서, 그 용량을 전부 도구 호출, 구조화된 데이터 추출, 텍스트 임베딩이라는 세 가지 작업에만 몰빵할 수 있었어.
아키텍처도 같은 맥락이야. 이건 'Simple Attention Network'인데, 밀집 피드포워드(dense feed-forward) 층을 다 날려버리고 그 자리에 레이어당 470만 개 대신 2만 5,600개 파라미터를 가진 Monarch Hadamard MLP를 넣었어. 보통 피드포워드 층이 담당하던 지식은 'engram'이라는 곳에 담았는데, 이건 해시된 n-gram 테이블이라 gather 연산으로 읽어오기만 하면 돼서 연산 비용이 아예 안 들어. 전체 1억 2,100만 파라미터 중 7,080만 개가 거기 들어있어서, 실제 모델은 5,000만 파라미터짜리 모델 수준의 연산만 해. 같은 형태의 트랜스포머가 토큰당 296 MFLOPs를 쓸 때 이건 100 MFLOPs밖에 안 써.
Mobile Actions(전화기 명령어 961개, 정확한 호출 여부로 채점)에서 20레이어 모델은 컨피던스 게이트를 켠 상태로 2비트 바이너리를 통해 86.0점을 찍었어. LFM2.5 1.2B가 82.4점, Qwen3.5 0.8B가 76.0점, FunctionGemma 270M이 65.1점, 애플의 온디바이스 파운데이션 모델이 57.6점인데, 얘네는 다 f16 기준이야. DeepSeek V4 Flash는 API로 88.4점이 나오는데, 그게 차트에 표시된 선이야.
우리가 제일 만족하는 건 점수보다도 호출 방식이야. 모든 인자는 요청문의 일부분(span)에서 따와. 모델이 먼저 짧게 추론 과정을 거치고('living room' -> room; '30' -> brightness), 스키마에서 컴파일된 바이트 단위 문법에 맞춰 호출을 내보내. 그래서 JSON 파싱 오류가 절대 안 나고 enum 값도 정해진 범위를 벗어날 일이 없어. 근거가 없는 선택 필드는 아예 생략하고, 근거 없는 필수 필드가 있으면 호출 자체를 안 해. 요청 내용이 부정하거나 배제하는 호출은 엔진이 알아서 걸러내. 두 가지를 요청하면 순서대로 두 번 호출해 줄 거야.
6개 전체 스위트에 대한 전체 표야(도구 호출은 정확 일치, 추출은 필드 F1 기준, Needle은 배포된 바이너리, 베이스라인은 vLLM 기반 f16):
Model
Params
Mobile Actions
DroidCall
BFCL v4
DSTC8 F1
SNIPS gold F1
SNIPS 7-way F1
DeepSeek V4 Flash (cloud)
-
88.4
60.5
77.2
80.0
69.4
66.7
Needle3-20L-121M
121M
86.0
47.0
50.2
40.7
30.2
24.7
LFM2.5 1.2B
1.2B
82.4
35.5
62.0
48.0
43.0
38.0
Needle3-16L-98M
98M
80.7
40.0
41.3
28.5
23.5
19.2
Qwen3.5 0.8B
800M
76.0
28.0
56.8
49.0
35.0
34.0
LFM2.5 350M
350M
72.8
32.5
59.1
20.0
34.0
29.0
LFM2.5 230M
230M
69.3
11.5
46.3
53.0
27.0
22.0
FunctionGemma 270M
270M
65.1
16.5
46.6
27.0
29.0
14.0
Needle 2
45M
63.5
17.0
-
-
-
-
Apple FM
3.0B
57.6
-
-
-
-
-
Needle3-8L-52M
52M
36.8
36.5
28.2
15.3
16.6
10.1
Needle3-4L-29M
29M
11.7
21.0
19.5
6.9
7.7
4.3
어디가 약한지도 보일 거야. BFCL이나 추출 스위트에서는 덩치 큰 베이스라인들이 앞서나가고, 일반적인 작업에서는 작은 서브네트워크들이 금방 힘이 빠져(왜 이게 괜찮은지는 4장에서 더 자세히 다룰게).
3) 구조화된 JSON 추출에서 2~3배 큰 모델들과 대등함
추출은 별도의 모드가 아니야. 레코드를 유일한 도구로 선언하고 질문이 포함된 문장을 넘기면 돼. 도구가 하나만 선언되어 있으니 문법상 해당 이름의 호출이 딱 하나만 허용되거든. 그래서 형태가 요청되는 게 아니라 보장되는 거야. 값들도 인자값과 똑같은 방식으로 근거를 찾아. 필드는 문장의 일부분에서만 채워지고, 근거가 없는 선택 필드는 None으로 돌아오며, 텍스트 어디에도 없는 연도가 포함된 날짜는 지어내는 대신 오류로 처리해.
별도의 학습 없이도 분류 작업에 바로 써먹을 수 있는데, enum은 그냥 제약이 걸린 값일 뿐이라서 그래. 레코드에 sentiment: Literal["positive", "neutral", "negative"]라고 선언만 하면, 출력값이 그 집합을 벗어날 수 없는 분류기가 뚝딱 만들어지는 식이지. 워치로 알림을 읽어서 가맹점, 금액, 날짜로 나누고, 그다음 답장으로, 마지막엔 감정 플래그로, 이렇게 레코드 하나씩 처리하는 거야. DSTC8이랑 SNIPS 스위트 두 곳에서 121M 모델은 230M이랑 350M 베이스라인 사이쯤에 위치하는데, 이게 제목에서 말한 2~3배 성능 차이야.
4) 지능의 사다리: 2층부터 20층까지, 층마다 모델 하나씩
이 부분이 제일 의견을 듣고 싶은 곳이야. Needle 3은 가중치 세트 하나인데, 2층부터 20층까지 깊이마다 전부 배포 가능한 모델이 돼. 0번 블록이랑 19번 블록은 항상 고정이고 나머지는 이분법으로 추가되는 방식이라, 하위 네트워크가 상위 네트워크 안에 쏙 들어가는 구조지. 학습할 때는 매 단계마다 경로 하나를 샘플링하는데, 대부분은 전체 모델을 쓰고 가끔 랜덤한 깊이를 써서 작은 경로를 전체 모델에서 증류해. 전체 깊이 모델은 같은 크기의 일반적인 모델보다 살짝 더 성능이 좋고, 그 아래의 모든 깊이도 그냥 잘라낸 게 아니라 제대로 학습된 상태야.
왜 이렇게 만들었냐면, 워치, 라즈베리 파이, 스마트폰이 다 똑같은 모델을 쓸 순 없잖아? 배포할 때 상황에 맞는 크기를 골라야 하니까. needle build --layers 8이라고 치면 8층짜리 파일이 나오고, 엔진 하나로 다 돌아가는 거지. 일반적인 벤치마크에서는 깊이가 얕을수록 정확도가 떨어지는데(위 표의 하단부), 특정 제품의 툴에 맞춰 파인튜닝하면 다시 복구돼. DroidCall에서는 모든 서브 네트워크가 18~36점씩 올랐고, 4층(29M 파라미터)부터는 파인튜닝된 서브 네트워크가 DeepSeek V4 Flash를 발라버려.
파인튜닝은 고정된 베이스 모델에 LoRA를 얹고 내보낼 때 합치는 방식인데, 파이썬 패키지로 로컬에서 4비트로 돌리면 돼(needle finetune data.jsonl 치고 needle build 하면 끝). 수학적인 원리는 Intelligence Ladders에, 워크플로우는 Fine-tuning Needle에 다 적어놨어.
5) 로컬에서 최대 4k 토큰/초 디코딩 속도
엔진 크기는 1MB도 안 되고, 순수 CPU로 돌아가. GPU나 NPU도 필요 없어. 가중치는 엔진이 메모리에 매핑한 단일 파일에서 바로 읽어오는데, 구조 정보를 담은 196바이트 헤더, 이름 없는 텐서 디렉토리, 그리고 포워드 패스 순서대로 정렬된 양자화 블롭으로 구성돼 있어. 라즈베리 파이 5 기준으로 사다리 하단에서는 디코딩이 초당 4k 토큰까지 나오고, 상단에서는 400 토큰 정도 나와. 프리필은 10k에서 1k까지 나오고. 응답마다 prefill_tps, decode_tps, peak_ram_mb가 찍히니까 우리 말만 믿지 말고 직접 기기에서 측정해 봐.
모든 응답에는 보정된 헤드에서 나온 confidence 점수도 포함돼 있어. 이건 작업이 끝난 후의 사후 판단이랑 토큰의 디코딩 확률 중 낮은 값을 가져온 거야. 엔진은 0.1 미만이면 아예 뱉어내질 않아. 그 이상이면 네가 판단하면 돼. 점수가 높으면 바로 실행하고, 애매하면 호출 내용을 보여주고 물어보고, []가 나오면 거절로 간주해. 어떻게 활용하는지는 Leveraging Needle's confidence를 참고해.
6) CQ2비트에서 25~121M 파라미터 배포 가능 (8~29MB 바이너리)
가중치는 Cactus Quants로 양자화했어. 128개 가중치를 한 그룹으로 묶어서 Walsh-Hadamard 행렬로 회전시키면 모든 그룹이 가우시안 분포처럼 보이거든. 이걸 fp16 노름(norm)이랑 단위 구 위의 방향으로 쪼개고, 방향 좌표를 4개짜리 Lloyd-Max 코드북에 맞추는 거야. 가중치당 2.125비트인데, 커널이 이걸 다시 풀지 않아. 대신 활성화 함수를 회전시키고 int8로 양자화한 다음, 테이블 룩업이랑 패킹된 인덱스에 대해 sdot을 때리는 거지. 임베딩이랑 컨피던스 헤드는 4비트를 유지하고, 노름이랑 게이트는 fp16 그대로 둬. 바이트 레이아웃이랑 그걸 읽는 20줄짜리 파서는 The .cact format에 있어.
7) 모바일, 웨어러블, 스마트 홈, 소형 로봇 및 마이크로컨트롤러용
모든 타겟은 미리 빌드된 엔진 폴더를 제공함: macOS, x86-64/ARM64/ARMv7/RISC-V/MIPS32(Ingenic 카메라 SoC) 기반 리눅스, Windows x64 및 ARM, Android, iOS, watchOS, tvOS, WebAssembly 기반 브라우저, 그리고 WIT world가 포함된 WASI 컴포넌트까지 다 지원함. pip install cactus-needle을 치면 데스크톱이랑 서버 플랫폼용 휠 태그 엔진이 설치되고, needle build --platform linux-arm64 --layers 8 --out ./pi를 실행하면 엔진이랑 가중치 파일이 폴더에 쏙 들어가서 그대로 복사해서 쓰면 됨. 추론 과정에서 네트워크를 아예 안 타니까, 인터넷 끊긴 에어갭 장비에서도 파일만 넣어주면 바로 돌아감. 지원하는 장치 목록이랑 런타임 환경은 What devices are supported에서 확인 가능.
import needle
@needle.tool
def get_weather(city: str):
"Get the current weather for a city."
return {"city": city, "temp_c": 27, "sky": "clear"}
agent = needle.Needle(tools=[get_weather])
print(agent.run("what's it like in Lagos right now?")["results"])
# [{'city': 'Lagos', 'temp_c': 27, 'sky': 'clear'}]
피드백 받고 싶은 부분
접지(grounding) 규칙이 방해되는 지점. 우리 엔진은 없는 숫자를 지어내거나, 요청에 없는 함수 호출을 쳐내거나, 요청에 명시되지 않은 필수 enum 값을 안 내놓게 설계됐음. 우리 테스트 셋에서는 잘 조정했는데, 실제 툴에 적용할 때 어떤 식으로 빡치게 하는지 알려주면 좋겠음.
레이어 단계(The ladder). 깊이(depth) 조절이 너희한테 적절한지, 아니면 너비(width) 조절이 더 필요한지, 그리고 2레이어랑 4레이어 모델을 어떤 장치에서 돌리고 싶은지 궁금함.
우리가 아직 못 본 추출 케이스들. 중첩 레코드, 배열, 다국어 텍스트(지금은 영어 우선이라 비영어권 텍스트는 토큰이 1.7배 정도 더 쪼개짐) 같은 거.
#!/usr/bin/env swift
//
// apple_model_info.swift
//
// 이 맥에 설치된 애플 파운데이션 모델을 출력합니다 — 애플 인텔리전스와 파운데이션 모델 프레임워크가 로컬에서 실행하는 온디바이스 모델이죠. 서버로 전송되는 건 없습니다: Private Cloud Compute 모델은 앱이 이름으로 요청해야 하는 별도의 객체입니다.
//
// 실행 방법:
// swift apple_model_info.swift
// 또는
// ./apple_model_info.swift
//
// macOS 27과 Swift 필요...