당신의 벤치마크를 그것을 무너뜨릴 수 있는 사람들에게 가져가세요
요약
MoE 모델의 전문가 가중치를 공동 활성화(co-activation) 기준으로 재정렬하여 디스크 읽기 효율을 높이는 mbolt 프로젝트를 소개합니다. 벤치마크 과정에서 발생한 반박과 검증을 통해 모델 레이아웃 최적화가 추론 엔진의 읽기 방식에 따라 미치는 영향을 분석합니다.
핵심 포인트
- MoE 모델의 전문가 가중치 재정렬로 디스크 읽기 2.23배 감소 달성
- MacBook 환경에서 235B 모델의 디코딩 처리량 32.3% 향상 확인
- 추론 엔진의 명시적 읽기(explicit reads) 방식이 최적화 효과의 핵심
- 데이터 기반의 반박과 검증을 통한 기술적 가설의 정교화 과정 강조
열흘 전, 저에게는 하나의 벤치마크가 있었습니다. 측정된 공동 활성화 (co-activation)를 기준으로 MoE (Mixture of Experts) 모델 파일 내부의 전문가 가중치 (expert weights)를 재정렬하면, 토큰당 디스크 읽기 (disk reads)가 2.23배 감소한다는 것이었습니다. 그것은 실제였고, 재현 가능했으며, 전적으로 제 개인 컴퓨터에서 측정되었습니다. 즉, 아직은 거의 가치가 없는 상태였습니다.
오늘날 이 작업에 붙은 가장 강력한 수치는 제가 만들어낸 것이 아닙니다: 48 GB MacBook에서 실행되는 235B 파라미터 모델에서 +32.3%의 디코딩 처리량 (decode throughput) 및 -26.3%의 첫 토큰 생성 시간 (time-to-first-token) 향상. 이는 제가 한 번도 만난 적 없는 누군가가, 제가 한 번도 실행해 본 적 없는 추론 엔진 (inference engine)에서, 제 스크립트를 수정 없이 사용하고, 페이지 캐시 (page-cache) 제어를 위해 arms를 교체하여 측정한 결과입니다. 그 과정에서, 제가 처음에 제안했던 세 가지 중 두 가지는 데이터에 의해 반박되었고, 사전에 등록했던 하나의 스케일링 (scaling) 예측은 자체 임계값에 도달하지 못했으며, 주요 파라미터를 예측하려던 두 번의 시도는 서로 반대 방향으로 빗나갔습니다.
반박은 생산적인 부분이었습니다. 이 포스트는 그 반박들을 저렴하게 만들어준 과정에 관한 것입니다. 다섯 개의 엔진, 대여섯 명의 낯선 사람들, 하나의 이슈 스레드 (issue thread), 그리고 그 안에서 생겨난 규율에 대한 이야기입니다. 왜냐하면 저는 이 과정이 일반화될 수 있다고 생각하며, 제가 보통 접하는 기술 문서들은 이를 언급하지 않기 때문입니다.
설정 (The setup)
아이디어 (프로젝트: mbolt)는 모델 바이너리 (model binaries)에 BOLT/PGO를 적용하는 것입니다. 체크포인트 텐서 순서 (Checkpoint tensor order)는 훈련 파이프라인 (training pipeline)의 우연한 결과입니다. 추론 (inference) 시의 MoE 라우팅 (routing)은 결코 무작위가 아닙니다. 전문가들은 클리크 (cliques) 단위로 작동합니다. 만약 엔진이 SSD에서 전문가를 스트리밍한다면, 파일 레이아웃 (file layout)은 토큰의 미스 (misses)가 소수의 긴 순차적 읽기 (sequential reads)가 될지, 아니면 수천 개의 흩어진 읽기가 될지를 결정합니다. 따라서: 라우팅을 추적하고, 공동 활성화 (co-activation)를 클러스터링하며, 파일을 다시 쓰고, 가중치를 바이트 단위로 정확하게 유지합니다.
일주일 동안 혼자 작업한 결과, 80B 모델에서 2.23배의 읽기 감소(read reduction)를 달성했으며, 패치된 llama.cpp 대비 1.55배의 엔드투엔드 디코드 이득(end-to-end decode gain)을 얻었습니다. 또한 정확도 게이트(correctness gates) (바이트 단위 동일 가중치, 순열을 통한 라우팅 매핑 100.000% 통과, 출력 발산이 같은 엔진의 CPU↔Metal 백엔드 편차보다 5배 낮음)와 500프롬프트 무작위 A/B 테스트를 진행하여 26개의 동점(ties)을 포함해 48 대 46점을 기록했습니다. 이는 목표인 동전 던지기 수준의 결과입니다.
또한 저 스스로 측정한 '무효' 사례도 있었습니다: 기본 llama.cpp에서는 아무것도 중요하지 않았습니다. mmap 페이지 폴트 경로는 레이아웃에 무관하며, 저는 그 패리티를 측정하고 그렇게 말했습니다. 이득을 얻으려면 명시적 읽기(explicit reads)를 발행하는 엔진이 필요합니다.
이 '무효' 사례가 출시 전략을 결정하게 했지만, 당시에는 그것을 전략이라고 생각하지 않았습니다. 명시적 읽기를 사용하는 전문가 스트리머들은 셀 수 있는 사람들입니다. Show HN을 게시하는 대신, 저는 이 데이터를 그들의 이슈 트래커에 가져갔습니다: ml-explore/mlx-lm#1438 (여기서 @mabaeyens가 MLX용 전문가 오프로드 스트리머를 배포하고 있었고), 그리고 NVMe에서 744B 모델을 스트리밍하는 C 엔진인 JustVugg/colibri#119였습니다. 첫 문장은 이렇습니다: '당신의 엔진이 레이아웃 패스를 위한 자연스러운 본거지이기 때문에 데이터를 공유합니다' — 그리고 제 발견 중 하나는 이 청중이 바로 당신이라는 것입니다, 특히요.
첫 접촉: 하나의 답글에 두 개의 발표가 좌절되다
mabaeyens는 제가 제시한 틀에 대해 논쟁하지 않았습니다. 그는 그것을 측정했습니다.
그의 답변은 제가 제시한 세 가지 제안 — RAM 내에서 가장 뜨거운 전문가(expert)를 고정(pin)하기, 공동 활성화(co-activation)를 통해 다음 레이어의 전문가를 프리페치(prefetch)하기, 그리고 파일 순서 재정렬하기 — 을 가져가서, 자신의 엔진 카운터(counters)를 통해 처음 두 가지를 무너뜨렸습니다. 상주 세트(resident set)를 히트-피닝(heat-pinning)하는 것은 동일한 주제, 동일한 세션 트래픽에서도 단순한 레이어별 LRU (Least Recently Used)에 패배합니다. LRU는 이미 재사용성을 확보하고 있으며, 고정된 모든 슬롯은 워크로드의 드리프트(drift)가 사용할 수 없는 슬롯이 되기 때문입니다. 그리고 레이어 간 프리페치(cross-layer prefetch)는 실행할 신호(signal)가 없습니다. 인접한 레이어 간의 전문가 중첩(expert overlap)을 측정한 결과, 셔플된 대조군(shuffled control)의 0.016에 비해 0.017로 나타났습니다. 독립적인 레이어별 라우터(router)와 부하 분산 손실(load-balance loss)은 레이어 간의 상관관계를 깨뜨립니다. 예측할 수 있는 것이 아무것도 없습니다.
그날 저는 스레드 내에서 두 제안 모두를 철회했습니다.
여기 제가 강조하고 싶은 점이 있는데, 이것이 전체 이야기의 핵심이기 때문입니다: 그 스레드는 반박을 패배가 아닌 범위(scoping)의 문제로 다루었습니다. 주장이 사라진 것이 아니라, 이동한 것입니다. 만약 유지(retention)가 디코드(decode)의 이점을 죽인다면, 레이아웃 레버(layout lever)는 여전히 읽기가 발생하는 곳, 즉 콜드 프리필(cold prefill)과 (그의 다음 측정 결과가 보여주듯) 전문가 테이블이 RAM보다 훨씬 커져서 미스가 영원히 콜드 상태로 남게 되는 디코드(decode) 단계로 이동합니다. 그의 단편화(fragmentation) 수치는 제 원래 제안보다 더 날카롭게 논거를 뒷받침했습니다: 그의 리더(reader)는 디코드 토큰당 약 471회의 파일 오픈(file opens)을 수행했으며, 콜드 전문가당 9개의 바이트 범위(byte-ranges)를 호출했습니다. 레버는 죽지 않았습니다. 단지 잘못 분류되었을 뿐입니다.
스레드가 수렴한 규칙들
누구도 방법론을 선언하지 않았습니다. 그것은 한 번씩 데인 손을 거치며 점진적으로 쌓였고, 마지막에는 다음과 같은 모습이 되었습니다:
실행하기 전에 실험을 고정(lock)하라. 우리가 결정적인 테스트 — 단독 병합(coalescing alone, 프로파일 프리, 결정론적) 대 병합 및 공동 활성화 재정렬(coalescing plus co-activation reorder, 트레이스 필요) — 를 설계했을 때, 실험군(arms), 지표(metrics), 그리고 결정 규칙(decision rule)은 누구도 실행하기 전에 스레드 내에서 합의되었습니다. 왜냐하면 그 결과가 그가 자신의 엔진의 '샤드로부터 직접 읽기(read-straight-from-shards)' 불변성(invariant)을 깨뜨릴지 여부를 결정했기 때문입니다.
당신에게 불리할 수 있는 임계값(threshold)과 함께 수치를 사전 등록하세요. 나의 기록된 예측: 더 깊은 오프로드(offload) 단계에서 병합 이득(coalesce gain)은 약 +20%에 도달하며, "만약 +15% 미만으로 떨어진다면, 정직한 보고서는 이 레버(lever)가 '훌륭하지만 필수적이지는 않음(nice-not-necessary)' 수준에서 정점을 찍는다고 말할 것이다." 결과는 +9.7%로 나왔습니다. 나는 완전히 인정했습니다. 설정(config) 차이에 대한 항변은 하지 않았습니다. 사전 등록을 통해 이미 그 차이를 포기했기 때문입니다.
커밋 속도로 인정하세요. 나의 분석 모델은 독자가 이미 제거했던 36 µs의 open() 비용을 과다 산정했습니다. 그의 공유 파일 디스크립터(shared-fd) 수정 사항은 내가 근거로 삼았던 수치를 인용한 지 약 4분 후에 나왔습니다. 그의 측정값이 나의 모델보다 낮게 나왔을 때, 모델은 당일 공개적으로 패배했습니다. 기록용 참고 사항: 해당 스레드 전체에서 나는 콜드 리드(cold-read) 점유율 예측에 대해 0승 2패를 기록했으며(두 번 모두 반대 방향으로 예측 실패), 커뮤니티의 규칙은 _엔진별로 측정하라, 절대 이를 교차해서 적용하지 마라_로 굳어졌습니다.
대조군(Controls), 그렇지 않으면 발생하지 않은 것입니다. 이 스레드의 카나리(canaries, 경고 신호)들은 내가 가장 좋아하는 유물입니다. 실제로는 macOS가 상주 페이지(resident pages)를 축출(evict)하기를 거부한 것이었던 "2.23배 속도 향상" (F_NOCACHE는 우리 중 세 명이 가정했던 대로 작동하지 않았습니다; 대조군(control arm)에서 불가능한 0.18배가 나온 것이 결정적 증거였습니다). 예측형 암(predictive arm)을 "확인"해 주었던 프리페치(prefetch) A/B 테스트는, 작성자가 두 셀(cell) 모두 동일한 코드를 실행했다는 사실을 발견하기 전까지(바이트 단위로 일치하는 히트율이 결정적 증거였습니다) 지속되었습니다. 그리고 제대로 다시 실행했을 때: 예측기는 저장된 바이트당 6.9바이트를 읽어 들임으로써 2.9% 차이로 패배했습니다. 분석적 균등 베이스라인(analytic uniform baseline) 대비 1.17배의 신호를 보여주고 셔플 대조군(shuffle control) 대비 0.98배를 보여준 교차 아키텍처 중첩(cross-architecture overlap) 주장 — 분석적 베이스라인이 신호를 조작하고 있었습니다. 페이지 캐시(page-cache) 상태를 위한 Arm 스와핑(Arm-swapping)은 스레드의 모든 A/B 테스트에서 표준이 되었습니다.
기준점(baseline)을 명시하지 않으면 수치는 전달되지 않습니다. 동일한 재작성(rewrite) 방식이 전문가(expert)당 9번의 분산된 읽기(scattered reads)를 수행하는 리더(reader)를 상대로는 +32%, 딥 오프로드(deep offload) 시 투영(projection)당 3번의 읽기를 수행하는 경우 +31%의 성능 향상을 보였지만, 각 전문가를 이미 연속적으로(contiguously) 저장하고 있는 컨테이너를 상대로는 향상 폭이 거의 없었습니다(~nil). 기준이 되는 읽기 패턴(read pattern)을 명시하지 않고 레이아웃 이득(layout gain)을 인용하는 것이 바로 수치가 재현되지 못하게 만드는 원인입니다.
하나의 살아있는 결과물(living artifact)을 유지하세요. 진행 중간에 저는 통합 요약본을 댓글로 게시하고 이를 버전별로 관리했습니다 — 현재까지 v1.0부터 v1.5.1까지 — API를 통해 제자리에서 편집하고 변경 이력(changelog)을 기록했으며, 참여자들이 명시적으로 수정 사항을 표시(red-line)할 수 있도록 했습니다. 사람들은 이를 활용했습니다. 한 참여자의 수정 사항은 순수하게 출처 표기(attributional)에 관한 것이었습니다 (엔진의 이름이었는데, 제가 이틀 동안 오타를 냈습니다. v1.4.1에서 수정되었으며, 네, 변경 이력에 기록되어 있습니다). 스레드가 만들어내는 것은 단일 결과가 아니라, 바로 이 요약본입니다: 범위가 지정된 조사 결과(scoped findings), 측정된 지점들, 그리고 함정(traps) 섹션입니다.
보상이 된 반증(falsification)
+9.7%의 실패는 이 프로젝트에 일어난 가장 좋은 일이었습니다.
그 이면에 있는 메커니즘 — 병합(coalescing)은 IOPS 레버이며, 4 MB 슬라이스(slice)는 이미 대역폭 제한(bandwidth-bound) 상태라는 점 — 은 이제 이 스레드에서 '슬라이스 크기 법칙(slice-size law)'이라 부르는 것으로 일반화되었습니다: 이득(gain) ≈ 1 + 절약된_연산(saved_ops) · t_op / (슬라이스_바이트(slice_bytes) / BW). 여기서 이 하드웨어 클래스의 경우 t_op ≈ 100 µs, BW ≈ 6 GB/s입니다. 엔드 투 엔드(End-to-end)로 보면, 이는 콜드 리드(cold-read) 비중 s와 결합하여 속도 향상(speedup) = 1/((1−s) + s/g가 됩니다. 이것은 물리학이 아닌 엔지니어링적 적합성(engineering fit)입니다. 하지만 이제 5개의 엔진에 걸쳐 6개의 측정된 지점을 확보했으며, 좋은 모델이 하는 단 한 가지 일을 해냅니다: 당신의 설정(configuration)이 그 수고를 들일 가치가 있는지 미리 알려주는 것입니다.
이 법칙의 검증은 초기 교환 이후 참여한 두 명의 참가자를 통해 이루어졌습니다. @lBroth는 수정하지 않은 제 변환 스크립트(conversion script)를 119 GB 규모의 Qwen3-235B 빌드에 실행한 뒤, 그 출력을 자신의 엔진에 연결했습니다: 디코드(decode) 속도가 +32.3% 향상되었습니다. 이는 개별 읽기(per-read) 이득이 커서가 아니라(그렇지 않았습니다; 큰 슬라이스에서 g ≈ 1.34), 2.6× over-DRAM 환경에서 콜드 리드(cold-read) 점유율이 ~0.96이며 법칙의 다른 항이 지배적이기 때문입니다. 그 후 그는 자신의 엔진 내부에 더 깊은 변형(deeper variant)을 구축했고, 그 결과 +31.2%를 측정했는데, 이는 그의 지정된 베이스라인(baseline) 대비 법칙이 예측한 +31.3%와 일치했습니다. 그리고 프로덕션 GLM-5.2 페이저(pager)를 실행 중인 @pierre427는 우리가 도출한 개별 읽기 이득 범위(1.2, 범위 1.15–1.3)를 사전 등록했으며, 1.195를 측정했습니다. 법칙이 사전 등록 후 실제로 측정된 첫 번째 지점입니다.
반대편의 사례도 가격이 매겨졌으며, 이 또한 똑같이 중요합니다: @philipjohnbasile는 각 전문가(expert)를 하나의 연속적인 ~19 MB 읽기로 이미 저장하고 있는 자신의 744B 엔진 컨테이너를 측정했을 때, 잔여 이득이 +1.3%로 나타났으며 이는 그 자신의 프로모션 게이트(promotion gate) 미만이었습니다. 그는 이를 '불가(no-go)'로 게시했고 저도 동의했으며, 제가 이전에 제시했던 ~+20%의 GLM급 수치는 철회되었습니다. 이제 병합 사다리(coalescing ladder)의 가격이 완전히 책정되었습니다: nine-scattered 방식은 점유율에 따라 +14–32% 향상; per-projection 방식은 딥 오프로드(deep offload) 시 +31% 향상; per-expert-contiguous 컨테이너 방식은 장치 대역폭 한계(device bandwidth ceiling)에서 거의 효과가 없음. 적용 불가능한 영역(domain of inapplicability)이 측정되고 발표된 최적화는 성공 사례만 있는 최적화보다 더 가치가 있습니다.
Null(무효 결과)은 제품이다
오늘 기준으로 이 스레드의 공동묘지(graveyard)에 들어간 항목들: 예측적 프리페치(predictive prefetch) (세 가지 독립적인 null — 구조적 null, 적절히 제어된 A/B 테스트, 교차 아키텍처 셔플 테스트), 히트 피닝(heat-pinning) 대 LRU, LRU보다 똑똑한 교체(eviction), 그리고 저의 원래 교차 레이어 프레이밍(cross-layer framing) (철회됨). 각 null은 범위(batch-1, 디코드, 해당 아키텍처들)가 정해져 있으며, 각각의 null은 누군가가 이제 더 이상 구축할 필요가 없는 로드맵 항목이 됩니다.
그 후 결말은 저절로 써 내려가졌습니다. 스레드 후반부에 PhilipJohnBasile가 자신의 엔진을 오픈 소스 (open-source)로 공개했는데, 그 README에는 우리가 대화를 나누기도 전에 이미 다섯 번째 엔진을 통해 측정된, 거의 전체 설계 이론에 대한 독립적인 재현 결과가 포함되어 있었습니다: 라우터-룩어헤드 프리페치 (router-lookahead prefetch)는 -6.96%로 무효화되었고, 더 스마트한 에비션 (eviction)은 히트 레이트 (hit-rate) 변화 없이 -26%로 무효화되었으며, 정적 피닝 (static pinning)과 핫셋 트레이닝 (hotset training)도 모두 무효화되었습니다. 그의 요약 문구는 다음과 같았습니다: "캐시 정책의 프런티어 (frontier)는 닫혔다: 용량 대 재사용 거리 (capacity vs reuse distance)가 바로 그 벽이다." 우리는 네 개의 서로 다른 코드베이스로부터 동일한 벽에 수렴했습니다.
(그 오픈 소스 공개는 또한 이 스레드의 위대한 작은 순간 중 하나인 인용 미스터리를 해결해 주었습니다. 모두가 우리의 측정값이라고 계속 주장하던 처리량 (throughput) 수치는, 알고 보니 그의 엔진이 포크 (fork)한 부모 프로젝트의 6141번 라인에 있는 이탈리아어 코드 주석임이 밝혀졌습니다. 이는 7월 초 리눅스 (Linux)에서 측정된 것이었으나, 그의 포크 버전에서는 플랫폼 식별자 (platform qualifier)가 누락되어 있었습니다. GitHub의 코드 검색이 그렇게 큰 파일을 조용히 인덱싱하지 않기 때문에, 우리 중 두 명은 잘못된 출처를 "확인"했던 것입니다. 이 내용 또한 현재 함정 (traps) 섹션에 포함되어 있습니다.)
내가 얻은 교훈
- 그 주장을 그것을 무너뜨릴 수 있는 사람들에게 가져가세요. 네 명의 엔진 개발자들이 며칠 만에 모든 주요 주장 (claims)을 조작하거나, 범위를 제한하거나, 확장했습니다. 일반 대중은 그렇게 하지 않습니다. 해당 니치 스레드 (niche thread)는 출시 (launch)에 대한 겸손한 대안이 아니라, 더 높은 대역폭 (higher-bandwidth)을 가진 채널이었습니다.
- 사전 등록 (Pre-registration)은 틀리는 비용을 저렴하게 만듭니다. 내가 사전에 약속한 모든 수치는 살아남았거나 (그리고 이제 무게감을 가집니다), 깔끔하게 사라졌거나 (그리고 하나의 법칙을 만들어냈습니다). 내가 헤징 (hedging)했던 두 가지 예측은 이를 해결하기 위해 후속 라운드 (follow-up rounds)를 소모하게 만든 것들입니다.
- 공개적인 인정은 통용되는 화폐입니다. 스레드의 작성자가 마지막에 제가 하는 것보다 더 잘 말했습니다: "내가 옳았던 부분보다 틀렸던 부분에서 더 많은 것을 배웠습니다. 좋은 스레드였습니다."
- 버전 관리가 가능하고 수정 사항을 표시할 수 있는 (red-line-able) 요약본을 하나 유지하세요. 이는 스크롤백 (scrollback)이나 그 어떤 논문 (paper)보다 뛰어난 성능을 발휘합니다. 최신 상태를 유지하며, 편집 가능하고, 모든 참여자가 이를 수정할 권한을 갖기 때문입니다.
- 승리뿐만 아니라 귀무 결과 (nulls)도 함께 공개하세요. 처음에 게시된 stock llama.cpp의 동등성 결과 (parity result)가 바로 적절한 사람들이 참여하게 만든 이유였습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기