RAG를 넘어서: 문서 전체를 당신의 노트북에서 로드하세요
요약
KV 압축 기술을 통해 소비자용 하드웨어에서도 대규모 컨텍스트를 처리할 수 있게 하는 quantcpp 라이브러리를 소개합니다. 6.4배의 KV 압축을 통해 메모리 사용량을 획기적으로 줄이면서도 FP32 수준의 품질을 유지합니다.
핵심 포인트
- 6.4배 KV 압축으로 16GB Mac에서 128K 컨텍스트 구현
- 의존성 없는 17,600줄의 C 언어 기반 경량 라이브러리
- Ollama 스타일의 CLI 및 OpenAI 호환 API 지원
- FP32 품질을 유지하며 메모리 사용량 3배 절감 가능
RAG (Retrieval-Augmented Generation)를 넘어서: 문서 전체를 당신의 노트북에서 로드하세요.
청킹 (Chunking)은 작은 컨텍스트 윈도우 (Context Window)를 해결하기 위한 임시방편이었습니다. 우리는 그것을 불필요하게 만들었습니다.
6.4배의 KV 압축 (KV compression)을 통해 소비자용 하드웨어에서도 문서 전체를 이해할 수 있게 합니다.
pip install quantcpp
— 17,600줄의 C 언어, 의존성 제로 (zero dependencies).
12K 토큰에서 10/10 RLV가 절벽을 넘음 |
7/7 vs 0/7 RAG 측정 결과 |
6.4배 압축 + 3% PPL (Perplexity) |
16GB Mac에서 128K 컨텍스트 (Context) |
17.6K LOC (Lines of Code), 의존성 없음 |
Ollama 스타일의 CLI (v0.12.0+):
pip install quantcpp
quantcpp pull qwen3 # Qwen3-4B Q4_K_M 다운로드 (~2.5 GB)
quantcpp run qwen3 # 대화형 채팅
...
권장 기본값: Qwen3-4B (4B 파라미터, MMLU 73, M3에서 4.5 tok/s). 속도와 품질 모두 최상 — Q4 NEON 융합 도트 경로 (fused dot path) 덕분에 더 큰 어휘 집합 (vocab)에도 불구하고 Phi-3.5-mini보다 2.4배 빠릅니다. 기타 별칭: phi3.5, smollm2, llama3.2:1b. 첫 번째 run / serve 시 자동으로 다운로드됩니다.
serve 서브 명령은 8080 포트에서 POST /v1/chat/completions (OpenAI 호환)를 노출합니다 — 클라이언트는 SSE 스트리밍을 위해 "stream": true를 전달하거나, 단일 JSON 응답을 위해 이를 생략할 수 있습니다. 내장된 quantcpp client는 두 모드(기본값: 스트리밍, 단일 응답은 --no-stream)를 모두 지원합니다.
원샷 질문 (One-shot question):
quantcpp run qwen3 "What is gravity?"
Python API (3줄):
from quantcpp import Model
m = Model.from_pretrained("Qwen3-4B")
print(m.ask("What is gravity?"))
첫 사용 시 다운로드되며 ~/.cache/quantcpp/에 캐시됩니다. API 키도, GPU도 필요 없습니다. 아키텍처 지원 매트릭스와 모델 선택 가이드는 docs/supported_models.md를 참조하세요. 브라우저에서 시도해 보세요 → · 대화형 가이드 →
128개의 FP32 토큰 + 그 외 모든 것은 4비트 = 컨텍스트 길이에 관계없이 FP32 품질을 제공합니다.
Llama 3.2 3B, 3970 토큰에서 측정됨 (k128 = 3.2% FP32):
| 설정 (Configuration) | PPL | vs FP32 | KV 메모리 (32K) | 속도 |
|---|---|---|---|---|
| FP32 (기준점) | 19.41 | — | 7.17 GB | 기준점 |
| 4-bit + 점진적 (progressive) | 19.39 | -0.1% | 2.33 GB | +13% |
| 4-bit flat | 20.02 | +3.1% | 2.30 GB | +13% |
m = Model("model.gguf", progressive=True) # ← FP32 품질, 메모리 3배 절감, 13% 더 빠름
작동 원리: Transformer 어텐션 (Attention)은 가중치의 약 70%를 마지막 약 128개의 토큰 (tokens)에 집중시킵니다. 나머지 모든 것을 압축하면서 이 토큰들을 전체 정밀도 (full precision)로 유지하는 것은 저장 정밀도를 정보 가치와 일치시키는 것이며, 이는 속도-왜곡 이론 (rate-distortion theory) 측면에서 거의 최적에 가깝습니다.
컨텍스트 길이 불변성 (Context-length invariant): 동일한 128-토큰 윈도우 (window)가 4K, 32K 또는 128K에서도 작동합니다. 128K 컨텍스트 (context)에서는 토큰의 0.1%만이 FP32이며, 사실상 FP32 품질을 가진 올-4비트 (all-4-bit) 방식과 같습니다.
6.4배 KV 압축을 적용한 Llama 3.2 3B. M1 Pro 16GB에서 측정한 실제 RSS:
| 컨텍스트 (Context) | FP32 KV | quant.cpp 6.4x | 절감량 (Savings) | 속도 (Speed) |
|---|---|---|---|---|
| 16K | 8.5 GB | 6.5 GB | -2.0 GB | 6.6 tok/s |
| 32K | 9.6 GB | 8.2 GB | -1.4 GB | 4.9 tok/s |
| 65K | — | 8.5 GB | — | 1.6 tok/s |
| 128K | OOM | 9.5 GB | — | 0.8 tok/s |
3B 모델로 128K 컨텍스트를 9.5 GB 내에서 구현했습니다. 생성 속도는 FP32와 동일합니다 (16K 기준 6.6 vs 6.5 tok/s).
m = Model("llama-3b.gguf", aggressive=True, context_length=131072) # 9.5 GB로 128K 구현
청킹 (Chunking) RAG는 작은 컨텍스트 윈도우 (context windows)를 위한 임시방편이었습니다. 그 임시방편이 교리 (dogma)가 되었습니다. 이제 컨텍스트 윈도우가 충분히 커졌기 때문에 더 이상 임시방편이 필요하지 않습니다.
Llama 3.2 3B Q8_0를 사용한 직접 비교: 5개 섹션의 합성 문서, 7개 질문 (4개 단일 홉 (single-hop), 3개 다중 홉 (multi-hop)):
| 방법 (Method) | 정확도 (Accuracy) | 실패 시 동작 (Behavior on failure) |
|---|---|---|
| Chunk-RAG (잘못된 섹션 검색됨) | 0/7 | 모든 답변을 환각 (Hallucinated) |
| 전체 문서 (Full Document, FP32 KV) | 7/7 | 정확함 |
| 전체 문서 (Full Document, 6.4× 압축된 KV) | 7/7 | 정확함 — 품질 저하 없음 |
Chunk-RAG의 숨겨진 실패 모드
Chunk-RAG가 잘못된 섹션을 검색할 때, 모델은 "모르겠습니다"라고 말하지 않습니다. 대신 그럴듯하게 들리는 거짓말을 생성합니다:
| 질문 | Chunk-RAG (잘못된 섹션) | 진실 |
|---|---|---|
| "CTO는 누구인가요?" | "John Smith" ❌ | Maria Santos |
| ... |
이것은 아무도 측정하지 않는 운영상의 리스크입니다: 검색 실패 시 발생하는 침묵하는 환각 (silent hallucination on retrieval failure). 모니터링 시스템은 100% 가동 시간(uptime)을 보여주지만, 사용자는 잘못된 답변을 받게 됩니다.
**6.4배의 KV 압축 (KV compression)**을 통해, 5개 섹션으로 구성된 전체 문서를 16GB Mac의 컨텍스트(context) 안에 담을 수 있습니다. 모델은 섹션 간의 정보를 연결해야 하는 멀티홉 추론 (multi-hop reasoning)을 포함하여 7개의 질문 모두에 정확히 답변합니다:
"어떤 리스크가 성장 지역에 영향을 미칩니까?" → 환율 변동 (섹션 3 "Asia growth"와 섹션 5 "Asia currency risk"를 연결해야 함)
Chunk-RAG는 이를 수행할 수 없습니다. 각 청크 (chunk)가 독립적으로 검색되기 때문입니다.
이것은 "RAG가 끝났다"는 뜻이 아닙니다. 10만 개 이상의 문서 코퍼스 (corpora)를 처리하는 데 있어 RAG는 여전히 유일한 방법입니다. 하지만:
RAG는 결정합니다 (검색 문제) 어떤 문서를 찾아볼 것인가
Long-context는 결정합니다 (추론 문제) 그것들을 얼마나 깊게 이해할 것인가
문제는 두 가지 모두에 동일한 도구를 사용했다는 점이었습니다. 해결책은 각 도구를 그에 적합한 용도로 사용하는 것입니다.
5분 안에 재현하기: bench/document_level_rag_test.sh
전체 벤치마크 보고서: bench/results/document_level_rag_breakthrough.md
매니페스토 (Manifesto): docs/beyond-rag-manifesto.md
솔직한 고지: v1은 단일 3B 모델을 사용하여 7개의 질문이 포함된 합성된 5개 섹션 문서입니다. 저희는 이것이 LongBench라고 주장하는 것이 아닙니다. Chunk-RAG가 숨겨왔던 실패 모드 (failure mode)에 대해 대화를 시작하기에 충분하다는 점을 주장하는 것입니다.
v2 업데이트 — Working Memory Cliff (2026-04-11):
우리는 v1 결과에 이어, 256–2048의 컨텍스트 길이(context length)에서 1B 및 3B 모델을 대상으로 204회의 NIAH(Needle In A Haystack) 테스트를 수행했으며, 6회의 FP32 가중치(weights) 대조군 테스트를 추가했습니다. 두 모델 모두 명목상 128K인 컨텍스트 창(context window)의 1% 미만 지점에서 급격한 절벽(cliff) 현상을 보였습니다 (계단 함수(step function) 형태로, 1B Q8 모델은 512–1024에서, 3B Q4 모델은 1024–1280에서 발생). 6.4배의 KV 압축(KV compression)은 20개 셀 중 18개에서 FP32 베이스라인과 비트 단위로 동일하므로, 이 절벽 현상은 모델 자체의 특성이지 KV의 특성이나 가중치 양자화(weight-quantization)로 인한 인위적인 결과가 아닙니다. 솔직하게 재정의하자면: 'Beyond RAG'는 모델의 유효 작업 기억(effective working memory) 안에 들어오는 문서에 대해서만 작동하며, 이 유효 작업 기억은 명목상 컨텍스트 창보다 2~3 자릿수(orders of magnitude) 더 작습니다.
전체 기술 보고서: docs/paper/working-memory-cliff.md
HF 블로그 포스트 초안: docs/paper/hf-blog-draft.md
v3 업데이트 — RLV로 절벽 넘기 (2026-04-14):
만약 이 절벽이 실재한다면, 해결책은 단 한 번의 LLM 호출로 전체 문서를 작업 기억(working memory)에 담으라고 요구하는 것을 멈추는 것입니다. RLV (Read-Locate-Verify)는 gist → locate → lookup → verify → research로 이어지는 5단계 파이프라인으로, 각 단계는 약 1K 토큰의 절벽 아래를 유지하면서도 문서는 임의의 길이까지 다룰 수 있습니다. 12K 토큰의 wikitext(Llama 3.2 3B Q4의 절벽보다 약 10배 긴 길이)에서 RLV는 10/10점을 기록한 반면, verify-only 방식은 8/10점, long-context-only 방식은 1/10점을 기록했습니다. 핵심 비결은 BM25 + Reciprocal Rank Fusion이 위치를 찾아내고(locating), LLM은 오직 동점자 처리(tiebreaker) 역할만 수행한다는 점입니다. 3B 모델과 동일한 16GB Mac에서 실행되며, RAG 인덱스나 임베딩(embeddings)이 필요하지 않습니다.
bench/rlv/ · docs/phase3_rlv_challenge.md
v3.1 처리량(throughput) 업데이트 (2026-04-15):
집중적인 성능 테스트(Q4_K/Q5_K int8 fused dot, ARMv8.2 vdotq_s32, 가중치 행 프리페치(weight-row prefetch), 2-row ILP, P-core 스레드 기본 설정)를 통해 M1 Pro 환경의 모델 라인업 전반에서 CPU 생성 처리량이 +58%에서 +141%까지 향상되었습니다. Phi-3.5-mini Q8_0는 5.4 → 13.0 tok/s로 급증했습니다 (현재 llama.cpp의 순수 CPU 속도의 71% 수준). 우리는 여전히 llama.cpp의 성숙한 Metal 커널(kernels)보다 3~6배 뒤처져 있으며, 이것이 우리가 메워야 할 다음 격차입니다.
전체 수치 및 재현 방법: bench/results/2026-04-15_throughput_vs_llamacpp.md
v3.2 배치 전처리(batched prefill) (2026-04-16): 프롬프트 전처리는 llama.cpp 대비 가장 큰 격차를 보였습니다 (40~50배 느림). 새로운 tq_forward_batch 경로는 Apple AMX를 통해 배치 행렬-행렬 곱셈(batched matrix-matrix matmul)을 사용합니다 (cblas_sgemm-에서 영감을 받아, 1.2 TFLOPS). 이제 모든 지원 아키텍처(Llama 계열, FP32 KV 및 기본 turbo_kv_4b KV 압축 모드 모두)에서 기본으로 활성화됩니다. 약 250 토큰 프롬프트가 있는 Llama-3.2-1B Q8의 경우: 42.7초 → 5.9초로 엔드투엔드(기본 KV 압축을 사용했을 때 총 7.2배 개선). 출력은 토큰별 기준선과 비트 단위로 동일합니다. 커밋 ed4b087, 672fea2, f4934e9에 추가되었으며, 양자화된 K 캐시 쓰기 지원도 포함됩니다.
v3.21 ★★★ Qwen3.6-35B 117 토큰 클리프(cliff) 극복: MoE 소프트맥스 온도 조정 — 이제 qwen35moe에 기본 적용됨 (2026-04-22): 하나의 노브인 TQ_MOE_ROUTE_TEMP=2.0이 Qwen3.6-35B-A3B가 40회 이상의 이전 디버그 라운드에서 117 토큰에 머물며 반복하던
사실 관계 조사(factual probe)가 정확해졌으나, 약 150 토큰 이후의 꼬리 부분 품질(Tail quality)은 여전히 저하됩니다. 급격한 성능 저하(hard cliff) 현상은 해결되었지만, 에세이 전체 길이의 생성(full essay-length generation)에는 여전히 양자화 노이즈 마진(quantization noise margins)이 존재합니다. 선택 사항(Opt-in)이며, 기본값은 변경되지 않았습니다. 권장 설정: TQ_MOE_ROUTE_TEMP=2.0 ./build/quant Qwen3.6-35B-Q5_K_M.gguf -p "..." -n 200 --rep-penalty 1.3
. 전체 보고서: bench/results/2026-04-22_moe_temp_cliff_break.md
. v0.28.0: docs/RELEASE_NOTES.md
.
v3.20 ★★ BPE 인코딩/디코딩 (encode/decode) UTF-8 수정 — 국제 텍스트의 무음 품질 재앙 해결 (2026-04-21): encode_byte_to_bpe_char 및 decode_bpe_token에서 발생한 두 가지 대칭적인 버그가 모든 Llama-3 / Qwen3 계열 모델에서 비-ASCII 문자(액센트, CJK, 키릴 문자, 바이트 폴백 이모지)를 포함하는 모든 프롬프트와 출력을 조용히 손상시키고 있었습니다.
- 인코딩(Encode): GPT-2 직접 바이트 코드포인트(direct-byte codepoints)에 대해 0x80 이상의 원시 바이트(raw bytes, 유효하지 않은 UTF-8)를 방출하여
str_lookup이 일치하지 않게 되었고, 문자들이 잘못된 낮은 ID 토큰으로 조용히 폴백(fallback)되었습니다. - 디코딩(Decode): 표현하고자 하는 원시 바이트 대신 U+0080-U+00FF 코드포인트의 UTF-8 인코딩을 방출하여, 출력이 이중 인코딩되었습니다 ("café" → "café").
토큰 수준의 HF(Hugging Face) 일치성(parity)을 확인했습니다: café / naïve / 日本語 / привет 모두 이제 Qwen3에서 HF AutoTokenizer와 바이트 단위로 동일하게 토큰화됩니다. 새로운 tools/refparity/ 프레임워크의 A/B 출력 차이(diff)를 통해 발견되었습니다. 또한 quant.h 단일 헤더에도 동기화되었습니다. 향후 리팩토링 시 오류를 명확히 확인할 수 있도록 scripts/test_tokenizer.sh 피스처(fixtures)를 추가했습니다. 범위: GPT-2 스타일의 바이트 수준 BPE (Llama-3.x, Qwen2.5/3.x/3.5/3.6); Gemma/Phi-3의 SentencePiece 경로는 영향을 받지 않았습니다. 회귀 테스트(Regression) 15/15 및 토크나이저 8/8 통과(PASS). v0.27.0.
16GB Mac에서의 실용적인 Qwen3.6-35B 레시피: 최상의 장문 일관성(long-form coherence)을 위해 Qwen3.6-35B-A3B-UD-Q5_K_M.gguf를 --rep-penalty 1.3과 함께 사용하세요. "Once upon a time in a faraway land" 문구로 측정 시 (-n 200, T=0): 기본 설정은 117 토큰에서 반복 루프에 빠지지만, Q5_K_M + rep-penalty 조합은 끝부분에서만 완만한 저하를 보이며 200 토큰 예산을 모두 소모합니다. 35B DeltaNet 드리프트(drift)는 여전히 미해결된 아키텍처 조사 과제로 남아 있으며, 이것이 현재 사용자에게 제공되는 최선의 설정입니다.
v3.19 ★ DeltaNet L2-norm 공식이 ggml과 일치함 — Qwen3.6 일관성(coherence) +36% (2026-04-21): R26의 "eps fix"는 진단은 정확했으나 공식(formulation)이 틀렸습니다. 우리는 1/sqrt(ss + eps)를 사용했습니다.
하지만 llama.cpp의 ggml_l2_norm은 1/max(sqrt(ss), eps)를 사용합니다.
— 입력값이 0에 가까울 경우 이 둘은 3자릿수(1e3 vs 1e6)만큼 차이가 납니다. 30개 이상의 DeltaNet 레이어와 위치(position)를 거치면서, 체계적인 K/Q 언더스케일링(under-scaling)이 누적되어 디코드 길이(decode-length) 저하로 이어집니다. 해결책: ggml과 정확히 일치시킴. Qwen3.6-35B IQ4_XS 자동 직렬화(auto-serial) "300단어 에세이 작성" 테스트 측정 결과: 117 → 160 토큰(+36%), 드리프트(drift) 발생 전 일관된 콘텐츠가 45 → 110 토큰으로 증가. 우리의 l2_normalize와 refs/llama.cpp/ggml/src/ggml-cpu/ops.cpp::ggml_compute_forward_l2_norm_f32를 직접 비교(diff)하여 발견했습니다. 15/15 회귀 테스트 통과(PASS). v0.26.0.
v3.18 Qwen3.6 자동 직렬화 품질 모드 — 결정론적(determinism) 동작 + 더 긴 일관성 (2026-04-20): 발견 사항: Qwen3.6-35B 멀티스레드 행렬 곱셈(matmul)은 T=0에서 비결정론적(non-deterministic)입니다 (동일한 프롬프트로 두 번 실행 시 결과가 다름). 병렬 FP 리덕션(reduction) 순서의 변동성이 30개의 MoE 레이어와 위치 피드백을 거치며 누적되어 top-1 argmax가 뒤바뀝니다. 해결책: qwen35moe+DeltaNet 하이브리드를 자동 감지하고 -j 1을 강제 적용합니다. 수정 전: 실행 시마다 반복되는 내용이 달라지며 60-70 토큰에서 품질이 저하됨. 수정 후: 결정론적으로 동작하며, 일관된 윈도우(coherent window)가 약 95 토큰까지 확장됨. 비용: 디코드 속도가 약 2-3배 느려짐 (8 t/s 대비 3 t/s). 제외 방법: TQ_NO_AUTO_SERIAL=1
AI 자동 생성 콘텐츠
본 콘텐츠는 GitHub ML Hardware의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기