dev.to

A 4 GB Laptop GPU Beats a 12-Core CPU by 4.3x on Gemma 4

4GB 노트북 GPU가 Gemma 4에서 12코어 CPU보다 4.3배 빠릅니다

동일한 노트북에서 Gemma 4 소형 양자화 모델을 CPU와 4GB GTX 1650 Ti로 번갈아 서빙한 결과, GPU 디코드 속도가 평균 4.27배 빨랐습니다. 모델의 대형 임베딩 테이블이 호스트 메모리에 남는 구조 덕분에 4GB GPU만으로도 충분히 적재할 수 있었습니다.

주제
AI시스템개발 도구

AI 요약

이 글은 동일한 노트북과 동일한 llama.cpp 환경에서 작은 언어 모델을 CPU만으로 실행할 때와 4GB 노트북 GPU로 오프로딩할 때의 차이를 비교합니다. 비교 대상은 13세대 Intel Core i7-1360P 노트북에 탑재된 12코어 16스레드 CPU와 NVIDIA GeForce GTX 1650 Ti Max-Q 4096MiB입니다. CPU는 4개의 SMT P-core와 8개의 E-core로 구성되고 AVX2·AVX VNNI를 사용하며, GPU는 TU117·compute capability 7.5 기반이고 tensor core는 없습니다.

■ 한 개의 플래그만 바꾼 비교

두 실험군은 llama.cpp 동일 커밋 c6824a9를 각각 CPU 빌드와 CUDA 빌드로 컴파일해 구성합니다. 모델, 프롬프트, 엔드포인트, 스레드 설정과 컨텍스트 길이는 모두 동일하게 유지하고, 실행 명령에서 달라지는 핵심 값은 CPU의 -ngl 0과 GPU의 -ngl 99뿐입니다. 실제 실행에는 Gemma 4 E2B Q4_0 instruct GGUF, 컨텍스트 길이 8192, 입력·출력 임베딩을 위한 f16 설정, flash attention 활성화, CPU 스레드 설정 -t 4 -tb 8, 단일 병렬 스트림 --parallel 1 등이 사용됩니다.

모델은 google/gemma-4-E2B-it-qat-q4_0-gguf이며 파일 크기는 3.35GB인 quantization-aware GGUF입니다. 두 실험군은 같은 127.0.0.1:8080 엔드포인트에서 한 번에 하나씩 실행했고, CPU 실험을 먼저 진행한 뒤 양쪽 실행 사이에 120초의 cooldown을 넣었습니다. 프롬프트 길이 4종과 출력 길이 2종을 조합한 8개 셀을 만들고 각 셀을 3회 반복했으며, 동시성은 1로 고정했습니다. 디코드 속도는 두 장치에서 공통으로 측정할 수 있는 지표인 SSE 스트림 기반 client-side inter-token rate로 계산했습니다.

■ 디코드는 평균 4.27배, 전체 응답은 3.81배 빨랐습니다

94토큰 입력과 32토큰 출력에서는 CPU 디코드가 17.61tok/s, GPU가 71.22tok/s로 4.04배 차이를 보였습니다. 입력 94토큰·출력 128토큰에서는 17.22tok/s 대 71.20tok/s로 4.13배였습니다. 입력 길이가 516토큰으로 늘어나면 CPU는 16.16~16.39tok/s, GPU는 69.49~69.93tok/s를 기록해 4.24~4.33배 차이가 났습니다.

998토큰 입력에서는 CPU가 15.81~16.10tok/s, GPU가 68.52~68.60tok/s로 4.26~4.34배 차이를 보였습니다. 가장 긴 1959토큰 입력에서도 CPU는 15.66~15.80tok/s, GPU는 67.61~67.64tok/s를 기록했고, 격차는 4.28~4.32배였습니다. 8개 셀의 중앙값으로 계산하면 디코드는 4.27배, prefill은 3.63배, end-to-end 성능은 3.81배입니다. 각 셀에서 3회 반복 결과의 최대 편차는 CPU 6.29%, GPU 0.84%였으며, 전체 디코드 비율은 4.04~4.34배 범위에 들어왔습니다.

TTFT(Time To First Token)는 입력 길이에 따라 크게 증가했습니다. CPU의 TTFT는 입력이 94토큰일 때 1143~1219ms였지만 1959토큰에서는 23773~23926ms까지 늘어났습니다. GPU는 같은 구간에서 385ms에서 6522ms로 증가했습니다. 반면 디코드 속도는 입력 길이가 94토큰에서 1959토큰으로 21배 늘어나는 동안 양쪽 모두 비교적 평평하게 유지됐습니다.

■ 디코드와 prefill이 입력 길이에 다르게 반응하는 이유

글에서는 디코드가 매 토큰마다 모델 전체를 읽어야 하므로 메모리 대역폭에 지배되고, prefill은 입력 프롬프트 전체를 대상으로 연산하므로 계산량에 지배된다고 설명합니다. 따라서 accelerator는 두 단계 모두를 빠르게 만들지만, 같은 방식으로 개선하는 것은 아닙니다. 단일 숫자만 인용하는 벤치마크는 디코드와 prefill의 차이를 숨길 수 있습니다.

