GPU 없이 2016년형 Xeon과 DDR3 메모리에서 Gemma 4를 읽기 속도로 구동하기 위한 추론 최적화 설정을 살펴봅니다.
2026년 06월 01일 게시
Christina Emilie Sørensen 작성
17분 읽기
이전 글에서는 Gemma 4의 MTP 드래프터를 양자화하고 검증기와 짝지우는 방법을 다뤘습니다. 이번 글은 이 결과물을 돌릴 법하지 않은 머신에서 실행하는 이야기입니다.
저에게는 재활용 서버가 있습니다. 장점이라면 무려 128 GB RAM이지만 DDR3입니다… 이 RAM은 현재 최고급 노트북 RAM보다 5-6배 느립니다. 2016년형 Intel Xeon E5-2620 v4 하나도 달려 있는데, 제 노트북 CPU보다 약 5배 느립니다…
아, 그리고 언급했듯이 GPU가 없습니다. 아니요, 이 Xeon에는 내장 GPU도 없습니다.
그래도 제 말 좀 들어보세요…
여기서 그냥 ollama를 꺼내 들면 어떨까요? 음… 이전 블로그 글에서 설명했듯, 그럴 수 없습니다. 필요한 모델 지원을 추가한다면 6개월 뒤에도 가능할지 운이 좋아야 할 겁니다. 아예 지원하지 않을 수도 있습니다. 설령 지원하더라도 ollama는 이것을 제대로 돌리기 위한 충분한 조절 장치를 노출하지 않습니다. 표준 llama-cpp조차 마찬가지입니다.
하지만. 그게 왜 우리를 멈추게 해야 할까요?
이전 글 몇 개가 너무 높은 수준이었다는 피드백을 받았습니다. 여기서는 합리적으로 가능한 한 명확하게 설명해 보겠습니다. 기술 업계 종사자이거나 컴퓨터를 조립하고 ChatGPT 같은 것을 써 본 Linux 애호가라면, 대부분 이해하기 쉬울 것입니다.
그러니 배경을 완전히 잡아 봅시다. lscpu 기준 하드웨어는 다음과 같습니다.
LLM 추론에서 제한 자원은 메모리 대역폭입니다. 생성되는 모든 토큰은 수 GB의 가중치를 RAM에서 CPU 캐시로 실어 와야 합니다.
ChatGPT 같은 도구를 사용하며 텍스트가 화면에 단어 단위로 흘러나오는 모습을 볼 때, 여러분은 “디코더 패스”를 보고 있는 것입니다. 이 단계에서 모델은 출력물을 한 조각씩, 즉 “토큰” 단위로 생성합니다.
이 단계에서는 시스템의 순수 처리 능력이 병목인 경우가 드뭅니다. 대신 한계는 메모리 대역폭입니다. 다음 단어를 계산하려면 프로세서는 막대한 양의 데이터를 계속 끌어와야 합니다. 그 데이터는 모델이 학습한 지식을 담고 있는 “가중치”입니다. 프로세서는 이를 메모리에서 연산 코어로 옮깁니다.
프로세서는 필요한 행렬 연산을 매우 빠르게 수행하므로, 다음 가중치 덩어리가 메모리 버스를 가로질러 물리적으로 이동하기를 기다리며 놀고 있게 됩니다. 전통적인 소프트웨어 용어로 디코딩은 연산 바운드가 아니라 메모리 바운드가 매우 강합니다.
이것이 이른바 “메모리 월”이며, Xeon이든 H100이든 오늘날 가장 큰 성능 장벽 중 하나입니다.
GPU 없는 DDR3 머신에서 llama-cli를 순진하게 실행하면, 실행할 수 있다 해도 끔찍하게 느립니다. 일반적인 GPU 사용 사례에 맞춰 최적화되어 있고, 개선 여지를 많이 남기기 때문입니다. 게다가 현재 최첨단 대규모 실행 방식이 쓰는 실제 최적화 대부분도 없습니다.
해결책은 ik_llama.cpp가 노출하는 모든 최적화 레버를 당기는 것입니다. 그중 다수는 약간 난해합니다.
이것을 실제로 실행하게 만드는 마법 주문은 다음과 같습니다.
llama-cli \
--model gemma-4-26B-A4B-it-Q8_0.gguf \
--model-draft gemma-4-26B-A4B-it-assistant-GGUF/\
wikitext-2-raw_ik-llama-mtp_drafter-conservative/\
gemma-4-26B-A4B-it-assistant-Q8_0.gguf \
--spec-type mtp --draft-max 3 --draft-p-min 0.0 --spec-autotune \
-cnv --color --jinja --special \
-sm graph -smgs -sas -mea 256 --split-mode-f32 \
--temp 0.7 -t 8 --parallel 8 \
--cpu-moe --merge-up-gate-experts \
--flash-attn on --mla-use 3 \
--mlock --run-time-repack --no-kv-offload
ollama 같은 블랙박스 도구에서는 이 줄을 결코 보지 못합니다. 노후 하드웨어에서는 각 플래그가 무엇을 하는지 이해해야 합니다. 절반은 적용되지 않으며, 엔진은 지나가듯 그 사실을 알려 주기 때문입니다.
추측 디코딩.
--spec-type mtp --draft-max 3 --draft-p-min 0.0 --spec-autotune
이는 26B 검증기를 이전 글의 소형 드래프터와 짝지웁니다. 드래프트당 최대 세 토큰(--draft-max 3), 모든 확률 수용(--draft-p-min 0.0), 그리고 워크로드별로 체인 길이를 조정하는 --spec-autotune입니다.
이는 앞서 다룬 메모리 바운드 디코더 패스 논의와 직접 연결됩니다.
모델이 긴 추론 체인을 사용할 때는 그 “사고” 토큰을 하나씩 생성하고 있습니다. 내부 추론이 사용자에게 숨겨지고 짧은 최종 답변만 보이더라도, 하드웨어는 숨겨진 체인의 토큰 하나하나에 대해 여전히 전체 디코더 패스를 수행해야 합니다.
사실 추측 디코딩은 “메모리 월”을 우회하기 위해 AI 업계가 발명한 가장 뛰어난 소프트웨어 우회책 중 하나이며, spec autotune은 그로부터 최대 속도를 짜내는 방법입니다.
추측 디코딩의 근거는 GPU보다 CPU에서 더 강합니다. CPU 연산은 검증기 가중치를 캐시로 스트리밍하는 비용에 비해 저렴하므로, 활성 레이어가 L3에 쉽게 들어가는 작은 드래프터에 추가 사이클을 쓰면 아주 적은 한계 비용으로 토큰을 얻습니다. 드래프터의 작업 세트는 L3에 들어갑니다. 반면 검증기는 모든 것을 넘쳐납니다.
CPU 및 MoE 라우팅.
--cpu-moe --merge-up-gate-experts -t 8 --parallel 8
Gemma 4 26B-A4B는 전문가 128개를 보유하며 토큰당 8개가 활성화되어, 총 ~25.2B 중 약 3.8B의 활성 파라미터를 갖습니다. --cpu-moe는 CPU 캐시 계층 구조에 맞춰 라우팅을 조정합니다.
CPU는 GPU와 메모리를 매우 다르게 다룹니다. GPU에는 초고속 High-Bandwidth Memory (HBM)라는 거대한 풀이 있는 반면, CPU는 프로세서 칩에 직접 내장된 작고 매우 빠른 “캐시”(L1, L2, L3)에 의존합니다.
MoE 모델에서는 128개 전문가 사이를 계속 오가면 “캐시 스래싱”이 발생할 수 있습니다. CPU가 끊임없이 캐시를 비우고 훨씬 느린 메인 시스템 RAM에서 새 가중치를 가져와야 하는 현상입니다(보통 DDR4/DDR5지만, 우리는 DDR3입니다!).
이 플래그는 라우터에게 전문가 선택을 더 영리하게 하여, 가중치가 가능한 한 오랫동안 CPU의 로컬 캐시 안에 깔끔하게 머물도록 순서를 최적화하라고 지시합니다.
--merge-up-gate-experts는 전문가별 프로젝션 두 개를 하나의 matmul로 융합하며, 로그도 이를 확인합니다.
fused_up_gate = 1
이는 앞서 논의한 메모리 대역폭 병목을 우회하는 소프트웨어 기법입니다.
전문가 내부에서 수학 연산은 데이터가 서로 다른 레이어를 거치도록 요구합니다. 보통 프로세서는 “up projection”을 계산하고, 결과를 메모리에 기록한 뒤, “gate projection”의 가중치를 불러와 계산하고, 둘을 결합합니다. 그러려면 데이터를 메모리 버스 너머로 여러 번 옮겨야 합니다.
메모리 버스를 두 번 따로 오가는 대신, 이 연산들은 단일 단계로 결합됩니다.
-t 8은 물리 코어 수와 일치합니다. 이 머신에는 SMT 스레드가 16개이지만 코어는 8개뿐입니다. 메모리 바운드 워크로드에서 스레드를 과도하게 할당하면 처리량은 늘지 않고 스케줄링 비용만 추가됩니다. 코어들은 서로가 아니라 DDR3을 기다리고 있기 때문입니다.
메모리 고정, 재패킹, KV 캐시.
--mlock --run-time-repack --no-kv-offload
--run-time-repack은 추론 직전에 가중치 행렬을 CPU 캐시 레이아웃에 맞추어 메모리에서 재구성합니다. 로그도 이를 확인합니다.
============ Repacked 265 tensors
프로세서에는 캐시(L1, L2, L3)라고 하는 자체 초고속 내장 메모리가 있습니다. 하지만 이 캐시는 데이터가 매우 특정한 형태와 크기로 공급되기를 기대합니다.
AI의 가중치 행렬이 범용 레이아웃으로 시스템 RAM에 놓여 있다면, CPU는 데이터를 조각조각 어색하게 끌어와야 하며 CPU가 멈추는 “캐시 미스”가 발생합니다. --run-time-repack은 엔진에게 시작 중 몇 초를 써서 RAM에 있는 거대한 숫자 표를 물리적으로 재구성하여 CPU가 받아들이려는 방식에 완벽히 맞추라고 지시합니다. 실제 텍스트 생성 중 최대 메모리 대역폭을 보장하기 위해 초기에 작은 시간 비용을 치르는 것입니다.
--mlock은 OS가 모델의 일부를 디스크 스왑으로 옮기지 못하도록 모델을 RAM에 고정하는 기능입니다.
mlock은 “memory lock”의 줄임말입니다. 놀랍죠, 알아요! 표준 운영체제에서는 시스템 RAM이 부족해지기 시작하면, 몇 초 동안 사용되지 않은 데이터를 조용히 물리 하드 드라이브로 “스왑”(또는 페이징)합니다.
OS가 AI 가중치 27GB를 디스크로 스왑하려 하면, 시스템이 다시 읽어 오려다 막히는 동안 생성 속도는 즉시 0으로 떨어집니다. --mlock은 Linux 커널에게 이렇게 말합니다. “이 27GB를 물리 RAM에 엄격히 고정해. 절대 디스크로 옮기지 마.”
주의하지 않으면 다음과 같은 메시지를 보게 됩니다.
warning: failed to mlock 27628376064-byte buffer
(after previously locking 0 bytes): Cannot allocate memory
Try increasing RLIMIT_MEMLOCK ('ulimit -l' as root).
플래그는 문제없습니다. 커널 측 memlock 한도가 27 GB 버퍼를 고정하기에 충분히 높게 설정되지 않은 것입니다. 이는 전혀 LLM 형태의 문제가 아니라 ulimit 기본값이며, 블랙박스 도구가 애초에 이 최적화를 요청하지 않음으로써 가려 버리는 종류의 함정입니다.
잠시 생각해 보세요. 많은 도구는 기본적으로, 최선의 선택이라고 판단되면 모델을 스왑에 넣는 데 전혀 문제가 없습니다. 이것이 성능을 얼마나 해칠지 상상할 수 있을 겁니다…
--no-kv-offload는 엔진에게 KV 캐시를 위한 GPU를 찾지 말라고 지시합니다. 찾을 GPU가 없지만, 이 플래그는 검사를 단축합니다.
KV (Key-Value) 캐시는 AI의 단기 기억입니다. 현재 대화의 문맥을 저장하므로 모델이 새 토큰마다 전체 프롬프트를 다시 읽지 않아도 됩니다.
KV 캐시는 계속 읽고 쓰기 때문에 AI 엔진은 보통 우리보다 훨씬 빠른 메모리를 가진 GPU로 이를 “오프로드”하려 합니다.
이 특정 설정은 순수하게 CPU에서 동작하도록 고도로 최적화되어 있으므로, 엔진이 존재하지 않는 GPU를 찾으려고 하드웨어 버스를 검색하게 두는 것은 시간 낭비이고 오류를 낼 수도 있습니다. 이 플래그는 그 검사를 명시적으로 단축하고, 단기 기억을 가중치와 함께 시스템 RAM에 보관하라고 엔진에 지시합니다.
그래프 레이아웃.
이해하기 쉽게 만들려고 최선을 다했지만, 이 부분은 단일 블로그 글에서 설명하기가 정말 어렵습니다.
이제 흑마법의 영역으로 갑시다. 최첨단 AI 소프트웨어에서 흔한 좌절은 엔진이 너무 빨리 개발되어 개발자들이 공식 문서를 작성할 시간이 없다는 점입니다. 엔진 최적화 방법을 알고 싶다면 원시 코드를 파헤치거나 개발자들 사이의 Github Pull Request (PR) 댓글을 읽어야 합니다.
-sm graph -smgs -sas -mea 256 --split-mode-f32
이 플래그들은 계산 그래프가 메모리 영역에 어떻게 할당되는지를 제어합니다. 일부 문서가 있더라도 전체 문서는 궁극적으로 코드에 있습니다.
플래그 -sm graph는 엔진에게 Graph 모드에서 Split Mode를 사용하라고 지시합니다(업계에서는 흔히 Tensor Parallelism으로 알려져 있습니다). 이는 거대한 수학 작업을 여러 프로세서나 메모리 영역(여러 CPU 소켓 또는 GPU 등)에 어떻게 나누는지에 관한 것입니다.
레이어 분할(기본값/대체): 엔진은 모델을 수평으로 자릅니다. 프로세서 A가 레이어 1–10을 계산한 뒤 시스템 버스를 통해 데이터를 프로세서 B로 보내고, B가 레이어 11–20을 계산합니다. 프로세서 A가 작업하는 동안 프로세서 B는 놀고 있습니다.
그래프 분할(목표): 엔진은 계산 그래프를 수직으로 자릅니다. 프로세서 A와 B가 레이어 1의 서로 다른 절반을 정확히 동시에 계산하고, 답을 결합한 뒤 함께 레이어 2로 넘어갑니다. 이렇게 하면 모든 하드웨어가 동시에 100% 가동되어 생성 속도가 크게 향상됩니다.
이 실행에서는 엔진이 거부합니다.
=======================================================
Split mode 'graph' is not supported for Gemma4 external MTP
=> changing split mode to 'layer'
=======================================================
MTP는 네트워크의 맨 끝에 훨씬 더 복잡한 수학 연결망을 만들기 때문에, 이 추론 엔진은 아직 MTP 아키텍처를 안전하게 “그래프 분할”(수직 슬라이싱)하는 지원을 갖추지 못했습니다. 엔진이 시작되면 MTP 레이어를 감지하고, -sm graph가 수학 연산을 망가뜨릴 것임을 파악한 뒤, 모델이 계속 실행될 수 있도록 더 느린 순차적 레이어 분할로 안전하게 낮춥니다.
미래에는 매우 유용할 가능성이 크므로 포함했습니다. 더 최신 버전으로 작업한다면 운을 시험해 보세요.
-sm graph는 비활성화되었지만, 다른 플래그들은 엔진의 메모리 관리 방식에 여전히 적용됩니다.
-sas (Split Across Sockets): 서버 메인보드의 서로 다른 물리 CPU 소켓(NUMA 노드)에 작업 부하를 어떻게 나눌지 엔진에 명시적으로 알려 줍니다. 현재 CPU는 하나뿐이지만 나중에 더 추가할 수도 있으므로 좋은 최적화입니다. 다만 그렇게 한다면 안전을 위해 반드시 벤치마크하세요. 오래된 보드는 오늘날의 가정을 깨뜨릴 수 있습니다.
--split-mode-f32: 데이터가 프로세서 간에 분할되면 다시 이어 붙여야 합니다. 이 플래그는 그 중간 연결 지점이 32-bit floating-point 정밀도(더 높은 품질의 연산)를 사용하도록 강제합니다. 분할 중 반올림 오류 때문에 AI가 지능을 잃거나 환각하는 것을 방지합니다.
그리고 다음이 보여도 걱정하지 마세요.
Oops: tensor with strange name rope_freqs.weight
이름이 이상합니다. 이상한 이름이 여기서 우리를 멈추게 하지는 않을 겁니다. :D
어텐션.
보세요. ik_llama.cpp의 제작자 ikawrakow는 “미쳤다”라는 말로도 부족합니다.
Kawrakow는 무거운 문맥 처리를 하는 동안 GPU가 필요 없도록 Flash Attention을 처리하는 맞춤 CPU 커널을 작성했습니다.
덕분에 보통 GPU에서만 하는 일을 할 수 있습니다.
--flash-attn on --mla-use 3
Flash Attention은 전체 어텐션 행렬을 구체화하지 않도록 어텐션 softmax와 matmul을 융합합니다. 당연하죠, 누구나 아는 얘기지만 설명해 보겠습니다.
텍스트를 생성하려면 AI는 프롬프트의 모든 단어가 다른 모든 단어와 어떻게 관련되는지 계산해야 합니다. 수학적으로 이는 크기가 인 격자를 만듭니다(은 토큰 수입니다).
AI에 짧은 문장을 주면 그 격자는 작습니다. 하지만 100,000단어 문서를 넣으면 이 행렬은 100억 개 셀로 폭발합니다. 보통 프로세서는 이 거대한 행렬을 계산하고 “구체화”합니다. 즉, 다음 단계를 위해 즉시 다시 읽을 거대한 격자 전체를 메인 시스템 RAM에 물리적으로 기록합니다.
Flash Attention은 Kernel Fusion 기법을 어텐션 메커니즘에 적용합니다. 어텐션 점수를 작은 청크로 계산하고 수학 연산(softmax)을 융합하여 거대한 행렬이 실제로 RAM에 기록되지 않도록 합니다. 이는 프로세서의 초고속 로컬 캐시 내부에서 완전히 계산되고 소비됩니다.
Flash Attention은 원래 GPU 하드웨어가 메모리 블록을 다루는 방식에 의존하기 때문에 엄격히 GPU용으로 발명되었습니다. 이처럼 복잡하고 하드웨어 특화적인 최적화를 표준 CPU에서 작동하도록 성공적으로 포팅한 것은 엄청난 소프트웨어 엔지니어링 성취입니다. 잘했습니다 ikawrakow.
--mla-use 3는 Multi-Head Latent Attention을 활성화합니다. 앞서 KV Cache(모든 단어마다 전체 프롬프트를 다시 읽지 않게 해 주는 대화에 관한 AI의 단기 기억)를 논의했습니다.
표준 아키텍처에서는 모든 토큰의 원시 Key 및 Value 데이터를 저장하면 RAM을 엄청나게 빠르게 소모합니다. Multi-Head Latent Attention (MLA)은 이 단기 기억을 크게 압축하는 획기적인 아키텍처입니다. 모든 토큰의 원시 데이터를 저장하는 대신, Keys와 Values를 훨씬 더 작고 밀도 높은 수학적 표현, 즉 “잠재” 공간으로 압축합니다.
이것은 KV 캐시의 메모리 점유량을 크게 줄여, 시스템 RAM이 고갈되지 않고도 모델이 방대한 대화를 기억할 수 있게 합니다. 플래그 --mla-use 3는 엔진에게 이 압축의 특정 티어 또는 커널 구현을 활성화하라고 지시할 뿐입니다.
하지만 이 모든 것은 그래프 분할 모드처럼 실험적인 것일 뿐일까요? 아닙니다. 로그는 둘 다 적용되었음을 확인합니다.
flash_attn = 1
fused_moe = 1
fused_up_gate = 1
로그의 메모리 계산입니다.
------------------- Layer sizes:
Layer 0: 825.98, 2048.00, 2873.98 77.00 MiB
...
Layer 29: 840.59, 1024.00, 1864.59 77.00 MiB
Layer 30: 748.00, 435.00, 1183.00 MiB (output layer)
--------------------------------------------------------------------------
Total : 24852.46, 56755.00, 81607.46 MiB
Memory required for model tensors + cache: 82355 MiB
2016년 Xeon의 DDR3에서 82 GB 점유량입니다. 전체 262K 문맥에서 가중치는 약 25 GB, KV 캐시는 56 GB입니다. KV 캐시가 모델보다 큽니다.
작동하는 구성에 25개 플래그가 필요하고 그중 절반은 문서화되지 않았으며 4분의 1은 조용히 실패한다는 점은, 첫 글에서 설명한 사용성 해자의 합리적인 정의입니다.
엔진은 25B 파라미터 MoE를 불러오고, MTP 드래프터를 상대로 추측 디코딩을 수행하며, 해당 아키텍처가 발명되기도 전에 이미 오래된 하드웨어에서 읽기 속도로 텍스트를 생성합니다.
일주일 전 이 연재를 시작했을 때, 로컬 오픈 웨이트 AI의 상황은 암울해 보였습니다. 우리는 업계가 좋아하는 마케팅 포장, 즉 보정되지 않은 거대한 가중치 파일을 저장소에 올리는 것이 “오픈 소스”에 해당한다는 발상 뒤의 장막을 걷어냈습니다. 누락된 문서, 조용한 기본값, 사용자 친화성이라는 명분 아래 성능을 죽이는 결정을 숨기는 블랙박스 래퍼로 만들어진 거대한 사용성 해자를 살펴봤습니다.
두 번째 글에서는 소매를 걷어붙이고 진창으로 들어갔습니다. 눈에 잘 띄지 않고 병합되지 않은 pull request를 찾아냈고, 특수 포크(ik_llama.cpp)를 컴파일했으며, 고정밀 추측 디코딩 드래프터를 만들기 위해 양자화의 표준 논리를 뒤집었고, 인프라 데이터 유출을 GGUF 메타데이터에서 제거하는 맞춤 스크립트를 작성했습니다.
마지막으로 이 글에서는 말뿐이 아님을 증명했습니다. 옷장에서, 아니 무덤에서 2016년형 엔터프라이즈 유물을 끌어냈습니다. 고통스러울 정도로 느린 DDR3 RAM에서 작동하는 단일 Intel Xeon, 언급할 GPU는 전혀 없는 장비였습니다. 그리고 이를 강제로 최첨단 260억 파라미터 Mixture-of-Experts 아키텍처를 읽기 속도로 실행하게 했습니다. 문제에 이색 하드웨어를 던져 넣지 않고 해냈습니다. 대신 배포 파이프라인을 진지하게 다루고, 아키텍처를 물리 하드웨어에 직접 매핑했으며, 메모리 할당을 조정하고 CPU 캐시 최적화의 절대적 한계를 열었습니다.
여기서 교훈은 간단합니다. 최첨단 AI를 로컬에서 실행하는 병목은 실리콘에만 있지 않습니다. 추론 엔진이 실제로 어떻게 작동하는지 깊이 이해해야 한다는 데 있습니다.
데이터 센터 그래픽 카드 클러스터, 기업 API 토큰 또는 막대한 예산은 특정 워크로드에 모두 극도로 유용하지만, 오픈 모델이 다루는 작업이라면 리퍼비시 하드웨어와 블랙박스 도구가 운전대를 잡게 두지 않겠다는 의지만 있으면 됩니다. 올바른 포크, 보정된 양자화 모델, 그리고 장비 안의 메모리 아키텍처에 대한 이해로 무장하면 사용성 해자는 사라집니다.
Open Weight AI의 최첨단은 유료 장벽이나 모델 제공업체 뒤에 잠겨 있지 않습니다. 이미 홈랩을 운영하고 있다면, 그것은 10년 된 서버의 명령줄 바로 այնտեղ 있습니다.
해자 반대편에 오신 것을 환영합니다. 이제 양자화 모델을 다운로드하고 직접 손을 더럽혀 보세요.
읽어 주셔서 감사합니다 :D