트렌딩 딥다이브 · 2026-07-18 · OSSInsight

PrismML-Eng/Bonsai-demo 딥다이브
— 27B 모델을 3.9GB로 접어 아이폰에 넣는 1비트 압축 실험실

Bonsai이미 학습이 끝난 27B급 LLM의 모든 가중치를 -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)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 주목받는가 — "4비트"는 4비트가 아니었다
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 — setup.sh 한 줄 뒤에서 벌어지는 일
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 하드웨어 · 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크 · 한계와 주의사항

1프로젝트 한 줄 요약

이 저장소가 정확히 무엇인가
한 문장으로

Bonsai-demo는 모델이 아니라 런처다. "1비트로 접힌 27B 모델을 당신 기계에서 돌아가게 만드는 설치·실행 스크립트 모음"이고, 모델 가중치 자체는 HuggingFace에 따로 있다.

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%의 지능을 유지한다"는 표현이 나온다 — 새로 키운 게 아니라 원래 나무를 접은 것이니까.

용어
양자화 (Quantization)
모델 가중치 숫자를 더 적은 비트로 표현하는 압축 기법. 원래 하나의 가중치가 FP16(16비트, 소수점 표현)이라면, 4비트로 줄이면 용량이 1/4이 된다. 문제는 정밀도가 떨어져 모델이 멍청해진다는 것 — 그래서 "얼마나 줄이면서 얼마나 안 멍청해지는가"가 이 분야 전체의 싸움이다.
용어
Ternary(3진) / Binary(1비트) 가중치
가중치가 가질 수 있는 값이 각각 {-1, 0, +1} 세 개, {-1, +1} 두 개뿐인 극단적 양자화. 곱셈이 사실상 "더하기/빼기/무시"로 바뀌어 연산도 빨라진다. 대신 그 자체로는 스케일(크기)을 표현 못 해서, 가중치 128개를 한 그룹으로 묶어 그룹당 FP16 스케일 1개를 따로 저장한다(g128 포맷).
용어
bpw (bits per weight, 가중치당 비트)
이 문서 전체에서 가장 중요한 지표. 모델 전체 용량 ÷ 파라미터 수로 계산되는 실효 비트수다. Bonsai ternary의 경우 log2(3) + 16/128 ≈ 1.71 bpw — 3진값 자체가 약 1.585비트, 여기에 128개당 FP16 스케일 하나(16/128 = 0.125비트)를 더한 값이다.

2왜 주목받는가 — "4비트"는 4비트가 아니었다

트렌딩 이유와 기존 양자화 대비 차별점

① 백서의 폭로: 라벨과 실측이 다르다

로컬 LLM을 써본 사람이라면 Q4_K_M, IQ2_XXS 같은 이름을 봤을 것이다. 이름만 보면 각각 4비트, 2비트 같다. Bonsai 백서가 실제로 재본 값은 이렇다.

흔한 이름사람들이 생각하는 비트실측 bpw왜 이런 차이가?
Q4_K_XL4비트5.2 bpw임베딩·어텐션·출력 레이어는 4~8비트로 남겨두는 혼합정밀 방식
IQ2_XXS2비트2.8 bpw같은 이유. 민감한 텐서는 못 줄임
Ternary Bonsai1.71 bpw임베딩·프로젝션·LM head까지 전 구간 압축
1-bit Bonsai1.125 bpw동일. 비전 타워만 예외적으로 HQQ 4비트
비유

"이 노트북 무게 1.2kg입니다"라고 광고해놓고 충전기와 어댑터는 빼고 잰 것과 같다. 가방에 넣고 다닐 실제 무게는 1.9kg인데 말이다. 기존 양자화 포맷의 "4비트"는 일부 레이어만 4비트라는 뜻이었고, 무거운 부품들은 그대로 남아 있었다.

Bonsai의 주장은 "우리는 충전기까지 포함해서 잰 무게가 이겁니다"에 가깝다. 그리고 그 전제에서 비교하면 격차가 훨씬 커 보인다.

② 벤치마크: 크기는 2/3인데 점수는 더 높다

백서 Table 10 기준(EvalScope + vLLM, H100, 15개 벤치마크 평균, thinking 모드).

모델실효 bpw용량15벤치 평균FP16 대비
Qwen3.6-27B FP1616.054 GB85.07100%
Q4_K_XL ("4비트")5.217.6 GB84.9999.9%
IQ2_XXS ("2비트")2.89.4 GB72.7385.5%
Ternary Bonsai 27B1.715.9 GB80.4994.6%
1-bit Bonsai 27B1.1253.9 GB76.1189.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는 도저히 못 올리는 기기" — 즉 폰, 저사양 노트북, 임베디드다. 백서 스스로도 무손실이 아니다라고 명시한다.