입력 컨텍스트가 커질수록 디코드 격차가 94토큰의 4.04배에서 998토큰의 4.34배로 증가한 점도 관찰됐습니다. CPU는 KV cache가 커질 때 상대적으로 더 불리해졌고 GPU 성능은 거의 유지됐습니다. 다만 CPU affinity를 설정하지 않았기 때문에 prefill 결과는 CPU에 불리하게 측정됐을 가능성이 있습니다. 별도의 llama-bench sweep에서는 이 다이에서 affinity 설정이 prefill을 1.61배 개선했으며, llama.cpp가 작업을 균등하게 나누고 가장 느린 스레드의 barrier를 기다리기 때문이라고 설명합니다. 따라서 3.63배의 prefill 격차는 상한선이며, 디코드는 스레드 수의 영향이 작아 4.27배 결과는 그대로 유지된다고 봅니다.

■ 4GB GPU에 모델이 들어간 이유는 파일 구조에 있습니다

실행 중 GPU가 점유한 메모리는 1598MiB였습니다. 파일 전체가 4GB GPU에 올라간 것이 아니라, 3.334GB의 tensor 데이터 중 약 1.08GB인 Q4_0 transformer 본체가 GPU에 상주했습니다. 여기에 약 60MiB의 KV cache와 compute buffer가 더해졌습니다. 글은 이 모델에 필요한 용량보다 GPU가 대략 2.5배 크다고 설명합니다.

핵심은 embedding tensor입니다. 두 embedding tensor는 Q6_K이며 전체 tensor bytes 중 2.257GB를 차지합니다. 그중 가장 큰 per_layer_token_embd는 1.93GB로 파일의 58%를 차지하지만, llama.cpp의 src/models/gemma4.cpp에서 TENSOR_READ_LAZY 방식으로 생성되고 mmap에서 GGML_OP_GET_ROWS를 통해 필요한 일부 행만 읽습니다. 이 데이터는 GPU로 이동하지 않습니다. 반대로 CPU 실행에서는 같은 구조가 비용으로 작용합니다. llama.cpp가 AVX2 kernel을 위해 Q4_0 weight를 interleaved layout으로 다시 패킹하면서 mmap의 본체가 anonymous memory로 복사되고, CPU 프로세스의 RssAnon은 1,202,152kB에 달합니다.

■ 장치와 실행 상태를 런타임에 검증했습니다

두 실행은 단순히 명령행의 라벨을 믿지 않고 실제 프로세스 상태를 확인했습니다. 실행 중인 바이너리는 /proc/<pid>/exe, 실제로 로드된 ggml backend는 /proc/<pid>/maps, 실제 -ngl 값은 /proc/<pid>/cmdline에서 읽어 각 보고서에 기록했습니다. llama.cpp가 backend를 dlopen하므로 ldd만으로는 CUDA backend 존재 여부를 확인할 수 없기 때문입니다.

CUDA backend가 maps에 나타나더라도 -ngl 0으로 실행하면 계산이 CPU에서 이뤄질 수 있으므로, GPU 실험의 판정에는 GPU backend가 매핑되어 있고 레이어가 GPU에 할당됐는지도 포함했습니다. CPU 실험은 -ngl 0이라는 값만 믿는 대신 프로세스에서 GPU 장치를 완전히 숨겼습니다. 각 실행 파일의 SHA-256도 보고서에 기록했고, 두 실험의 프롬프트는 device-neutral filler를 사용해 바이트 단위로 동일하게 맞췄습니다.

■ 결과를 해석할 때의 범위와 한계

이 결과는 한 대의 노트북, 하나의 GGUF 모델, llama.cpp c6824a9, 8개 paired cell, 셀마다 3회 반복, 동시성 1이라는 범위에서 얻은 값입니다. GGUF는 양쪽 모두 warm page cache 상태였으므로 cold start 성능을 보여주지 않습니다. lazy embedding에 대한 설명도 dropped-cache 실험이 아니라 소스 코드와 resident-memory 수치를 바탕으로 확인했습니다.

CPU 실험이 먼저 실행됐고 12개 코어를 포화시켰습니다. CPU와 Max-Q GPU가 같은 thermal envelope를 공유하는 상황에서 GPU 실험은 따뜻해진 패키지에서 시작했으며, 120초 cooldown은 측정으로 검증한 값이 아니라 정해진 시간입니다. 이런 조건이 결과에 영향을 줬다면 GPU 성능을 낮추는 방향일 수 있다고 설명합니다. 또한 CPU affinity는 양쪽 모두 고정하지 않았고, 단일 스트림만 사용했습니다. 글의 결론은 4GB GPU가 모든 소형 모델에서 일반적으로 빠르다는 것이 아니라, 이 Gemma 4 checkpoint의 embedding 구조와 llama.cpp 실행 방식이 결합된 조건에서 4GB 카드도 충분한 메모리 여유를 갖고 CPU보다 평균 4.27배 빠른 디코드를 제공했다는 것입니다.

원문: dev.to / 번역·요약: Trawling