GITHUB 레포 딥다이브 · 2026-08-04 · SQLITEAI/WASTE · 982GB 모델을 64GB 노트북에서 돌리는, 디스크를 "주 메모리"로 쓰는 임베더블 C 추론 엔진

waste 딥다이브
"가중치를 RAM에 다 올리지 말고, 디스크에서 흘려보내라" : 2.78조 모델을 노트북에 앉히는 스트리밍 엔진

sqliteai/waste(WASTE = Weight-Aware Streaming Tensor Engine, 가중치 인식 스트리밍 텐서 엔진)는 외부 런타임 의존성이 하나도 없는 C 추론(inference) 엔진이다. 모델의 공통 부분(트렁크)만 RAM에 두고, 토큰마다 필요한 전문가(expert) 가중치만 디스크에서 직접 흘려 읽으며, 남은 RAM은 "얼마나 자주 디스크를 다시 읽을지"를 줄여 주는 한도가 걸린 전문가 캐시(bounded expert cache)로 쓴다.

목표는 명확하다 — Kimi K3 같은 프론티어급 초거대 MoE 모델을 소비자용 하드웨어에서 돌리기. 실제로 2.78조(2.78T) 파라미터짜리 Kimi K3 전체가, 64GB 맥북 프로에서 초당 약 0.6토큰으로 돌아간다. 증류·가지치기한 축소판이 아니라 온전한 모델이다. 공개 가중치는 1.42TB, 이를 변환한 WASTE 컨테이너는 982GB인데, 이걸 여는 데 필요한 최소 RAM은 29.06GB뿐이다.

같은 폴더의 kimi-k3-in-c가 "초거대 모델을 노트북에서 돌리는 법"을 교보재로 증명했다면, waste는 그 아이디어를 제품 수준으로 끌어올린 판본이다 — 아무 프로그램에나 링크되는 임베더블 라이브러리 + 9개 명령의 CLI + OpenAI 호환 서버 + 비전(멀티모달) + 리눅스 cgroup 인식 예산기까지 갖췄다. "사람이 아이디어·가설·우선순위·테스트·결정을 내리고, 코드는 LLM(Opus 5)이 쓴다"는 개발 방식마저 README에 대놓고 적어 두었다.