③ BitNet과의 결정적 차이: 재학습이 필요 없다

1비트 LLM 얘기가 나오면 늘 등장하는 게 마이크로소프트의 BitNet(b1.58)이다. 백서는 이 차이를 정면으로 다룬다.

항목BitNet 계열Bonsai
접근 방식처음부터 1비트로 사전학습(pretrain)학습 끝난 모델을 사후 변환
비용모델을 통째로 새로 학습 (막대함)변환 연산만 (상대적으로 저렴)
도달한 규모대체로 2B 파라미터 언저리27B (한 자릿수 이상 큼)
기존 자산 활용불가 — 기존 모델을 버려야 함Qwen, Gemma 등 그대로 재활용

백서는 Qwen3.6-27B뿐 아니라 Gemma-4-31B 계열에도 같은 방법을 적용해 유사한 패턴을 재현했다고 보고한다. "특정 모델의 우연이 아니라 방법론 자체의 성질"이라는 교차검증인 셈이다.

흥미로운 부수 효과
저비트 가중치 모델은 저비트 KV 캐시도 잘 버틴다

4비트 KV 캐시를 쓰면 보통 출력 분포가 흐트러지는데(forward-KL 발산), Bonsai는 FP16 모델 대비 그 발산이 12~95배 작았다(백서 Table 6). 이미 거칠게 양자화된 모델은 추가 노이즈에 둔감하다는 얘기로, 저비트 가중치 + 저비트 KV캐시 조합이 "공짜 점심"에 가까워진다.

④ 그런데 왜 "demo" 레포가 뜨는가

모델 가중치는 HuggingFace에 있고 커널 코드는 llama.cpp/MLX 포크에 있다. 그럼 이 저장소에 남는 건 스크립트뿐인데, 왜 여기가 별을 모을까? 답은 여기가 유일한 정문(front door)이기 때문이다.

3기술 스택 전체 지도

백엔드 · 런타임 · 인프라를 각각 상세히

언어 구성이 특이하다

AI 프로젝트인데 Python이 3%밖에 안 된다. Shell 65% + PowerShell 32%가 본체다. 이유는 명확하다 — 이 저장소는 모델을 만들지 않고 설치하고 실행하기 때문이다. 무거운 연산은 전부 C++(llama.cpp)와 Metal/CUDA 커널이 한다.

용어
llama.cpp / GGUF
llama.cpp는 C++로 짜인 LLM 추론 엔진으로, GPU 없이도 CPU에서 LLM을 돌리게 만든 프로젝트다. GGUF는 그 전용 모델 파일 포맷(하나의 파일에 가중치+토크나이저+메타데이터가 다 들어감). 로컬 LLM 생태계의 사실상 표준이다.
용어
MLX
Apple이 만든 Apple Silicon(M1~M4) 전용 머신러닝 프레임워크. 통합 메모리(unified memory) 구조를 활용해 CPU/GPU가 데이터를 복사 없이 공유한다. Mac에서 LLM을 돌릴 때 llama.cpp의 Metal 백엔드와 경쟁하는 선택지.

백엔드 · 추론 엔진

구성요소버전 / 상태역할
llama.cppPrismML 포크 prism 브랜치
릴리스 prism-b9591-62061f9
주력 엔진. CPU/CUDA/Metal/Vulkan/ROCm 전부
Q1_0 커널llama.cpp 본류 병합 완료1비트(binary) 가중치 연산
Q2_0 커널CPU/Metal/Vulkan 병합, CUDA는 리뷰 중ternary 가중치 연산 (그래서 포크가 필요)
MLXPrismML 포크 + mlx-lm==0.31.2
본류 PR mlx#3161 대기
Apple Silicon 전용 경로, safetensors 포맷
DSpark drafterQ4_1 소형 GGUF투기적 디코딩용 초안 모델 (실험적)

Python 계층 — venv를 셋으로 쪼갠 이유

이 프로젝트는 파이썬 가상환경을 3개 만든다. 처음 보면 과해 보이지만 이유가 있다.

