-1 / 0 / +1 세 값(또는 -1 / +1 두 값)으로 갈아끼워 용량을 9.4배 줄이는 압축 모델 패밀리이고, 이 저장소는 그 모델을 내 노트북에서 한 줄로 돌려보게 해주는 런처 겸 데모 키트다.
비슷한 시도인 BitNet 계열은 "1비트로 처음부터 다시 학습"해야 해서 2B 언저리에 갇혀 있었는데, Bonsai는 반대로 기성 프리트레인 모델을 사후 변환해 27B까지 밀어붙였다는 게 화제의 핵심이다.
그리고 이 문서에서 진짜 배울 만한 건 모델 자체보다도, "4비트 양자화"라는 라벨이 실제로는 5.2비트였다는 백서의 폭로 — 즉 양자화 스펙을 비판적으로 읽는 법이다.
덤으로 llama.cpp와 MLX 두 런타임을 셸 스크립트만으로 자동 감지·자동 설치하는 설계, SSRF 방어가 들어간 에이전트 도구 코드까지 교보재가 널려 있다.
(저장소: PrismML-Eng/Bonsai-demo · Shell 65% + PowerShell 32% + Python 3% · Apache-2.0 · 별 1,768 · 포크 170 · 최신 푸시 2026-07-18)
PrismML이라는 회사(웹사이트 prismml.com)가 만든 Bonsai 모델 패밀리는, Qwen3.6-27B 같은 이미 학습이 끝난 모델을 가져와 수학적 변환을 거쳐 모든 가중치를 3진값 또는 2진값으로 바꾼 것이다. 27B(파라미터 274억)짜리 모델이 FP16으로는 54GB인데, 이 변환을 거치면 ternary 5.9GB / binary 3.9GB가 된다.
이 저장소가 하는 일은 딱 세 가지다. ① 내 하드웨어(CUDA? Metal? ROCm? Vulkan?)를 자동 감지해서 맞는 llama.cpp 바이너리를 받아오고, ② HuggingFace에서 필요한 quant 파일만 골라 내려받고, ③ CLI·서버·Open WebUI 중 원하는 형태로 띄운다. 명령어는 ./setup.sh 한 줄이다.
분재(bonsai, 盆栽)라는 이름이 곧 설명이다. 분재는 거대한 나무를 다른 종으로 바꾸는 게 아니라, 같은 나무를 화분에 들어가도록 접고 다듬는 기술이다. 잎의 모양도, 수형도 원래 나무 그대로인데 크기만 줄어든다.
BitNet 방식이 "작은 화분에 맞는 새 품종을 씨앗부터 개량하는 것"이라면, Bonsai는 이미 다 자란 27B 나무를 가져와 뿌리를 다듬어 화분에 넣는 것이다. 그래서 "95%의 지능을 유지한다"는 표현이 나온다 — 새로 키운 게 아니라 원래 나무를 접은 것이니까.
{-1, 0, +1} 세 개, {-1, +1} 두 개뿐인 극단적 양자화. 곱셈이 사실상 "더하기/빼기/무시"로 바뀌어 연산도 빨라진다. 대신 그 자체로는 스케일(크기)을 표현 못 해서, 가중치 128개를 한 그룹으로 묶어 그룹당 FP16 스케일 1개를 따로 저장한다(g128 포맷).log2(3) + 16/128 ≈ 1.71 bpw — 3진값 자체가 약 1.585비트, 여기에 128개당 FP16 스케일 하나(16/128 = 0.125비트)를 더한 값이다.로컬 LLM을 써본 사람이라면 Q4_K_M, IQ2_XXS 같은 이름을 봤을 것이다. 이름만 보면 각각 4비트, 2비트 같다. Bonsai 백서가 실제로 재본 값은 이렇다.
| 흔한 이름 | 사람들이 생각하는 비트 | 실측 bpw | 왜 이런 차이가? |
|---|---|---|---|
| Q4_K_XL | 4비트 | 5.2 bpw | 임베딩·어텐션·출력 레이어는 4~8비트로 남겨두는 혼합정밀 방식 |
| IQ2_XXS | 2비트 | 2.8 bpw | 같은 이유. 민감한 텐서는 못 줄임 |
| Ternary Bonsai | — | 1.71 bpw | 임베딩·프로젝션·LM head까지 전 구간 압축 |
| 1-bit Bonsai | — | 1.125 bpw | 동일. 비전 타워만 예외적으로 HQQ 4비트 |
"이 노트북 무게 1.2kg입니다"라고 광고해놓고 충전기와 어댑터는 빼고 잰 것과 같다. 가방에 넣고 다닐 실제 무게는 1.9kg인데 말이다. 기존 양자화 포맷의 "4비트"는 일부 레이어만 4비트라는 뜻이었고, 무거운 부품들은 그대로 남아 있었다.
Bonsai의 주장은 "우리는 충전기까지 포함해서 잰 무게가 이겁니다"에 가깝다. 그리고 그 전제에서 비교하면 격차가 훨씬 커 보인다.
백서 Table 10 기준(EvalScope + vLLM, H100, 15개 벤치마크 평균, thinking 모드).
| 모델 | 실효 bpw | 용량 | 15벤치 평균 | FP16 대비 |
|---|---|---|---|---|
| Qwen3.6-27B FP16 | 16.0 | 54 GB | 85.07 | 100% |
| Q4_K_XL ("4비트") | 5.2 | 17.6 GB | 84.99 | 99.9% |
| IQ2_XXS ("2비트") | 2.8 | 9.4 GB | 72.73 | 85.5% |
| Ternary Bonsai 27B | 1.71 | 5.9 GB | 80.49 | 94.6% |
| 1-bit Bonsai 27B | 1.125 | 3.9 GB | 76.11 | 89.5% |
읽는 법: IQ2_XXS는 9.4GB를 쓰고 72.73점, Ternary Bonsai는 5.9GB를 쓰고 80.49점이다. 용량은 37% 적은데 점수는 7.8점 높다. 이게 이 프로젝트가 화제가 된 실질적 근거다.
단 정직하게 읽자면 Q4_K_XL(17.6GB, 99.9%)이 여전히 품질 챔피언이다. Bonsai가 이기는 영역은 "17GB는 도저히 못 올리는 기기" — 즉 폰, 저사양 노트북, 임베디드다. 백서 스스로도 무손실이 아니다라고 명시한다.
1비트 LLM 얘기가 나오면 늘 등장하는 게 마이크로소프트의 BitNet(b1.58)이다. 백서는 이 차이를 정면으로 다룬다.
| 항목 | BitNet 계열 | Bonsai |
|---|---|---|
| 접근 방식 | 처음부터 1비트로 사전학습(pretrain) | 학습 끝난 모델을 사후 변환 |
| 비용 | 모델을 통째로 새로 학습 (막대함) | 변환 연산만 (상대적으로 저렴) |
| 도달한 규모 | 대체로 2B 파라미터 언저리 | 27B (한 자릿수 이상 큼) |
| 기존 자산 활용 | 불가 — 기존 모델을 버려야 함 | Qwen, Gemma 등 그대로 재활용 |
백서는 Qwen3.6-27B뿐 아니라 Gemma-4-31B 계열에도 같은 방법을 적용해 유사한 패턴을 재현했다고 보고한다. "특정 모델의 우연이 아니라 방법론 자체의 성질"이라는 교차검증인 셈이다.
4비트 KV 캐시를 쓰면 보통 출력 분포가 흐트러지는데(forward-KL 발산), Bonsai는 FP16 모델 대비 그 발산이 12~95배 작았다(백서 Table 6). 이미 거칠게 양자화된 모델은 추가 노이즈에 둔감하다는 얘기로, 저비트 가중치 + 저비트 KV캐시 조합이 "공짜 점심"에 가까워진다.
모델 가중치는 HuggingFace에 있고 커널 코드는 llama.cpp/MLX 포크에 있다. 그럼 이 저장소에 남는 건 스크립트뿐인데, 왜 여기가 별을 모을까? 답은 여기가 유일한 정문(front door)이기 때문이다.
community-benchmarks/)도 여기다AI 프로젝트인데 Python이 3%밖에 안 된다. Shell 65% + PowerShell 32%가 본체다. 이유는 명확하다 — 이 저장소는 모델을 만들지 않고 설치하고 실행하기 때문이다. 무거운 연산은 전부 C++(llama.cpp)와 Metal/CUDA 커널이 한다.
| 구성요소 | 버전 / 상태 | 역할 |
|---|---|---|
| llama.cpp | PrismML 포크 prism 브랜치릴리스 prism-b9591-62061f9 | 주력 엔진. CPU/CUDA/Metal/Vulkan/ROCm 전부 |
| Q1_0 커널 | llama.cpp 본류 병합 완료 | 1비트(binary) 가중치 연산 |
| Q2_0 커널 | CPU/Metal/Vulkan 병합, CUDA는 리뷰 중 | ternary 가중치 연산 (그래서 포크가 필요) |
| MLX | PrismML 포크 + mlx-lm==0.31.2본류 PR mlx#3161 대기 | Apple Silicon 전용 경로, safetensors 포맷 |
| DSpark drafter | Q4_1 소형 GGUF | 투기적 디코딩용 초안 모델 (실험적) |
이 프로젝트는 파이썬 가상환경을 3개 만든다. 처음 보면 과해 보이지만 이유가 있다.
| venv | 핵심 패키지 | 왜 분리했나 |
|---|---|---|
.venv | huggingface-hub, cmake, ninja (+ mlx-lm, torch 2.10, transformers 5.2) | 기본 환경. MLX는 PrismML 포크를 씀 |
.venv-vlm | mlx-vlm 0.6.3, transformers 5.5 | 비전 모델용. transformers 버전이 기본 venv와 충돌해서 격리 |
.venv-jupyter | jupyter-server, numpy, pandas, matplotlib, sympy, yfinance | LLM이 코드를 실행하는 샌드박스. 무거운 과학 스택을 본 환경에 안 섞음 |
포크된 MLX(1비트 커널 포함)와 정품 mlx-vlm은 같은 환경에 못 산다. 흔한 대응은 버전을 억지로 맞추다 둘 다 망가뜨리는 것인데, 이 프로젝트는 그냥 venv를 하나 더 만든다. 디스크는 몇 GB 더 쓰지만 재현성은 확실해진다.
패키지 매니저로 uv(Astral, Rust로 짠 초고속 pip 대체재)를 쓴다. uv venv, uv sync, uv pip install 조합이며 uv.lock으로 버전을 못 박는다. 파이썬 요구는 >=3.11.
소스 빌드도 플랫폼별 스크립트가 따로 있다: build_mac.sh, build_cpu_linux.sh, build_cuda_linux.sh, build_cuda_windows.ps1.
| 항목 | 내용 |
|---|---|
| 라이선스 | Apache License 2.0 (저장소 코드) |
| CI 워크플로 | 플랫폼 스모크 테스트(라벨 smoke-test 트리거), 소스 빌드 테스트, em-dash 문자 검사 |
| 테스트 | unittest 기반 — 웹 fetch / SQL / 코드 인터프리터 / WebUI 시딩 |
| UI | llama.cpp 내장 웹UI(8080), Open WebUI 0.10.2 핀 고정(9090) |
| 프로토콜 | OpenAI 호환 /v1/chat/completions, MCP(Model Context Protocol) 클라이언트 내장 |
CI에 check-no-em-dashes.yml이 있다. 긴 대시(—) 문자를 코드베이스에서 금지하는 검사다. 이건 "AI가 쓴 티가 나는 문장"을 걸러내는 위생 규칙으로 널리 쓰인다 — LLM은 em-dash를 유난히 많이 쓰기 때문이다. 문서를 AI로 많이 쓰는 팀이 스스로 건 가드레일로 읽힌다.
./setup.sh 한 줄 뒤에서 실제로 벌어지는 일BONSAI_FAMILY(ternary / bonsai / all)와 BONSAI_MODEL(27B / 8B / 4B / 1.7B / all)의 곱으로 경로와 파일 패턴이 결정된다. scripts/common.sh에 중첩 case문으로 사전 계산해두고 모든 스크립트가 이 함수를 재사용한다.
# scripts/common.sh — 실제 코드
case "$BONSAI_MODEL" in
27B|8B|4B|1.7B)
case "$BONSAI_FAMILY" in
ternary)
GGUF_MODEL_DIR="models/ternary-gguf/${BONSAI_MODEL}"
MLX_MODEL_DIR="models/Ternary-Bonsai-${BONSAI_MODEL}-mlx-2bit"
GGUF_QUANT_PATTERN="*-Q2_0.gguf"
BONSAI_DISPLAY="Ternary-Bonsai-${BONSAI_MODEL}"
;;
esac
;;
esac
all이라는 값은 setup/download 스크립트에서만 유효하다. 실행 스크립트는 _assert_concrete_model()이라는 가드 함수로 all을 명시적으로 거부한다. "네 개를 한꺼번에 받아라"는 말은 되지만 "네 개를 한꺼번에 실행해라"는 말이 안 되니까. 같은 변수를 쓰면서 단계별로 허용 범위를 좁히는 깔끔한 처리다.
download_models.sh는 huggingface_hub.snapshot_download()를 파이썬 인라인으로 호출하되, allow_patterns로 필터를 건다.
# 27B ternary를 받을 때의 패턴 (개념 요약)
allow_patterns = [
"*-Q2_0.gguf", # 본체 (ternary 가중치)
"*mmproj*.gguf", # 비전 프로젝터 (멀티모달용)
"*dspark-Q4_1*.gguf", # 투기적 디코딩용 초안 모델
]
이 필터가 없으면 저장소에 함께 올라간 F16 참조본(47GB짜리)까지 딸려온다. 27B에서만 mmproj와 dspark를 추가로 받는데, 작은 모델들은 비전 타워가 없기 때문이다.
download_binaries.sh가 하는 하드웨어 감지 순서:
맞는 tar.gz를 curl 또는 wget으로 받아 풀고, macOS라면 Gatekeeper 격리 속성 제거(xattr -cr) + 임시 서명(codesign -s -)까지 자동 처리한 뒤 --version으로 스모크 테스트한다. Mac에서 남이 만든 바이너리를 돌릴 때 겪는 "손상되었기 때문에 열 수 없습니다" 문제를 스크립트가 대신 풀어주는 것이다.
start_llama_server.sh가 최종적으로 조립하는 명령이다. 로컬 LLM 서버 운용의 교과서라 한 줄씩 볼 값어치가 있다.
exec "$BIN" -m "$MODEL" --host "$HOST" --port "$PORT" -ngl "$NGL" -fa on -c "$_ctx" \
--temp 0.7 --top-p 0.95 --top-k 20 --min-p 0 \
--jinja \
${MMPROJ:+--mmproj "$MMPROJ"} \
${_imt:+--image-max-tokens "$_imt"} \
${MD:+-md "$MD"} $_spec_flags \
$_kv_args ${KV_BIAS:+--kv-mean-center "$KV_BIAS"} \
--webui-config-file "$_webui_cfg" \
"$@"
| 플래그 | 의미 |
|---|---|
-ngl | GPU에 올릴 레이어 수. bonsai_llama_ngl() 함수가 하드웨어 보고 자동 결정 |
-fa on | Flash Attention 활성화 (메모리·속도 개선) |
-c 0 | 컨텍스트 크기 자동 맞춤(--fit). 지원 안 되면 RAM 기반 폴백값으로 재시도 |
--jinja | 모델의 Jinja 채팅 템플릿 사용 → OpenAI 스타일 네이티브 tool_calls 가능 |
--mmproj | 비전 프로젝터 연결 (이미지 입력) |
-md | draft model — 투기적 디코딩용 초안 모델 |
${VAR:+...} | 셸 문법. VAR가 비어있지 않을 때만 그 플래그를 붙인다 (조건부 인자 조립) |
백서 Section 5의 주장 중 기술적으로 가장 무거운 부분이다. 베이스가 된 Qwen3.6-27B는 표준 트랜스포머가 아니다.
문제는 기존의 저비트 커널들이 표준 어텐션을 가정하고 짜여 있다는 것이다. 그래서 PrismML은 이 하이브리드 구조에 맞는 1비트/2비트 커널을 CUDA와 MLX 양쪽에 새로 작성해야 했다. "모델만 압축하면 끝"이 아니라 런타임 커널까지 함께 만들어야 실제로 돌아간다는 게 이 분야의 현실이다.
Open WebUI 데모에 심는 도구 4종(weather, web_fetch, sql, code_interpreter) 중 두 개가 특히 잘 짜여 있다.
# scripts/openwebui/tool_web_fetch.py — SSRF 방어
def _is_public_ip(ip_str: str) -> bool:
try:
ip = ipaddress.ip_address(ip_str)
except ValueError:
return False
# is_global rejects loopback, private LAN, link-local (169.254/16 -> cloud
# metadata), CGNAT (100.64/10), reserved, multicast and unspecified.
return ip.is_global
LLM이 "이 URL 좀 읽어줘" 도구를 갖게 되면, 프롬프트 인젝션으로 http://169.254.169.254/(클라우드 메타데이터 서버) 같은 내부 주소를 읽게 만들 수 있다. 거기엔 보통 인스턴스의 IAM 자격증명이 들어 있다.
이 코드가 막는 방식은 두 겹이다. ① 리다이렉트가 일어날 때마다 매번 해석된 IP를 재검증한다(첫 요청만 검사하면 리다이렉트로 우회 가능). ② ProxyHandler({})로 환경변수 프록시 설정을 무력화해 프록시 경유 우회를 막는다.
# scripts/openwebui/tool_sql.py — 읽기 전용 3중 잠금
stripped = _without_leading_comments(sql).strip().rstrip(";").strip()
if ";" in stripped:
return "Only a single SQL statement is allowed." # ① 다중문 차단
if not re.match(r"(?is)^(select|with)\b", stripped):
return "Only read-only SELECT / WITH queries are allowed." # ② 동사 화이트리스트
...
with sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True, timeout=10) as conn:
conn.execute("PRAGMA query_only = ON") # ③ DB 레벨 강제
주목할 점은 정규식만 믿지 않는다는 것. 문자열 검사(①②)를 통과해도 SQLite 연결 자체를 mode=ro로 열고 PRAGMA query_only까지 건다. 앞의 방어가 뚫려도 DB가 쓰기를 거부한다.
make_demo_db.py는 가짜 B2B 매출 SQLite DB를 random.seed(1337)로 결정론적으로 생성한다. 그런데 코드 주석에 스토리가 하드코딩되어 있다.
"EMEA 매출이 2025 Q3에 무너진다. 원인은 두 앵커 고객이 v3 펌웨어 버그로 지원 티켓이 폭증한 뒤 이탈한 것 + 주력 제품 20% 가격 인하가 겹친 것."
사용자가 "EMEA 매출 왜 떨어졌어?"라고 물으면, LLM이 SQL 도구로 여러 테이블을 스스로 조인해가며 탐정처럼 원인을 추적하도록 설계된 것이다. 에이전트 데모를 만들 때 "그럴듯한 데이터"만으로는 부족하고 발견할 만한 인과관계를 심어둬야 한다는 좋은 교훈이다.
README 외에 기능별 마크다운이 6개 더 있고, 그중 AGENTS.md는 사람이 아니라 AI 코딩 에이전트가 읽으라고 쓴 문서다. 설치가 복잡한 프로젝트가 "사용자가 Claude Code한테 시킬 것"을 전제하고 문서를 준비해두는 건 2026년다운 패턴이다.
이 저장소를 통틀어 가장 값어치 있는 한 가지. "Q4_K_M"이라는 이름을 보면 실제 파일 크기 ÷ 파라미터 수를 직접 계산해보는 습관을 들이게 된다. 4비트라 광고된 게 5.2bpw인 이유(임베딩·LM head는 안 줄임)를 이해하면, 앞으로 어떤 양자화 포맷을 봐도 "그래서 무엇을 안 줄였는가"를 먼저 묻게 된다.
실습: 내가 쓰는 GGUF 파일의 실제 bpw를 계산해보고, HuggingFace 모델 카드에 적힌 라벨과 비교해보라.
POSIX sh만으로 macOS/Linux/Windows(WSL) + CPU/CUDA/Metal/Vulkan/ROCm 조합을 전부 커버한다. 배울 기법: 기능 감지(feature detection) 우선(OS 이름이 아니라 nvcc 존재 여부로 판단), 폴백 체인, 멱등성(이미 끝난 단계는 건너뛰어 재실행 안전), ${VAR:+...}를 이용한 조건부 인자 조립.
LLM에게 도구를 주는 순간 프롬프트 인젝션이 곧 원격 코드 실행 시도가 된다. 이 저장소가 보여주는 방어 목록: SSRF 방지(리다이렉트마다 IP 재검증 + 프록시 무력화), zip bomb 방어(MAX_DECOMPRESSED_BYTES), 호출 횟수 상한(MAX_FETCHES_PER_CHAT), SQL 3중 잠금, Jupyter 커널 격리(전용 작업 디렉토리 + 비밀키 환경변수 unset).
이론상 항상 이득일 것 같지만 현실은 다르다. CUDA에서는 1.34~1.37배 빨라지는데 Apple Silicon에서는 오히려 손해다(batch=1에서 검증 forward의 오버헤드를 상쇄하지 못함). 최적화 기법이 하드웨어 특성에 따라 부호가 뒤집힌다는 실전 감각을 얻을 수 있다.
64개 레이어 중 16개만 KV 캐시가 자라는 구조 → 캐시 메모리 1/4. 긴 컨텍스트에서 진짜 병목이 가중치가 아니라 KV 캐시라는 사실(100K 컨텍스트에서 ternary 27B는 가중치 6.7GB + 캐시 7GB)을 숫자로 체감할 수 있다.
community-benchmarks/TEMPLATE-llama-cpp.md가 좋은 예다. llama-bench의 pp512(prompt processing, 입력 처리 속도)와 tg128(text generation, 출력 생성 속도)를 표준 지표로 고정하고, 하드웨어·백엔드·빌드 커밋까지 함께 적게 한다. "빠르다"를 재현 가능한 숫자로 바꾸는 양식 설계를 배울 수 있다.
| 모델 / 포맷 | 가중치 | 4K 컨텍스트 | 10K | 100K |
|---|---|---|---|---|
| Bonsai-27B (Q1_0) | 3.53 GB | 4.8 GB | 5.2 GB | 10.8 GB |
| Bonsai-27B (MLX 1bit) | 3.92 GB | 5.5 GB | 5.9 GB | 11.4 GB |
| Ternary-27B (Q2_0) | 6.66 GB | 7.8 GB | 8.1 GB | 13.7 GB |
| Ternary-27B (MLX 2bit) | 7.05 GB | 8.6 GB | 8.9 GB | 14.4 GB |
| (참고) 27B FP16 | 47.7 GB | 49 GB | 49.6 GB | 55.2 GB |
BONSAI_KV4=1로 4비트 KV 캐시를 켜면 100K 컨텍스트에서 ternary가 13.7GB → 9.2GB로 줄어든다.
| 기기 | 권장 | 이유 |
|---|---|---|
| iPhone (12GB RAM) | 1-bit 27B만 | iOS는 앱당 물리메모리의 약 절반만 허용 → 가용 ~6GB. ternary(5.9~7.2GB)는 안 들어감 |
| MacBook (16GB) | ternary 8B 또는 1-bit 27B | ternary 27B는 컨텍스트 여유가 빠듯함 |
| MacBook (32GB+) | ternary 27B 여유롭게 | 100K 컨텍스트도 가능 |
| NVIDIA GPU 12GB+ | ternary 27B + DSpark | 투기적 디코딩이 실제로 이득 나는 유일한 환경 |
| 구형 CPU only | 1.7B / 4B | 동작은 하지만 27B는 실용 속도 안 나옴 |
| 하드웨어 | 모델 | 측정치 |
|---|---|---|
| Apple M4 Pro (48GB, Metal) | Bonsai-8B | tg128 117 tok/s |
| Apple M4 Pro (48GB, Metal) | Bonsai-1.7B | tg128 308 tok/s |
| NVIDIA L40S (48GB, CUDA 12.8) | Ternary-27B | tg128 70 tok/s → DSpark 적용 87~103 tok/s |
| AMD Strix Halo (128GB, ROCm) | Bonsai-1.7B | pp512 5,001 tok/s (소비자 APU 치고 이례적) |
| iPhone 17 Pro Max (A19 Pro) | 1-bit 27B | 약 11 tok/s (백서 실측) |
| OS | 지원 백엔드 |
|---|---|
| macOS | Metal (Apple Silicon) / CPU (Intel Mac) |
| Linux | x64·arm64 — CPU, CUDA 12.4·12.8, Vulkan, ROCm 7.2 |
| Windows | x64·arm64 — CPU, CUDA, Vulkan, HIP |
| iOS | XCFramework 제공 |
llama.cpp CUDA 빌드는 메모리를 엄청나게 먹는다. 빌드 스크립트가 GPU VRAM이 16GB 미만이면 자동으로 -j2로 병렬도를 낮춘다. 그래도 죽으면 더 낮추거나, 애초에 소스 빌드 대신 사전 빌드 바이너리를 쓰는 게 낫다.
이미 갖고 있는(또는 HuggingFace에서 파일 크기만 확인 가능한) 양자화 모델들의 파일크기(bit) ÷ 파라미터수를 계산해 표로 만든다. Q4_K_M, Q5_K_M, Q8_0, IQ2_XXS를 같은 모델에 대해 비교하고, 라벨과 실측의 차이를 정리한다.
배우는 것: 양자화 마케팅과 실제의 간극. 이 하나만 해봐도 이 문서의 절반은 소화한 것이다.
BONSAI_MODEL=1.7B BONSAI_FAMILY=ternary ./setup.sh로 설치한 뒤 CLI → 서버(:8080) → Open WebUI(:9090) 순으로 세 가지 실행 경로를 다 밟아본다. 27B는 다운로드가 크니 처음엔 1.7B로.
배우는 것: 로컬 LLM의 실행 형태 3종(CLI / OpenAI 호환 서버 / 웹UI)의 차이와 각각의 용도.
community-benchmarks/TEMPLATE-llama-cpp.md 양식대로 llama-bench를 돌려 pp512/tg128을 측정하고, 하드웨어 정보와 함께 PR을 올린다. 실제로 머지되면 오픈소스 기여 이력이 하나 생긴다.
배우는 것: 표준화된 성능 측정 방법론 + 실제 OSS 기여 워크플로. 코드를 안 고치고도 기여할 수 있는 좋은 진입점이다.
scripts/openwebui/tool_weather.py를 템플릿 삼아 새 도구를 만든다(예: 사내 위키 검색, 로컬 파일 grep). 그리고 tool_web_fetch.py의 방어 패턴을 참고해 보안 가드를 반드시 함께 넣는다. tests/에 유닛테스트도 추가한다.
배우는 것: 툴콜링 스키마 설계, 에이전트 도구 보안, seed_openwebui.py의 멱등 시딩 패턴.
같은 베이스 모델(Qwen 계열)의 FP16 / Q4_K_M / IQ2_XXS / Ternary Bonsai를 모두 준비해, 동일한 평가셋(예: MMLU 서브셋, GSM8K 일부)으로 직접 점수를 재본다. 백서 Table 10의 순위가 내 환경에서도 재현되는지 확인한다.
배우는 것: 벤치마크 재현의 어려움 그 자체. 서빙 스택·샘플링 파라미터·프롬프트 포맷이 조금만 달라도 점수가 흔들린다는 걸 체감하게 된다. "논문 숫자를 그대로 믿지 않는 법"이 이 실습의 진짜 수확이다.
| 주차 | 주제 | 구체적 할 일 |
|---|---|---|
| 1주차 | 양자화 기초 | FP32/FP16/BF16/INT8의 비트 배치 이해 → GGUF quant 타입(Q4_K_M, Q5_K_S, IQ 계열)이 각각 무엇을 어떻게 줄이는지 정리 → 실습 1 수행 |
| 2주차 | llama.cpp 실전 | 직접 빌드해보기 → llama-cli/llama-server/llama-bench/llama-quantize 각각의 역할 → -ngl, -c, -fa 플래그 튜닝 감각 |
| 3주차 | KV 캐시와 컨텍스트 | KV 캐시가 왜 컨텍스트에 비례해 커지는지 수식으로 → GQA/MQA, linear attention(Gated-DeltaNet, Mamba) 개념 → KV 캐시 양자화 트레이드오프 |
| 4주차 | 1비트 LLM 논문 읽기 | BitNet b1.58 논문 → Bonsai 백서 3종 → 두 접근(사전학습 vs 사후변환)의 장단점을 스스로 표로 정리 |
| 5주차 | 추론 가속 기법 | 투기적 디코딩(원논문 + Medusa/EAGLE 계열) → 실습으로 accept-length 측정 → 왜 하드웨어별로 손익이 갈리는지 분석 |
| 6주차 | Apple Silicon / MLX | 통합 메모리 아키텍처 → MLX 기본 API → 같은 모델을 MLX vs llama.cpp Metal로 벤치마크 비교 |
| 7주차 | 에이전트 도구와 보안 | OpenAI tool calling 스펙 → MCP 프로토콜 → 프롬프트 인젝션 공격 유형과 방어 → 실습 4 수행 |
| 8주차 | 온디바이스 배포 | iOS XCFramework 통합 → 모바일 메모리 제약(jetsam) → 양자화 선택이 곧 제품 결정이 되는 지점 이해 |
| 용어 | 뜻 |
|---|---|
| bpw | bits per weight. 가중치당 실효 비트수. 모델 용량 ÷ 파라미터 수 |
| Ternary | 가중치가 {-1, 0, +1} 3값만 갖는 양자화. 이론상 log2(3) ≈ 1.585비트 |
| Binary / 1-bit | 가중치가 {-1, +1} 2값만 갖는 양자화 |
| g128 | 가중치 128개를 한 그룹으로 묶어 그룹당 FP16 스케일 1개를 공유하는 포맷 |
| GGUF | llama.cpp의 모델 파일 포맷. 가중치+토크나이저+메타데이터 단일 파일 |
| Q1_0 / Q2_0 | Bonsai가 쓰는 GGUF quant 타입. 각각 binary / ternary |
| safetensors | HuggingFace 표준 텐서 저장 포맷. MLX 경로에서 사용 |
| MLX | Apple Silicon 전용 ML 프레임워크. 통합 메모리 활용 |
| KV 캐시 | 어텐션의 Key/Value를 재계산 안 하려고 저장해두는 메모리. 컨텍스트 길이에 비례해 증가 |
| Gated-DeltaNet | Linear attention 계열 구조. KV 캐시가 자라지 않는 고정 크기 상태를 유지 |
| 하이브리드 어텐션 | linear attention 레이어와 full attention 레이어를 섞은 구조. Qwen3.6-27B가 약 75:25 |
| 투기적 디코딩 | 작은 초안 모델이 추측 → 큰 모델이 일괄 검증. 출력은 동일(lossless)한데 속도만 향상 |
| DSpark | Bonsai가 쓰는 초안 모델. semi-autoregressive 방식으로 K=4 토큰 블록을 병렬 생성 |
| pp512 / tg128 | llama-bench 표준 지표. 입력 512토큰 처리 속도 / 출력 128토큰 생성 속도 |
| forward-KL | 두 확률분포의 차이 측정치. 양자화가 출력 분포를 얼마나 왜곡했는지 재는 데 사용 |
| mmproj | multimodal projector. 이미지 인코더 출력을 LLM 임베딩 공간으로 옮기는 어댑터 |
| HQQ | Half-Quadratic Quantization. Bonsai가 비전 타워에만 별도로 쓰는 4비트 양자화 |
| SSRF | Server-Side Request Forgery. 서버가 공격자 지정 내부 주소로 요청하게 만드는 공격 |
| MCP | Model Context Protocol. LLM에 외부 도구/데이터소스를 연결하는 표준 프로토콜 |
| uv | Astral이 Rust로 만든 초고속 파이썬 패키지 매니저. pip/venv 대체 |
| 멱등성(idempotent) | 여러 번 실행해도 결과가 같은 성질. 설치 스크립트의 핵심 요구사항 |
ternary 94.6%, binary 89.5%. 특히 binary는 10% 이상 잃는다. "Q4_K_XL이 99.9%를 17.6GB로 달성"한다는 사실을 잊으면 안 된다. Bonsai가 이기는 건 17GB를 올릴 수 없는 기기에서일 뿐이다.
백서 Section 9가 명시한다 — 멀티파일 수정, run-test-repair 루프 같은 코딩 에이전트 작업은 이번 릴리스가 강하게 겨냥한 영역이 아니다. "코딩 특화 Bonsai 27B 변형이 로드맵에 있다"고 되어 있으니, 지금 버전으로 Claude Code를 대체할 생각은 접는 게 좋다.
ternary의 이론 최소는 5.9GB인데 실제 배포는 7.2GB다. 현재 커널이 ternary 값을 2비트 슬롯에 담기 때문이다. 백서는 네이티브 ternary 커널이 "액티브 엔지니어링 타깃"이라고 표현했는데, 이는 아직 완성 안 됐다는 뜻이다.
투기적 디코딩(BONSAI_SPECULATIVE=1)과 4비트 KV 캐시(BONSAI_KV4=1) 모두 문서에 "Experimental"로 표시되어 있고 off-by-default다. 특히 Apple Silicon에서 투기적 디코딩은 오히려 느려진다고 README와 AGENTS.md가 반복 경고한다.
MLX 백엔드에는 요청 간 프롬프트 캐시가 없어서, 매 턴마다 전체 대화(이미지 토큰 포함)를 다시 프리필한다. AGENTS.md도 멀티턴 사용자에게는 llama.cpp 백엔드를 권장한다.
README와 백서 모두 27B HuggingFace 리포가 비공개일 수 있다고 언급하며 BONSAI_TOKEN 설정 절차를 안내한다. 이 문서 작성 시점 기준으로 지금 바로 27B를 익명 다운로드할 수 있는지는 보장되지 않는다. 8B 이하로 먼저 시작하는 걸 권한다.
start_openwebui.sh는 WEBUI_AUTH=false로 구동하고 코드 실행(Jupyter)까지 켜져 있다. 그래서 스크립트가 loopback이 아닌 주소로 바인딩하는 것을 명시적으로 거부한다(BONSAI_ALLOW_REMOTE=1을 강제로 주지 않는 한).
이 가드를 우회해 공용 네트워크에 노출하면, 누구나 당신 기계에서 임의 코드를 실행할 수 있다. 데모는 localhost에서만 쓰자.
git clone --depth 1로 받은 실제 소스와 저장소에 포함된 백서 PDF를 근거로 작성했다. 별·포크 수는 GitHub API 실시간 조회값(별 1,768 / 포크 170)이다. 벤치마크 수치는 모두 PrismML이 자체 발표한 값이며 제3자 재현 검증은 아직 확인되지 않았다.