(저장소 sqliteai/waste · 라이선스 Apache-2.0(영구 오픈소스 선언) · 언어 C11(엔진 코어 약 10,600줄) + 변환용 Python · 버전 v0.6.3(2026-08-03) · 컨테이너 포맷 v0 · 저작권 SQLite Cloud, Inc. · 첫 공개 2026-07-28 · ★ 약 1,530 · 플랫폼 macOS · Linux · Windows(MinGW) · TrendShift Daily #16)
목차
  1. 한 줄 정체
  2. 왜 주목받는가 — kimi-k3-in-c가 있는데도 또 뜬 이유
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — 트렁크·전문가·캐시, 그리고 "페이징 절벽"
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 로드맵 (주차별)
  10. 핵심 키워드 사전
  11. 참고 링크

1한 줄 정체

이 프로젝트가 정확히 무엇인지 한 문장으로

waste는 "디스크에 982GB로 저장된 2.78조 파라미터 모델 Kimi K3를, 대부분의 가중치를 RAM에 올리지 않고 디스크에서 스트리밍하며 추론해 내는, 의존성 없는 임베더블 C 추론 엔진"이다. 핵심 발상은 한 줄로 압축된다 — "모델의 무게가 RAM이 아니라 빠른 저장장치(NVMe SSD)에 살게 하고, 남는 RAM은 캐시로 쓴다." 새 모델을 학습시키지도, 모델을 손상시켜(양자화로 뭉개) 억지로 우겨넣지도 않는다. 이미 공개된 거대 모델을, 가진 RAM이 모델보다 훨씬 작아도 온전히 실행하는 "실행 엔진" 그 자체가 목적이자 결과물이다.

한 장의 비유

"작은 주방 조리대 + 냉장 창고를 오가는 배달원 한 명으로, 수천 가지 재료의 레스토랑을 굴리기"

어떤 레스토랑의 냉장 창고(디스크, 982GB)엔 재료가 수천 종 있다. 흔한 방식은 모든 재료를 조리대(RAM) 위에 다 꺼내 두는 것이다 — 그러려면 조리대가 수백 개(수백 GB RAM) 필요하다.

waste는 다르게 한다. 조리대엔 모든 요리에 공통으로 쓰는 기본 재료(트렁크)만 상시 올려 둔다. 그리고 요리 하나(토큰 하나)마다 그 요리에만 필요한 희귀 재료(전문가) 몇 가지를 창고에서 그때그때 꺼내 온다(스트리밍). 방금 쓴 희귀 재료는 작은 보조 냉장고(전문가 캐시)에 잠깐 넣어 둬서, 또 필요하면 창고까지 안 가도 되게 한다.

여기 반전이 있다. 보조 냉장고를 지나치게 크게 만들면 오히려 요리가 느려진다 — 냉장고가 주방을 다 차지해 정작 조리할 공간이 없어지고(=페이징), "냉장고에서 꺼내기"가 "창고까지 다녀오기"보다 더 느려진다. 그래서 waste의 유일하면서도 가장 중요한 손잡이는 "보조 냉장고를 딱 알맞게 잡는 것"이다.

용어
추론(inference) 엔진 · 임베더블(embeddable)
추론 엔진은 학습이 끝난 모델에 프롬프트를 넣어 다음 토큰을 예측·생성하는 실행기다(훈련의 반대 방향). 임베더블은 이 엔진이 독립 실행 프로그램이면서 동시에 "아무 다른 프로그램에나 갖다 붙일 수 있는 라이브러리"라는 뜻이다. waste의 공개 API는 헤더 파일 하나(src/waste.h)로, 전역 상태가 없어 한 프로세스에서 여러 모델을 동시에 열 수 있고, 에러를 화면에 찍지 않고 반환하며, 절대 스스로 exit()하지 않는다. 즉 llama.cpp처럼 내 앱에 심어 쓸 수 있는 엔진이다.
용어
MoE (Mixture of Experts, 전문가 혼합) · "활성 파라미터"
모델을 수백 개의 작은 신경망 조각("전문가")으로 나눠 두고 토큰마다 그중 소수만 골라 쓰는 구조. Kimi K3는 층마다 896명의 전문가 중 16명(top-16)만 뽑아 쓴다. 그래서 전체 파라미터는 2.78조로 거대하지만, 한 토큰이 실제로 건드리는 파라미터(활성 파라미터)는 전체의 약 4%뿐이다. 이 "전체는 크지만 매번 쓰는 건 일부"라는 성질이, 안 쓰는 전문가를 굳이 RAM에 안 올려도 되는 스트리밍의 근거가 된다. waste의 모든 설계는 이 4%를 어떻게 디스크에서 제때 실어 나르느냐로 귀결된다.

왜 이게 어려운 문제인지 숫자 몇 개로 감을 잡자.

┌──────────────────────────────────────────────────────────────────┐ │ Kimi K3 (2.78조 파라미터) 를 어떻게 노트북에 앉히나? │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 공개 가중치 원본 1.42 TB │ │ WASTE 컨테이너로 변환 982 GB ← SSD 1TB 를 거의 다 먹는다 │ │ 그냥 RAM 에 다 올리면 수백 GB ← 서버 클러스터급 │ │ │ │ ─────────── waste 가 실제로 요구하는 것 ─────────── │ │ │ │ K3 를 여는 최소 RAM 29.06 GB ← 4K 문맥 기준 하한선 │ │ 권장 RAM 64 GB ← 하이엔드 노트북에도 있다 │ │ 속도 0.45~0.62 tok/s (증류 아닌 온전한 모델) │ │ 필요 GPU 0 개 ← 현재 CPU 경로가 가장 빠름 │ │ │ └──────────────────────────────────────────────────────────────────┘

수백 GB가 필요할 일을 29GB로 여는 것 — 그 격차를 정확도를 한 톨도 깎지 않고(출력이 캐시에서 왔든 디스크에서 왔든 비트 단위로 동일) 메운 방법을 설명하는 것이 이 레포의 전부다. 대가는 오직 하나, 속도다.

2왜 주목받는가 — kimi-k3-in-c가 있는데도 또 뜬 이유

"디스크에서 K3 돌리기" 레포가 여럿인데, 이게 굳이 상위에 오른 차별점

첫째, 헤드라인이 여전히 강렬하다. "2.78조 파라미터. 64GB 노트북. 온전한 모델." — 이 조합은 "큰 모델엔 큰 하드웨어가 필요하다"는 상식을 정면으로 반박한다. 게다가 waste는 그 위업을 장난감이 아니라 제품 뼈대 위에서 해낸다는 점이 다르다. 같은 아이디어의 교보재판(FareedKhan-dev/kimi-k3-in-c)이 "어떻게 가능한가"를 보여 줬다면, waste는 "이걸 실제로 쓸 수 있는 물건으로 만들면 무엇이 필요한가"에 답한다.

비유

같은 원리, 다른 완성도. 라이트 형제의 첫 비행기와, 실제로 승객을 태우는 여객기는 "공기보다 무거운 물체를 날린다"는 원리는 같다. 하지만 여객기엔 좌석·계기판·정비 매뉴얼·안전 점검이 붙는다. kimi-k3-in-c가 "디스크 스트리밍으로 K3가 난다"를 증명한 첫 비행이라면, waste는 라이브러리 API·CLI·OpenAI 서버·비전·cgroup 안전장치·회귀 테스트를 붙여 "탈 수 있게" 만든 판본이다. 그래서 둘 다 트렌딩에 오른다 — 관심사가 다르니까.

둘째, "정확도가 아니라 시간을 판다"는 트레이드오프가 우아하다. 대부분의 경량화는 모델을 손상시켜(공격적 양자화·가지치기) 억지로 우겨넣는다. waste의 핵심 불변식은 정반대다 — 전문가가 RAM 캐시에서 왔든 디스크에서 갓 읽혔든, 출력은 비트 단위로 똑같다. 바이트를 "어디에 둘 것인가(RAM vs 디스크)"만 조절할 뿐, 계산 결과는 건드리지 않는다. 실제로 모든 층을 PyTorch 레퍼런스와 대조해 최종 로짓이 3.6e-06 이내, 비전 타워는 2.3e-06 이내로 일치함을 검증해 둔다.

셋째, "메모리를 더 주면 항상 빨라진다"는 직관을 데이터로 깨뜨린다. waste가 가르치는 가장 반직관적인 교훈은 "페이징 절벽(paging cliff)"이다. 전문가 캐시를 키우면 히트율은 계속 오르지만, 어느 선을 넘으면 처리량이 8배까지 폭락한다. 캐시가 커져 OS가 메모리 압박을 받으면 "캐시 히트"가 사실은 "디스크에서 다시 페이지를 불러오는 페이지 폴트"가 되기 때문이다. 이 현상을 4개 캐시 크기에서 한 프로세스로 측정해 표로 박아 뒀다(4장 참고). 이런 "실패 모드까지 정직하게 공개"가 신뢰를 만든다.

넷째, 가중치 없이도 정확성을 재현할 수 있다. 982GB를 안 받아도 make check 한 줄이면 작은 합성 컨테이너로 엔진 전체를 검증한다(변환용 Python도 필요 없다). 배우는 사람이 거대 파일 없이 코드의 정확성을 직접 확인할 수 있다는 건 교육 자료로서 결정적이다.

이 레포의 진짜 강점
"불가능해 보이는 위업" + "제품급 완성도" + "측정으로 뒷받침된 정직함"

waste는 화려한 숫자만 던지지 않는다. 각 수치가 어떤 하드웨어·컨테이너·커밋에서 측정됐는지까지 명시하고, 불안정한 값은 범위로 적으며, 나중에 틀린 것으로 밝혀진 측정도 지우지 않고 기록으로 남긴다(docs/LEARNED.md). "실패한 아이디어와 부정적 결과를 일부러 보존한다"는 문화는, 최적화를 제안하기 전에 반드시 읽으라고 안내될 정도다. 위대한 카피 뒤에 견고한 엔지니어링과 정직함이 같이 있는, 흔치 않은 물건이다.

과장 주의 (정직한 경계)
"64GB 노트북에서 2.78조 모델"은 사실이지만, 여전히 실용 속도는 아니다

이 위업의 대가는 속도다. K3는 초당 0.45~0.62토큰 — 한 문장 뽑는 데 수십 초가 걸린다. 콜드 상태의 K3 토큰 하나는 디스크에서 약 17GB의 전문가를 읽어야 하고, 그래서 저장장치 속도가 전부다(내장 SSD 12.78GB/s vs 테스트한 USB 외장 0.94GB/s). 반드시 컨테이너를 내장 NVMe에 둬야 하며, USB 외장에 두면 실용 불가다. 또한 포맷·API가 아직 고정(freeze)되지 않았고, 릴리스 전 대규모 QA를 돌리지만 불안정성이 얼마든지 있을 수 있다고 스스로 밝힌다. "지금 당장 내 노트북에서 K3를 실무에"가 아니라 "로컬 추론을 저장장치로 어디까지 밀 수 있는지 탐구하는 프로젝트"가 올바른 이해다.

3기술 스택 전체 지도

"추론 경로엔 의존성 0" — 컴파일러·libc·pthreads, 그리고 OS 시스템콜 몇 개가 전부

waste 스택의 첫 번째 놀라움은 추론 경로가 거의 아무것도 안 쓴다는 점이다. 딥러닝 추론이라면 으레 딸려 오는 CUDA·cuDNN·BLAS·PyTorch가 전부 없다. 행렬 곱 커널마저 손으로 짰다. Python·PyTorch·safetensors는 모델 변환과 검증에만 쓰이고, 추론에는 절대 관여하지 않는다. 이 "런타임 의존성 없음"이 이식성(macOS·Linux·Windows)과 임베더블성의 근원이다.

엔진 코어 (src/ · 순수 C11)

영역무엇을 쓰나역할
언어C11 (-std=gnu11, POSIX 확장)엔진 전체. 추론 경로는 libc + pthreads에만 의존
병렬화pthreads (OpenMP 아님)행렬 곱·리더 스레드. 전문가를 미리 읽어 오는 I/O 스레드도 여기
SIMDAVX2+FMA · AVX-512 · NEON (런타임 디스패치)CPU 한 명령으로 float 여러 개 동시 곱셈-누산. 빌드시 3개 다 넣고 실행시 선택
디스크 I/Opread · O_DIRECT(Linux) · F_NOCACHE(macOS) · FILE_FLAG_NO_BUFFERING(Win)OS 페이지 캐시를 건너뛴 정렬 직접 읽기 — 스트리밍의 핵심
메모리posix_memalign · cgroup v2 (memory.max/high) 파싱4KiB 정렬 버퍼. 컨테이너 안에서 실제 허용 RAM을 읽어 예산 산정
무결성CRC32 (crc32.c, 전문가 레코드 헤더)디스크에서 읽은 각 전문가 레코드의 손상 검증
vendoredthird_party/stb_image.h (비전 입력 디코딩)유일하게 끌어온 외부 헤더 1개(이미지 로딩). 나머지는 자체 구현
선택 백엔드metal.m (Metal 스텁), CUDA/BLAS 플래그현재는 CPU+SIMD 경로가 가장 빠름. GPU 백엔드는 탐색 중
용어
O_DIRECT / F_NOCACHE · 왜 일부러 OS 캐시를 끄나
파일을 읽으면 보통 OS는 방금 읽은 내용을 페이지 캐시(RAM)에 남겨 둔다. 그런데 작은 테스트 컨테이너(17GB)를 64GB 머신에서 돌리면, OS가 파일 전체를 캐시에 담아 버려서 waste가 측정하는 "전문가 캐시 히트율"이 허구가 된다 — 실제 K3(982GB)에선 그런 공짜 캐시가 불가능하니까. 그래서 waste는 세 OS 모두에서 페이지 캐시를 우회해 "디스크→내 버퍼"로 곧장 읽는다. 자기 캐시 정책이 진짜로 얼마나 좋은지 정직하게 재려는 측정 규율이다.

변환·검증 도구 (tools/ · Python, 추론엔 불필요)

엔진은 C지만 주변 도구는 Python이다. 이들은 추론 실행에 관여하지 않고(그래서 make check엔 Python이 필요 없다) 준비·검증·측정을 맡는다.

서버·예제 (serve/ · Python 표준 라이브러리)

빌드 · CI

스택에서 배우는 것
"라이브러리 우선(library-first)" 설계가 곧 검증가능성과 이식성을 만든다

waste는 로깅·시그널 핸들러·설정 파일·인자 파싱을 엔진에 절대 넣지 않는다 — 그건 호스트(=CLI)의 몫이다. "CLI가 어떤 기능을 필요로 하면, 먼저 waste.h에 넣는다"는 규칙 덕에, 사용자가 셸에서 할 수 있는 모든 일을 C 호스트도 할 수 있다. 결과적으로 엔진은 작고 순수하게 유지되고, 서버(Python)·CLI(C)라는 서로 다른 두 클라이언트가 같은 헤더 하나를 두드리게 된다. 관심사 분리가 이식성과 테스트 용이성으로 되돌아오는 좋은 본보기다.

4아키텍처 심화 — 트렁크·전문가·캐시, 그리고 "페이징 절벽"

이 레포의 진짜 알맹이. 바이트를 세 군데에 나눠 담고, 남은 RAM을 단 하나의 손잡이로 쓰는 법

축 0 — 2.78조 파라미터의 무게는 세 군데에 산다

모든 설계의 출발점은 "무게를 어디에 실을 것인가"를 세 덩어리로 가르는 것이다. 이 구분이 곧 아키텍처 전체다:

┌───────────────────────────────────────────────────────────────────────┐ │ WASTE 의 메모리 배치 (K3, 64GB 머신) │ ├───────────────────────────────────────────────────────────────────────┤ │ │ │ ① 트렁크 (RAM 상주, 약 27.28 GB) │ │ 임베딩 · LM head · 어텐션 · 라우터 · 공유 전문가 · norm │ │ └ 모든 토큰이 매번 쓰는 "공통 부분". 4~8bit 로 양자화 │ │ │ │ ② 전문가 뱅크 (디스크 상주, 약 950 GB) ◀── 여기가 스트리밍 대상 │ │ 896 experts × 60 layers, 각 전문가 = gate/up/down 행렬 │ │ └ 3bit 잔차 VQ. "한 전문가 = 한 번의 정렬된 pread" │ │ │ │ ③ 전문가 캐시 (RAM 여유분, 예: 17.56 GB) │ │ ②에서 최근·자주 읽은 전문가를 담아 재읽기를 줄임 (LFRU) │ │ └ 유일하게 "속도를 사는" 손잡이. 크다고 좋은 게 아니다(4-3 참고) │ │ │ │ + KDA 상태 · MLA 잠재 KV 캐시 · 스크래치 (합쳐 수 GB) │ │ └ KDA 덕에 4K 문맥 KV 가 11.25GB → 0.21GB 로 줄어 RAM 을 아낀다 │ │ │ └───────────────────────────────────────────────────────────────────────┘

①은 반드시 RAM에 있어야 하는 하한선(floor), ②는 디스크에서 흘려보내는 대상, ③은 남는 RAM으로 사는 속도다. waste가 K3를 여는 데 필요한 29.06GB의 대부분은 사실 ③ 캐시가 아니라 ① 트렁크(27.28GB)다.

축 1 — 컨테이너 포맷: "한 전문가 = 한 번의 읽기"

스트리밍이 빠르려면 디스크 배치(layout)가 전부다. WASTE 컨테이너(v0)는 단일 파일이 아니라 디렉토리이며, 네 가지 설계 목표로 짜였다:

용어
잔차 벡터 양자화 (residual/multi-stage VQ)
가중치를 4비트 미만으로 압축하되 정확도가 무너지지 않게 하는 기법. 8차원 벡터들을 코드북(대표 벡터 사전)에서 가장 가까운 항목으로 치환하고, 그 오차(잔차)를 다음 단계 코드북으로 또 근사하는 식으로 여러 단계를 쌓는다. waste는 실측 결과 3비트(오차 19.4%)를 전문가의 작동점으로 택했다 — 4비트 미만에서 단순 반올림(RTN)을 확실히 이기는 지점이다. 반면 민감한 트렁크(어텐션·라우터·norm)는 4~8비트로 더 정밀하게 둔다. "위치는 속도를 정하되 정밀도는 절대 건드리지 않는다"는 원칙의 구체적 구현.
  1. 전문가당 하나의 합쳐진 읽기. 한 전문가의 gate·up·down 행렬을 디스크에 인접 배치해 단 한 번의 pread로 읽는다. NVMe에서 "쓸 수 있는 대역폭 vs 못 쓰는 대역폭"의 차이가 여기서 갈린다.
  2. 위치는 속도를, 절대 정밀도를 정하지 않는다. 전문가가 RAM 캐시에서 왔든 디스크에서 왔든 출력은 비트 단위로 동일하다(불변식).
  3. O_DIRECT 친화. 독립적으로 읽는 모든 레코드를 4KiB로 정렬·배수화해, 페이지 캐시를 우회해도 되게 만든다.
  4. 붕괴 없는 4비트 미만. 위 잔차 VQ가 이 목표를 담당한다.

컨테이너 디렉토리는 이렇게 생겼다:

model.waste/ manifest.json # 설정 · 트렁크 텐서 색인 · 전문가 뱅크 색인 trunk.bin # RAM 상주 dense 부분 experts-L{층}.bin # 층마다 전문가 뱅크 하나 (스트리밍 대상) codebooks.bin # VQ 코드북 (RAM 상주) tokenizer.model # 모델의 tiktoken rank 파일을 그대로 복사 specials.json # 특수 토큰 id 와 정확한 문자열 vision.json # 비전 타워 형상 (있으면) chat.json # 대화 포맷 (손으로 작성) usage.waste # 런타임에 누적되는 라우팅 통계 · 학습된 핫리스트
보안 관점 (배울 점)
manifest.json 은 "신뢰할 수 없는 입력"으로 다뤄진다

모델 파일이라고 무조건 믿지 않는다. cfg_sane()이 모든 차원 값을 상한으로 검사하고, 텐서 크기는 그것을 담은 파일 크기로 제한하며, format_version이 없거나 다르면 읽지 않고 거부한다. 전용 퍼저(fuzz_container.py)까지 둔다. 읽기 경로에서 각 전문가 레코드는 매직 넘버·기대한 전문가 번호·스트라이드·CRC32를 "믿는 게 아니라 매니페스트에서 유도해 대조"한다. 남이 준 모델 파일을 파싱하는 모든 프로그램이 새겨야 할 태도다.

축 2 — 전문가 캐시(ecache.c): 이 레포의 심장

"모델이 RAM에 안 들어가므로 전문가는 디스크에서 스트리밍되고, 한도가 걸린 캐시가 그게 얼마나 자주 읽기가 되는지 결정한다." — ecache.h의 첫 문장이 이 프로젝트 전체를 요약한다. 캐시 정책의 핵심 두 가지:

용어
LFRU 축출 · 표본 기반 희생자 선택
캐시가 꽉 차면 누구를 내보낼지 정해야 한다. waste는 LFRU(빈도 우선, 최근성은 동점 처리)를 쓴다 — 자주 쓰인 전문가를 오래 남긴다. 게다가 전체를 훑지 않고 무작위로 16개(EC_SAMPLE)만 뽑아 그중 최악을 축출한다(근사 알고리즘). 왜 굳이? 작은 캐시 구간에서 평범한 LRU는 히트율이 5%로 붕괴하는데 LFRU는 29%를 지킨다는 걸 시뮬레이션(Gate 2)으로 확인했기 때문이다. 슬롯 상태는 EMPTY→INFLIGHT(읽는 중)→READY→FAILED로 관리된다.

두 번째는 선행 읽기(read-ahead / 라우터 룩어헤드)다. 진짜 라우터가 다음 층의 전문가를 고르기 전에, 가벼운 예측기가 "다음 층에 필요할 전문가"를 미리 추측해 디스크 읽기를 먼저 시작한다. 계산과 I/O가 겹쳐 돌아가 대기 시간이 줄어든다. 중요한 건 진짜 라우터가 여전히 최종 결정을 내리므로, 이 예측은 타이밍만 바꾸고 결과는 바꾸지 않는다는 점(불변식 유지). 이 룩어헤드 덕에 3.32GB짜리 작은 캐시도 29.1% 히트를 내어, 17GB 캐시와 처리량 10% 이내로 붙는다.

축 3 — RAM 예산: 하한선(floor)을 알고, 천장(ceiling)을 씌우고, "페이징 절벽"을 피한다

waste의 예산기는 "메모리를 더 주면 빨라진다"는 순진한 가정을 거부한다. 사용자가 --budget을 안 주면 엔진이 스스로 안전한 값을 고르는데, 그 논리가 이 레포에서 가장 교육적이다.

전문가 캐시는 토큰 하나의 워킹셋(working set)의 정수배일 때만 의미가 있다 — 1배 미만이면 토큰 사이에 아무것도 못 남기고, 1배 위의 어중간한 초과분은 히트율 몇 점을 사는 대신 머신을 페이징으로 밀어 넣는다. 그래서 기본값은 워킹셋을 한 칸씩 밟아 floor + 3배, 안 되면 2배, 안 되면 1배, 그것도 안 되면 floor를 택한다. K3는 64GB 머신에서 floor + 1배(예산 46.25GB, 캐시 17.56GB)에 안착한다.

실측 표가 "왜 크다고 좋은 게 아닌가"를 못 박는다:

전문가 캐시히트율디코드 속도해석
3.32 GB29.1%0.56~0.58 tok/s작아도 룩어헤드로 버틴다
17.32 GB36.2%0.63 tok/s측정 곡선의 정점 (기본값)
23.32 GB38.4%0.07~0.09 tok/s히트율은 올랐는데 속도 폭락 ⚠
29.32 GB41.3%0.07~0.08 tok/s8배 느려짐 = 페이징 절벽
비유

책상과 서류 더미. 책상(RAM)에 자주 보는 서류를 쌓아 두면 편하다. 그런데 서류를 책상이 넘치도록 쌓으면, 정작 펜을 놓고 글 쓸 자리가 없어져 서류를 바닥(디스크)에 잠깐 내렸다 다시 올리길 반복하게 된다. "책상에 있다"고 표시돼 있어도 실제론 바닥을 오가는 셈(=캐시 히트인 척하는 페이지 폴트). waste의 예산기는 "책상에 자리를 조금 비워 두는 게 오히려 빠르다"를 수치로 증명하고, 기본값으로 그 자리를 남긴다.

용어
cgroup 인식 예산 (0.6.3의 핵심 수정)
리눅스 컨테이너(도커 등) 안에서 sysconf(_SC_PHYS_PAGES)호스트 전체 RAM을 보고한다 — 컨테이너에 32GiB만 허용됐는데도 256GiB로 착각한다. 그러면 예산기가 80GB를 달라고 하고, 커널이 프로세스를 즉시 죽인다(페이징 절벽과 달리 서서히 느려지는 게 아니라 그냥 kill). 0.6.3은 천장을 min(물리 RAM, cgroup 한도)로 바꿔, cgroup과 그 조상들의 가장 작은 memory.max/high를 읽어 이 문제를 없앴다. "환경이 말하는 용량을 곧이곧대로 믿지 말라"는 시스템 프로그래밍의 교훈.

축 4 — KDA(Kimi Delta Attention): RAM을 아끼는 선형 어텐션

스트리밍이 통하려면 어텐션의 KV 캐시가 RAM을 잡아먹지 않아야 한다. 보통의 어텐션은 문맥이 길어질수록 KV 캐시가 선형으로 커져 RAM을 다 먹는다. K3는 KDA(선형 어텐션)를 주로 쓰는데, KDA 상태는 시퀀스 길이에 무관한 O(1)이다 — 헤드마다 행렬 상태 하나 + 작은 컨볼루션 창뿐. 그 결과 4K 문맥에서 KV 캐시가 11.25GB → 0.21GB로 줄어, 아낀 RAM이 고스란히 전문가 캐시로 간다(속도의 원천).

waste는 K3 출시 전에 공개된 Kimi-Linear-48B(같은 아키텍처 계열)의 레퍼런스 코드로 KDA 커널을 미리 구현·검증했다. 4개 층마다 하나꼴로 전통적 MLA(전체 어텐션) 층을 끼우는 3:1 인터리브 구조이며, MLA는 압축된 잠재(latent) KV로 문맥 비용을 더 낮춘다. K3에서 KDA 게이트 공식·출력 게이트가 Kimi-Linear와 미묘하게 다른데, 둘 다 "구조 차이가 아니라 설정으로 감지되는 차이"여서 커널 자체는 손댈 필요가 없었다.

아키텍처에서 배우는 것
"메모리 계층을 재설계하면, 모델을 손대지 않고도 하드웨어 벽을 넘는다"

waste의 어떤 부분도 모델의 수학을 바꾸지 않는다. 대신 바이트가 어디 사는가(RAM/디스크), 무엇을 언제 읽는가(캐시·룩어헤드), 얼마를 남겨 두는가(예산)를 재설계했다. 이건 데이터베이스·OS·게임 엔진이 수십 년간 써 온 메모리 계층(memory hierarchy) 최적화의 사고방식을, LLM 추론에 정면으로 적용한 사례다. "모델을 압축한다"만 떠올리던 사람에게 "배치를 바꾼다"라는 완전히 다른 축을 보여 준다.

5디렉토리 구조 해부

10,600줄 C 엔진이 어디에 어떻게 나뉘어 있나 — 큰 파일 순으로 읽는 지도
waste/ ├── src/ ◀── 엔진 코어 (C11, 추론 경로는 libc+pthreads만) │ ├── model.c (3281줄) ★ 순전파의 심장 — 어텐션·MoE·norm·로짓 │ ├── waste.c (1029줄) ★ 공개 API 구현 (open/close/generate/plan/stats) │ ├── ecache.c (728줄) ★ 한도 걸린 전문가 캐시 (LFRU·리더 스레드·룩어헤드) │ ├── tokenizer.c(625줄) tiktoken BPE 를 C 로 (base64 vocab·프리토큰 정규식) │ ├── vision.c (337줄) 비전 타워 (이미지 → 프롬프트 위치) │ ├── model.h (305줄) 모델 구조체·설정 정의 │ ├── backend.c (229줄) 백엔드 디스패치 (CPU/SIMD/Metal 선택) │ ├── memory.c (161줄) RAM 예산·floor 계산·cgroup 한도 파싱 │ ├── simd_avx2/avx512/kda_neon CPU 별 SIMD 커널 (런타임 선택) │ ├── vq.c (125줄) 벡터 양자화 디코드 (전문가 압축 해제) │ ├── crc32.c (192줄) 전문가 레코드 무결성 검사 │ └── waste.h (412줄) ★ 공개 헤더 = 이 프로젝트의 "계약서" │ ├── cli/main.c (1235줄) ◀── CLI (라이브러리의 첫 번째 클라이언트) │ run·chat·eval·tokenize·bench·plan·info·version ├── serve/ (Python 표준 라이브러리) ◀── OpenAI 호환 서버 │ ├── server.py · api.py · engine.py · xtml.py ctypes 로 waste.h 호출 ├── tools/ (Python·Shell) ◀── 변환·검증·측정 (추론엔 불필요) │ ├── convert.py · fetch_weights.sh · memplan.py · routing_stats.py │ └── kimi_ref.py · kda_ref.py · quant_lab.py · diskbench.c ... ├── examples/ api_text.c · api_plan.c · api_vision.c (임베딩 예제) ├── docs/ ENGINE · FORMAT · KDA · K3 · EFFICIENCY · LEARNED · GATES ... ├── tests/ 모델 없는 스위트 + 실모델 층별 대조 ├── third_party/ stb_image.h (유일한 vendored 의존성) └── Makefile · CHANGELOG.md · CLAUDE.md · LICENSE(Apache-2.0)

읽는 순서를 추천하면: src/waste.h(무엇을 할 수 있는 엔진인지 = 계약서) → docs/ENGINE.md(왜 이렇게 나눴는지) → src/ecache.c(캐시 = 심장) → src/model.c(순전파 = 근육) 순이다. 10개가 넘는 docs/ 문서가 각 결정의 근거를 담고 있어, 코드보다 문서를 먼저 읽는 게 이 레포에선 오히려 빠르다.

용어
docs/LEARNED.md · "부정적 결과의 박물관"
대부분의 프로젝트는 성공한 것만 기록한다. waste는 반대로, 시도했다가 효과 없어 버린 아이디어와 그 측정치를 일부러 남긴다. 예: "전문가마다 비트 수를 다르게 주는(GEMQ식) 할당기"를 만들려다, 실측해 보니 전문가 간 중요도 차이가 1.01~1.30배로 사실상 평평해서 "최적 할당기와 동전 던지기가 같은 컨테이너를 쓴다"는 걸 확인하고 폐기했다. 그래서 모든 전문가가 같은 비트 폭을 쓴다 — 코드가 없어서가 아니라 측정 후 일부러. 최적화를 제안하려면 이 문서부터 읽으라고 안내된다.

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

이 레포 한 편으로 건드릴 수 있는, 서로 다른 분야의 알짜 지식들

① 시스템 프로그래밍 — 저장장치를 "느린 RAM"으로 다루기

pread·O_DIRECT·posix_memalign으로 OS 캐시를 우회한 정렬 직접 I/O를 어떻게 하는지, 왜 4KiB 정렬이 NVMe 대역폭을 좌우하는지, 리더 스레드로 I/O와 계산을 어떻게 겹치는지를 실코드로 배운다. cgroup v2 한도를 파싱해 컨테이너 안 실제 RAM을 읽는 부분은 클라우드/도커 환경 프로그래밍의 필수 교훈이다.

② LLM 추론 내부 — MoE·양자화·어텐션을 밑바닥에서

MoE 라우팅(top-16 선택), 잔차 벡터 양자화(3비트 전문가), 선형 어텐션(KDA)의 O(1) 상태, 압축 잠재 KV(MLA)를 프레임워크 없이 순수 C로 구현한 코드를 읽을 수 있다. "논문의 수식이 실제 커널에서 어떤 모양이 되는가"를 배우기에 이만한 교재가 드물다.

③ 캐시·메모리 계층 설계 — CS의 고전을 AI에 적용

LFRU 축출, 표본 기반 희생자 선택, 워킹셋 정수배 예산, 그리고 "메모리를 더 줘도 느려지는" 페이징 절벽. 이건 운영체제·데이터베이스 수업에서 배우는 개념 그대로인데, 여기선 실측 표와 함께 살아 움직인다.

④ 라이브러리 API 설계 — "계약"으로서의 헤더

전역 상태 없음, 에러는 반환(출력 아님), exit() 금지, "CLI가 필요하면 헤더에 먼저"라는 규칙. 임베더블 C 라이브러리를 어떻게 설계하는가의 모범 답안이다. 같은 헤더를 C(CLI)와 Python(ctypes 서버)이 두드리는 것도 좋은 관찰 대상.

⑤ 엔지니어링 문화 — 측정 규율과 정직함

모든 수치에 하드웨어·커밋을 붙이고, 불안정한 값은 범위로, 틀린 값도 기록으로 남기며, 실패한 아이디어를 LEARNED.md에 박제한다. "측정이 핵심 산출물"이라는 태도는 코드 실력과 별개로 배울 값어치가 있다.

한 줄 요약
"시스템 프로그래밍 × LLM 내부 × 캐시 이론"이 한 레포에서 만난다

보통은 세 분야를 따로 공부하는데, waste는 세 개가 하나의 실제 문제(초거대 모델 로컬 추론)를 푸는 데 어떻게 맞물리는지를 보여 준다. 각 분야의 깊이도 얕지 않아, 어느 배경에서 와도 얻을 게 있다.

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

빌드만 할지, Kimi-Linear로 맛볼지, K3 풀 모델을 돌릴지에 따라 천차만별
단계필요한 것비고
빌드 + 테스트C11 컴파일러 + make, macOS/Linux(Win은 MinGW)make check는 합성 모델이라 가중치 다운로드 불필요
맛보기 (Kimi-Linear 48B)RAM 1.28GB, 컨테이너 19GB약 10.65 tok/s — 엔진을 체감하기 좋은 진입점
K3 풀 모델RAM 64GB 권장(하한 29.06GB), 내장 NVMe 약 1TB0.45~0.62 tok/s. 32GB로는 열리지만 심하게 페이징
직접 변환 시추가 임시 저장 1.42TB(외장 가능, 이후 삭제)변환에 torch+safetensors, 3워커 약 4.7시간
가장 흔한 함정
컨테이너를 USB 외장에 두면 K3는 사실상 멈춘다

K3 토큰 하나가 디스크에서 약 17GB의 전문가를 읽는다. 내장 SSD는 12.78GB/s를 내지만, 테스트한 USB 외장은 0.94GB/s에 그쳤다 — 13배 이상 차이. 반드시 변환된 컨테이너를 내장 NVMe에 둬야 한다. 또한 --budget은 이유가 없으면 건드리지 말 것 — 기본값이 페이징 절벽을 피하도록 설계돼 있고, 직접 크게 잡으면 오히려 8배 느려질 수 있다.

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

982GB를 안 받아도 시작할 수 있는 것부터, 풀 모델 튜닝까지 — 난이도별
난이도 ★☆☆ — 가중치 없이

1. 빌드하고 모델-프리 테스트 통과시키기

git clonemakemake check. 합성 컨테이너로 엔진 전체가 도는 걸 확인한다. 이어서 ./waste version, ./waste --help로 9개 명령을 훑어보고, tools/diskbench.c를 빌드해 내 SSD의 실제 대역폭을 재 본다(K3를 돌릴 수 있을지 사전 판단).

난이도 ★☆☆ — 문서 읽기

2. "페이징 절벽" 표를 스스로 설명해 보기

docs/ENGINE.mddocs/EFFICIENCY.md를 읽고, "왜 캐시를 키웠는데 8배 느려지는가"를 그림 한 장으로 재구성한다. 그다음 docs/LEARNED.md에서 "폐기된 아이디어" 하나를 골라, 무엇을 가정했고 어떤 측정이 그 가정을 깼는지 요약한다. 코드 없이도 시스템 사고를 크게 키우는 과제.

난이도 ★★☆ — Kimi-Linear로

3. 19GB 모델을 받아 캐시 예산을 손으로 바꿔 보기

Kimi-Linear 48B 컨테이너를 준비해 ./waste plan MODEL --budget 4G로 floor·권장·캐시 배분을 확인하고, ./waste bench로 여러 --budget 값에서 tok/s를 측정한다. 내 머신에서도 "정점 이후 하락" 곡선이 재현되는지 직접 그려 본다. README의 K3 표와 모양이 같은지 비교.

난이도 ★★★ — 코드 읽기/수정

4. ecache.c의 축출 정책을 LRU로 바꿔 히트율 비교

ecache.c에서 정책 스위치(policy: 0=LFRU, 1=LRU)를 이용하거나 직접 LRU 경로를 손봐, 같은 트레이스에서 LFRU vs LRU 히트율routing_stats.py simulate로 대조한다. "작은 캐시에서 LRU가 5%로 붕괴한다"는 README의 주장을 스스로 재현·반증해 본다.

난이도 ★★★★ — 풀 모델

5. K3를 변환해 임베딩 API로 직접 호출하기

64GB+ 머신과 1TB NVMe가 있다면 fetch_weights.shconvert.py로 K3를 변환하고, examples/api_text.c를 참고해 내 C 프로그램에 libwaste를 링크해 토큰 콜백(캐시 히트/미스·I/O 시간 포함)을 받아 진행률 UI를 그린다. 그다음 python3 -m serve로 OpenAI 호환 엔드포인트를 띄워 curl로 대화해 본다.

9관련 기술 심화 로드맵 (주차별)

waste를 온전히 소화하기 위한 8주 학습 경로 — 배경이 없어도 따라올 수 있게
주차주제목표 · 산출물
1주C·시스템콜 기초포인터·메모리·read/pread·파일 디스크립터. 파일을 청크로 읽어 합치는 작은 C 프로그램 작성
2주메모리 계층 & 캐시RAM vs 디스크 대역폭/지연, 페이지 캐시, LRU/LFU. diskbench.c로 내 SSD 곡선 측정
3주Transformer & 어텐션self-attention·KV 캐시가 왜 문맥 길이에 비례해 커지는지. 작은 GPT를 넘파이로 순전파
4주MoE & 라우팅전문가 혼합·top-k 라우팅·활성 파라미터 개념. K3의 896×top-16이 왜 스트리밍 가능한지 설명
5주양자화 & VQint8/int4, RTN vs 벡터 양자화, 잔차 다단계 VQ. quant_lab.py로 3비트 오차 재현
6주선형 어텐션(KDA/MLA)O(1) 상태 어텐션·압축 잠재 KV. docs/KDA.md + src/kda.c 정독
7주waste 캐시·예산 정독ecache.c + memory.c + docs/ENGINE.md. 페이징 절벽을 직접 재현·시각화
8주임베딩 & 서빙waste.h로 라이브러리 임베딩 → OpenAI 호환 서버 띄우기. 나만의 진행률 UI 완성

배경이 이미 있다면 2주(캐시) → 7주(waste 캐시/예산)만 골라 읽어도 이 레포의 핵심(전문가 캐시 + 페이징 절벽 + 예산기)을 잡을 수 있다. LLM 내부가 궁금하면 4·5·6주가 알짜다.

10핵심 키워드 사전

이 문서와 README를 읽을 때 걸리는 용어들 — 한 줄 정의
키워드
스트리밍 추론 (weight streaming)
모든 가중치를 RAM에 올리지 않고, 지금 필요한 것만 디스크에서 그때그때 읽어 계산하는 방식. waste의 핵심 전략.
키워드
트렁크 (trunk)
모든 토큰이 공통으로 쓰는 dense 부분(임베딩·어텐션·라우터·공유 전문가·LM head). RAM에 상주하며 floor의 대부분을 차지한다.
키워드
전문가 뱅크 (expert bank)
층마다의 전문가 가중치 묶음(experts-L{층}.bin). 디스크에 두고 스트리밍하는 대상. "한 전문가 = 한 번의 pread"로 배치.
키워드
전문가 캐시 (expert cache) · LFRU
최근·자주 쓴 전문가를 RAM에 담아 재읽기를 줄이는 한도 걸린 캐시. LFRU(빈도 우선+최근성 동점처리)로 축출하며, 유일하게 속도를 사는 손잡이.
키워드
페이징 절벽 (paging cliff)
캐시를 너무 키워 OS가 메모리 압박을 받으면, 캐시 히트가 사실상 페이지 폴트가 되어 처리량이 급락하는 현상. waste가 예산기로 피하려는 핵심 실패 모드.
키워드
floor / ceiling / working set
floor=모델을 여는 최소 RAM(트렁크+상태+최소캐시), ceiling=엔진이 쓸 수 있는 상한(물리·cgroup 중 작은 값), working set=토큰 하나가 건드리는 전문가 크기. 예산은 floor + (워킹셋 정수배).
키워드
잔차 벡터 양자화 (residual VQ)
벡터를 코드북으로 근사하고 그 오차를 다음 단계로 또 근사하는 다단계 압축. waste 전문가는 3비트, 트렁크는 4~8비트.
키워드
KDA (Kimi Delta Attention) · MLA
KDA=시퀀스 길이에 무관한 O(1) 상태의 선형 어텐션. MLA=압축 잠재 KV의 전체 어텐션. K3는 4:1로 인터리브해 KV 캐시 RAM을 크게 줄인다.
키워드
O_DIRECT / F_NOCACHE
OS 페이지 캐시를 우회해 디스크에서 버퍼로 곧장 읽는 옵션. 캐시 측정을 정직하게 하고 RAM 낭비를 막기 위해 일부러 사용.
키워드
임베더블 / library-first
엔진이 곧 라이브러리(waste.h)이고 CLI·서버는 그 클라이언트라는 설계 철학. 전역 상태 없음, 에러 반환, exit() 금지.
키워드
컨테이너 포맷 v0 (.waste)
manifest+트렁크+전문가뱅크+코드북+토크나이저를 담은 디렉토리. 4KiB 정렬, 전문가당 단일 읽기, format_version 강제 검증.
키워드
라우터 룩어헤드 (read-ahead)
다음 층에 필요할 전문가를 미리 예측해 디스크 읽기를 앞당기는 것. 결과가 아니라 타이밍만 바꿔, 작은 캐시로도 높은 히트율을 낸다.

11참고 링크

원본을 직접 확인하고 싶을 때