venv핵심 패키지왜 분리했나
.venvhuggingface-hub, cmake, ninja
(+ mlx-lm, torch 2.10, transformers 5.2)
기본 환경. MLX는 PrismML 포크를 씀
.venv-vlmmlx-vlm 0.6.3, transformers 5.5비전 모델용. transformers 버전이 기본 venv와 충돌해서 격리
.venv-jupyterjupyter-server, numpy, pandas, matplotlib, sympy, yfinanceLLM이 코드를 실행하는 샌드박스. 무거운 과학 스택을 본 환경에 안 섞음
배울 점
"의존성 지옥"의 정공법 — 격리

포크된 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.

인프라 · CI

항목내용
라이선스Apache License 2.0 (저장소 코드)
CI 워크플로플랫폼 스모크 테스트(라벨 smoke-test 트리거), 소스 빌드 테스트, em-dash 문자 검사
테스트unittest 기반 — 웹 fetch / SQL / 코드 인터프리터 / WebUI 시딩
UIllama.cpp 내장 웹UI(8080), Open WebUI 0.10.2 핀 고정(9090)
프로토콜OpenAI 호환 /v1/chat/completions, MCP(Model Context Protocol) 클라이언트 내장
여담 — em-dash 린트가 왜 있나

CI에 check-no-em-dashes.yml이 있다. 긴 대시(—) 문자를 코드베이스에서 금지하는 검사다. 이건 "AI가 쓴 티가 나는 문장"을 걸러내는 위생 규칙으로 널리 쓰인다 — LLM은 em-dash를 유난히 많이 쓰기 때문이다. 문서를 AI로 많이 쓰는 팀이 스스로 건 가드레일로 읽힌다.

4아키텍처 심화 분석

./setup.sh 한 줄 뒤에서 실제로 벌어지는 일

전체 시스템 구조도

┌──────────────────────────────────────────────────────────────────┐ │ ./setup.sh (425줄, POSIX sh / setup.ps1은 PowerShell 대응) │ │ │ │ ① 시스템 의존성 확인 Xcode CLT(mac) / build-essential(linux) │ │ ② uv 설치 → .venv 생성 (python 3.11) │ │ ③ uv sync cmake, ninja, huggingface-hub │ │ ④ download_models.sh HuggingFace prism-ml/* → models/ │ │ ⑤ download_binaries.sh GitHub Release → bin/{mac,cuda,...}/ │ │ ⑥ MLX 빌드 (macOS만) git clone -b prism PrismML-Eng/mlx │ │ ⑦ Open WebUI 설치 webui extra │ │ ⑧ .venv-jupyter 구성 code interpreter 샌드박스 │ └──────────────────────────────────────────────────────────────────┘ │ 실행 경로는 세 갈래로 갈린다 ┌───────────────────────┼───────────────────────┐ ▼ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ run_llama.sh │ │ run_mlx.sh │ │ start_openwebui.sh │ │ 1회성 CLI 실행 │ │ Apple Silicon │ │ 풀스택 에이전트 데모 │ │ │ │ 전용 (safeten..) │ │ │ │ start_llama_ │ │ start_mlx_ │ │ ├ llama/mlx 서버 │ │ server.sh │ │ server.sh │ │ ├ Jupyter 커널 │ │ → :8080 │ │ → :8081 │ │ └ 도구 4종 시딩 │ │ OpenAI 호환 API │ │ │ │ → :9090 │ └──────────────────┘ └──────────────────┘ └──────────────────────┘

핵심 ① 모델 선택은 환경변수 두 개의 조합

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.shhuggingface_hub.snapshot_download()를 파이썬 인라인으로 호출하되, allow_patterns로 필터를 건다.

# 27B ternary를 받을 때의 패턴 (개념 요약)
allow_patterns = [
    "*-Q2_0.gguf",          # 본체 (ternary 가중치)
    "*mmproj*.gguf",        # 비전 프로젝터 (멀티모달용)
    "*dspark-Q4_1*.gguf",   # 투기적 디코딩용 초안 모델
]

이 필터가 없으면 저장소에 함께 올라간 F16 참조본(47GB짜리)까지 딸려온다. 27B에서만 mmproj와 dspark를 추가로 받는데, 작은 모델들은 비전 타워가 없기 때문이다.

핵심 ③ 바이너리 — 내 GPU를 알아서 찾아낸다

download_binaries.sh가 하는 하드웨어 감지 순서:

nvcc / nvidia-smi 있나? ─── 있음 ──▶ CUDA 버전 파싱 (12.4 / 12.8) │ └─▶ bin/cuda/ │ 없음 ▼ rocminfo 있나? ─── 있음 ──▶ bin/rocm/ │ 없음 ▼ vulkaninfo 있나? ─── 있음 ──▶ bin/vulkan/ │ 없음 ▼ macOS(arm64)인가? ─── 맞음 ──▶ bin/mac/ (Metal) │ 아님 ▼ bin/cpu/ (최후 폴백)

맞는 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" \
    "$@"
플래그의미
-nglGPU에 올릴 레이어 수. bonsai_llama_ngl() 함수가 하드웨어 보고 자동 결정
-fa onFlash Attention 활성화 (메모리·속도 개선)
-c 0컨텍스트 크기 자동 맞춤(--fit). 지원 안 되면 RAM 기반 폴백값으로 재시도
--jinja모델의 Jinja 채팅 템플릿 사용 → OpenAI 스타일 네이티브 tool_calls 가능
--mmproj비전 프로젝터 연결 (이미지 입력)
-mddraft model — 투기적 디코딩용 초안 모델
${VAR:+...}셸 문법. VAR가 비어있지 않을 때만 그 플래그를 붙인다 (조건부 인자 조립)
용어
투기적 디코딩 (Speculative Decoding)
작은 "초안(draft) 모델"이 다음 토큰 여러 개를 빠르게 추측하고, 큰 "본(target) 모델"이 그걸 한 번의 forward로 일괄 검증하는 기법. 맞으면 여러 토큰을 한꺼번에 얻고, 틀리면 그 지점부터 버린다. 출력 결과는 본 모델 단독 실행과 수학적으로 동일(lossless)한데 속도만 빨라진다는 게 핵심.

핵심 ⑤ 하이브리드 어텐션이라는 숨은 난관

백서 Section 5의 주장 중 기술적으로 가장 무거운 부분이다. 베이스가 된 Qwen3.6-27B는 표준 트랜스포머가 아니다.

Qwen3.6-27B 의 64개 레이어 구성 ┌────────────────────────────────────────────────────┐ │ 약 75% Gated-DeltaNet (linear attention) │ │ └ KV 캐시가 자라지 않음 (고정 크기 상태) │ │ │ │ 약 25% Full attention (16개 레이어) │ │ └ 여기만 KV 캐시가 컨텍스트에 비례해 증가 │ └────────────────────────────────────────────────────┘ 결과: KV 캐시 메모리가 순수 트랜스포머의 약 1/4

문제는 기존의 저비트 커널들이 표준 어텐션을 가정하고 짜여 있다는 것이다. 그래서 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
왜 이게 중요한가
SSRF — LLM에게 URL 접근을 주면 생기는 구멍

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 도구로 여러 테이블을 스스로 조인해가며 탐정처럼 원인을 추적하도록 설계된 것이다. 에이전트 데모를 만들 때 "그럴듯한 데이터"만으로는 부족하고 발견할 만한 인과관계를 심어둬야 한다는 좋은 교훈이다.

5디렉토리 구조 해부

각 파일이 무슨 역할인가
Bonsai-demo/ ├── README.md # 퀵스타트, 모델표, 벤치마크, 폴더구조 ├── AGENTS.md # AI 코딩 에이전트가 설치를 도울 때 볼 튜닝 가이드 ├── VISION.md # 이미지 입력 사용법·토큰 비용·OCR 팁 ├── TOOLS.md # 툴콜링 / MCP 서버 연동 ├── KV-CACHE.md # 4비트 KV 캐시 (실험적) ├── SPECULATIVE.md # 투기적 디코딩 DSpark (실험적) ├── OPENWEBUI.md # 에이전틱 데모 가이드 ├── LICENSE # Apache 2.0 ├── pyproject.toml / uv.lock # 파이썬 의존성 (uv 기반) ├── setup.sh / setup.ps1 # ★ 원커맨드 셋업 (425줄) ├── *-whitepaper.pdf (3개) # 27B / 1-bit 8B / ternary 8B 백서 ├── assets/ # 로고 SVG, frontier 벤치마크 그래프 ├── community-benchmarks/ # 커뮤니티 제출 하드웨어별 실측치 │ ├── bonsai/ ternary-bonsai/ │ └── TEMPLATE-llama-cpp.md # 제출 양식 (표준화된 측정 방법) ├── .github/workflows/ # 스모크테스트 / 소스빌드 / em-dash 린트 ├── tests/ # 도구 유닛테스트 (unittest) └── scripts/ ├── common.sh # ★ 공용 함수 — 경로계산·하드웨어감지·색상출력 ├── download_models.sh # HuggingFace → models/ ├── download_binaries.sh # GitHub Release → bin/ ├── run_llama.sh / .ps1 # llama.cpp 1회성 CLI ├── run_mlx.sh # MLX 1회성 CLI (Apple Silicon) ├── mlx_generate.py # └ 실제 MLX 생성 로직 ├── start_llama_server.sh # ★ 상시 서버 :8080 (OpenAI 호환 API) ├── start_mlx_server.sh # MLX 서버 :8081 ├── start_openwebui.sh # ★ 풀스택 :9090 (서버+Jupyter+도구 관리) ├── make_kv_bias.sh # 4비트 KV 캐시용 평균중심화 보정치 계산 ├── build_mac.sh # ┐ ├── build_cpu_linux.sh # │ llama.cpp 소스 빌드 ├── build_cuda_linux.sh # │ (바이너리 대신 직접 빌드할 때) ├── build_cuda_windows.ps1 # ┘ ├── webui-config.json # 기본 MCP 서버 설정 (HF, DeepWiki) └── openwebui/ ├── seed_openwebui.py # 도구·모델설정·시스템프롬프트 시딩 (멱등) ├── make_demo_db.py # 가짜 B2B 매출 DB 생성 (시드 1337) ├── tool_weather.py # open-meteo 날씨 (API키 불필요) ├── tool_web_fetch.py # ★ SSRF 방어 내장 URL 리더 ├── tool_sql.py # ★ 읽기전용 SQL 실행 └── tool_code_interpreter.py # Jupyter 기반 코드 실행 래퍼
구조에서 읽히는 것
문서 파일이 7개 — 코드보다 문서가 먼저다

README 외에 기능별 마크다운이 6개 더 있고, 그중 AGENTS.md사람이 아니라 AI 코딩 에이전트가 읽으라고 쓴 문서다. 설치가 복잡한 프로젝트가 "사용자가 Claude Code한테 시킬 것"을 전제하고 문서를 준비해두는 건 2026년다운 패턴이다.

6학습 포인트 — 기술별 배울 것

이 저장소에서 실제로 가져갈 지식
학습 포인트 01

양자화 스펙을 비판적으로 읽는 법

이 저장소를 통틀어 가장 값어치 있는 한 가지. "Q4_K_M"이라는 이름을 보면 실제 파일 크기 ÷ 파라미터 수를 직접 계산해보는 습관을 들이게 된다. 4비트라 광고된 게 5.2bpw인 이유(임베딩·LM head는 안 줄임)를 이해하면, 앞으로 어떤 양자화 포맷을 봐도 "그래서 무엇을 안 줄였는가"를 먼저 묻게 된다.

실습: 내가 쓰는 GGUF 파일의 실제 bpw를 계산해보고, HuggingFace 모델 카드에 적힌 라벨과 비교해보라.

학습 포인트 02

셸 스크립트로 크로스플랫폼 설치기를 짜는 법

POSIX sh만으로 macOS/Linux/Windows(WSL) + CPU/CUDA/Metal/Vulkan/ROCm 조합을 전부 커버한다. 배울 기법: 기능 감지(feature detection) 우선(OS 이름이 아니라 nvcc 존재 여부로 판단), 폴백 체인, 멱등성(이미 끝난 단계는 건너뛰어 재실행 안전), ${VAR:+...}를 이용한 조건부 인자 조립.

학습 포인트 03

LLM 에이전트 도구의 보안 패턴

LLM에게 도구를 주는 순간 프롬프트 인젝션이 곧 원격 코드 실행 시도가 된다. 이 저장소가 보여주는 방어 목록: SSRF 방지(리다이렉트마다 IP 재검증 + 프록시 무력화), zip bomb 방어(MAX_DECOMPRESSED_BYTES), 호출 횟수 상한(MAX_FETCHES_PER_CHAT), SQL 3중 잠금, Jupyter 커널 격리(전용 작업 디렉토리 + 비밀키 환경변수 unset).

학습 포인트 04

투기적 디코딩의 실제 손익 계산

이론상 항상 이득일 것 같지만 현실은 다르다. CUDA에서는 1.34~1.37배 빨라지는데 Apple Silicon에서는 오히려 손해다(batch=1에서 검증 forward의 오버헤드를 상쇄하지 못함). 최적화 기법이 하드웨어 특성에 따라 부호가 뒤집힌다는 실전 감각을 얻을 수 있다.

학습 포인트 05

하이브리드 어텐션과 KV 캐시 경제학

64개 레이어 중 16개만 KV 캐시가 자라는 구조 → 캐시 메모리 1/4. 긴 컨텍스트에서 진짜 병목이 가중치가 아니라 KV 캐시라는 사실(100K 컨텍스트에서 ternary 27B는 가중치 6.7GB + 캐시 7GB)을 숫자로 체감할 수 있다.

학습 포인트 06

재현 가능한 벤치마크 방법론

community-benchmarks/TEMPLATE-llama-cpp.md가 좋은 예다. llama-benchpp512(prompt processing, 입력 처리 속도)와 tg128(text generation, 출력 생성 속도)를 표준 지표로 고정하고, 하드웨어·백엔드·빌드 커밋까지 함께 적게 한다. "빠르다"를 재현 가능한 숫자로 바꾸는 양식 설계를 배울 수 있다.

7하드웨어 · 시스템 요구사항

내 기계에서 돌아갈지 판단하기

메모리 요구량 (27B 기준, 텍스트 전용, FP16 KV)

모델 / 포맷가중치4K 컨텍스트10K100K
Bonsai-27B (Q1_0)3.53 GB4.8 GB5.2 GB10.8 GB
Bonsai-27B (MLX 1bit)3.92 GB5.5 GB5.9 GB11.4 GB
Ternary-27B (Q2_0)6.66 GB7.8 GB8.1 GB13.7 GB
Ternary-27B (MLX 2bit)7.05 GB8.6 GB8.9 GB14.4 GB
(참고) 27B FP1647.7 GB49 GB49.6 GB55.2 GB

BONSAI_KV4=1로 4비트 KV 캐시를 켜면 100K 컨텍스트에서 ternary가 13.7GB → 9.2GB로 줄어든다.

읽는 법
왜 MLX 쪽이 조금 더 큰가
GGUF는 그룹당 스케일 하나만 저장하는데, MLX 양자화 포맷은 스케일 + 바이어스를 함께 저장하기 때문이다. 같은 모델인데 포맷 차이로 0.4GB 정도 차이가 난다.

기기별 현실적 판단

기기권장이유
iPhone (12GB RAM)1-bit 27B만iOS는 앱당 물리메모리의 약 절반만 허용 → 가용 ~6GB. ternary(5.9~7.2GB)는 안 들어감
MacBook (16GB)ternary 8B 또는 1-bit 27Bternary 27B는 컨텍스트 여유가 빠듯함
MacBook (32GB+)ternary 27B 여유롭게100K 컨텍스트도 가능
NVIDIA GPU 12GB+ternary 27B + DSpark투기적 디코딩이 실제로 이득 나는 유일한 환경
구형 CPU only1.7B / 4B동작은 하지만 27B는 실용 속도 안 나옴

커뮤니티 실측 속도

하드웨어모델측정치
Apple M4 Pro (48GB, Metal)Bonsai-8Btg128 117 tok/s
Apple M4 Pro (48GB, Metal)Bonsai-1.7Btg128 308 tok/s
NVIDIA L40S (48GB, CUDA 12.8)Ternary-27Btg128 70 tok/s → DSpark 적용 87~103 tok/s
AMD Strix Halo (128GB, ROCm)Bonsai-1.7Bpp512 5,001 tok/s (소비자 APU 치고 이례적)
iPhone 17 Pro Max (A19 Pro)1-bit 27B11 tok/s (백서 실측)

OS · 백엔드 지원 매트릭스

OS지원 백엔드
macOSMetal (Apple Silicon) / CPU (Intel Mac)
Linuxx64·arm64 — CPU, CUDA 12.4·12.8, Vulkan, ROCm 7.2
Windowsx64·arm64 — CPU, CUDA, Vulkan, HIP
iOSXCFramework 제공
설치 시 주의
CUDA 소스 빌드에서 OOM

llama.cpp CUDA 빌드는 메모리를 엄청나게 먹는다. 빌드 스크립트가 GPU VRAM이 16GB 미만이면 자동으로 -j2로 병렬도를 낮춘다. 그래도 죽으면 더 낮추거나, 애초에 소스 빌드 대신 사전 빌드 바이너리를 쓰는 게 낫다.

8직접 해볼 수 있는 실습 과제

난이도별 5개
난이도 ★☆☆☆☆

실습 1. 내 GGUF 파일들의 진짜 bpw를 계산하기30분

이미 갖고 있는(또는 HuggingFace에서 파일 크기만 확인 가능한) 양자화 모델들의 파일크기(bit) ÷ 파라미터수를 계산해 표로 만든다. Q4_K_M, Q5_K_M, Q8_0, IQ2_XXS를 같은 모델에 대해 비교하고, 라벨과 실측의 차이를 정리한다.

배우는 것: 양자화 마케팅과 실제의 간극. 이 하나만 해봐도 이 문서의 절반은 소화한 것이다.

난이도 ★★☆☆☆

실습 2. 가장 작은 모델(1.7B)로 전체 파이프라인 돌려보기1~2시간

BONSAI_MODEL=1.7B BONSAI_FAMILY=ternary ./setup.sh로 설치한 뒤 CLI → 서버(:8080) → Open WebUI(:9090) 순으로 세 가지 실행 경로를 다 밟아본다. 27B는 다운로드가 크니 처음엔 1.7B로.

배우는 것: 로컬 LLM의 실행 형태 3종(CLI / OpenAI 호환 서버 / 웹UI)의 차이와 각각의 용도.

난이도 ★★★☆☆

실습 3. 나만의 하드웨어 벤치마크를 PR로 제출하기반나절

community-benchmarks/TEMPLATE-llama-cpp.md 양식대로 llama-bench를 돌려 pp512/tg128을 측정하고, 하드웨어 정보와 함께 PR을 올린다. 실제로 머지되면 오픈소스 기여 이력이 하나 생긴다.

배우는 것: 표준화된 성능 측정 방법론 + 실제 OSS 기여 워크플로. 코드를 안 고치고도 기여할 수 있는 좋은 진입점이다.

난이도 ★★★★☆

실습 4. Open WebUI에 나만의 도구 추가하기1~2일

scripts/openwebui/tool_weather.py를 템플릿 삼아 새 도구를 만든다(예: 사내 위키 검색, 로컬 파일 grep). 그리고 tool_web_fetch.py의 방어 패턴을 참고해 보안 가드를 반드시 함께 넣는다. tests/에 유닛테스트도 추가한다.

배우는 것: 툴콜링 스키마 설계, 에이전트 도구 보안, seed_openwebui.py의 멱등 시딩 패턴.

난이도 ★★★★★

실습 5. 압축 효과를 직접 재현·검증하기1주+

같은 베이스 모델(Qwen 계열)의 FP16 / Q4_K_M / IQ2_XXS / Ternary Bonsai를 모두 준비해, 동일한 평가셋(예: MMLU 서브셋, GSM8K 일부)으로 직접 점수를 재본다. 백서 Table 10의 순위가 내 환경에서도 재현되는지 확인한다.

배우는 것: 벤치마크 재현의 어려움 그 자체. 서빙 스택·샘플링 파라미터·프롬프트 포맷이 조금만 달라도 점수가 흔들린다는 걸 체감하게 된다. "논문 숫자를 그대로 믿지 않는 법"이 이 실습의 진짜 수확이다.

9관련 기술 심화 학습 로드맵

주차별 학습 계획
주차주제구체적 할 일
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) → 양자화 선택이 곧 제품 결정이 되는 지점 이해

10핵심 키워드 사전

이 문서에 나온 용어 총정리
용어
bpwbits per weight. 가중치당 실효 비트수. 모델 용량 ÷ 파라미터 수
Ternary가중치가 {-1, 0, +1} 3값만 갖는 양자화. 이론상 log2(3) ≈ 1.585비트
Binary / 1-bit가중치가 {-1, +1} 2값만 갖는 양자화
g128가중치 128개를 한 그룹으로 묶어 그룹당 FP16 스케일 1개를 공유하는 포맷
GGUFllama.cpp의 모델 파일 포맷. 가중치+토크나이저+메타데이터 단일 파일
Q1_0 / Q2_0Bonsai가 쓰는 GGUF quant 타입. 각각 binary / ternary
safetensorsHuggingFace 표준 텐서 저장 포맷. MLX 경로에서 사용
MLXApple Silicon 전용 ML 프레임워크. 통합 메모리 활용
KV 캐시어텐션의 Key/Value를 재계산 안 하려고 저장해두는 메모리. 컨텍스트 길이에 비례해 증가
Gated-DeltaNetLinear attention 계열 구조. KV 캐시가 자라지 않는 고정 크기 상태를 유지
하이브리드 어텐션linear attention 레이어와 full attention 레이어를 섞은 구조. Qwen3.6-27B가 약 75:25
투기적 디코딩작은 초안 모델이 추측 → 큰 모델이 일괄 검증. 출력은 동일(lossless)한데 속도만 향상
DSparkBonsai가 쓰는 초안 모델. semi-autoregressive 방식으로 K=4 토큰 블록을 병렬 생성
pp512 / tg128llama-bench 표준 지표. 입력 512토큰 처리 속도 / 출력 128토큰 생성 속도
forward-KL두 확률분포의 차이 측정치. 양자화가 출력 분포를 얼마나 왜곡했는지 재는 데 사용
mmprojmultimodal projector. 이미지 인코더 출력을 LLM 임베딩 공간으로 옮기는 어댑터
HQQHalf-Quadratic Quantization. Bonsai가 비전 타워에만 별도로 쓰는 4비트 양자화
SSRFServer-Side Request Forgery. 서버가 공격자 지정 내부 주소로 요청하게 만드는 공격
MCPModel Context Protocol. LLM에 외부 도구/데이터소스를 연결하는 표준 프로토콜
uvAstral이 Rust로 만든 초고속 파이썬 패키지 매니저. pip/venv 대체
멱등성(idempotent)여러 번 실행해도 결과가 같은 성질. 설치 스크립트의 핵심 요구사항

11참고 링크 · 한계와 주의사항

읽기 전에 알아둘 것

이 프로젝트의 한계 (백서가 직접 인정한 것)

한계 01
무손실이 아니다

ternary 94.6%, binary 89.5%. 특히 binary는 10% 이상 잃는다. "Q4_K_XL이 99.9%를 17.6GB로 달성"한다는 사실을 잊으면 안 된다. Bonsai가 이기는 건 17GB를 올릴 수 없는 기기에서일 뿐이다.

한계 02
에이전틱 코딩은 아직 타깃이 아니다

백서 Section 9가 명시한다 — 멀티파일 수정, run-test-repair 루프 같은 코딩 에이전트 작업은 이번 릴리스가 강하게 겨냥한 영역이 아니다. "코딩 특화 Bonsai 27B 변형이 로드맵에 있다"고 되어 있으니, 지금 버전으로 Claude Code를 대체할 생각은 접는 게 좋다.

한계 03
배포 용량이 이론값보다 크다

ternary의 이론 최소는 5.9GB인데 실제 배포는 7.2GB다. 현재 커널이 ternary 값을 2비트 슬롯에 담기 때문이다. 백서는 네이티브 ternary 커널이 "액티브 엔지니어링 타깃"이라고 표현했는데, 이는 아직 완성 안 됐다는 뜻이다.

한계 04
실험적 기능 두 가지는 기본 꺼짐

투기적 디코딩(BONSAI_SPECULATIVE=1)과 4비트 KV 캐시(BONSAI_KV4=1) 모두 문서에 "Experimental"로 표시되어 있고 off-by-default다. 특히 Apple Silicon에서 투기적 디코딩은 오히려 느려진다고 README와 AGENTS.md가 반복 경고한다.

한계 05
MLX 경로는 멀티턴에서 느리다

MLX 백엔드에는 요청 간 프롬프트 캐시가 없어서, 매 턴마다 전체 대화(이미지 토큰 포함)를 다시 프리필한다. AGENTS.md도 멀티턴 사용자에게는 llama.cpp 백엔드를 권장한다.

한계 06
27B 가중치 접근성이 유동적

README와 백서 모두 27B HuggingFace 리포가 비공개일 수 있다고 언급하며 BONSAI_TOKEN 설정 절차를 안내한다. 이 문서 작성 시점 기준으로 지금 바로 27B를 익명 다운로드할 수 있는지는 보장되지 않는다. 8B 이하로 먼저 시작하는 걸 권한다.

보안상 반드시 지킬 것

가드레일
Open WebUI 데모는 인증이 꺼진 채로 뜬다

start_openwebui.shWEBUI_AUTH=false로 구동하고 코드 실행(Jupyter)까지 켜져 있다. 그래서 스크립트가 loopback이 아닌 주소로 바인딩하는 것을 명시적으로 거부한다(BONSAI_ALLOW_REMOTE=1을 강제로 주지 않는 한).

이 가드를 우회해 공용 네트워크에 노출하면, 누구나 당신 기계에서 임의 코드를 실행할 수 있다. 데모는 localhost에서만 쓰자.

정보 검증에 관한 메모

조사 과정 기록
GitHub 웹페이지 README가 낡아 있었다
이 문서를 만들면서 GitHub 웹페이지를 조회했을 때 캐시된 구버전 README가 반환됐다(27B·비전·툴콜링 항목이 통째로 없는 버전). 본문의 모든 기술 내용은 git clone --depth 1로 받은 실제 소스와 저장소에 포함된 백서 PDF를 근거로 작성했다. 별·포크 수는 GitHub API 실시간 조회값(별 1,768 / 포크 170)이다. 벤치마크 수치는 모두 PrismML이 자체 발표한 값이며 제3자 재현 검증은 아직 확인되지 않았다.