A 4 GB Laptop GPU vs a 6-Core CPU on Gemma 4, Re-Measured in ABBA Order: 4.1x A developer benchmarked a 4 GB GeForce GTX 1650 Ti Max-Q against an Intel Core i7-10750H in the same Lenovo Yoga 9 chassis for serving the Gemma 4 E2B QAT Q4_0 GGUF via llama.cpp, finding the GPU decodes 4.14x faster, prefills 3.42x faster and completes requests 3.62x faster end to end. Running the passes in ABBA order to cancel thermal drift moved the decode ratio only from 4.09x to 4.17x, indicating a single-order benchmark on this laptop is off by roughly 2%. Both builds came from one llama.cpp commit (f95b0d9, build 318) on Debian sid with gcc 16.2.0 and CUDA 13.4, differing by a single flag, and the project also ships Python MCP tools for managing the llama.cpp deployment. This article provides a step by step guide to measuring a laptop GPU against the CPU in the same chassis for serving Gemma 4 E2B through llama.cpp, with both builds on a fresh Debian sid toolchain and the passes run in ABBA order so heat cannot pick the winner. A suite of Python MCP tools is built to simplify management of the llama.cpp hosted deployment. https://github.com/xbill9/gemma4-dev https://github.com/xbill9/gemma4-dev The GTX 1650 Ti decodes 4.14x faster than the i7-10750H it shares a chassis with, prefills 3.42x faster and finishes requests 3.62x faster end to end. Running the two devices in one order and then the other moves that decode ratio from 4.09x to 4.17x, so a single-order benchmark on this laptop is off by about 2%. The same 3.35 GB quantization-aware GGUF, google/gemma-4-E2B-it-qat-q4 0-gguf , served by llama-server twice on one laptop: once on the CPU, once on the GPU. Both builds come from one llama.cpp commit and the two command lines differ by one flag. An earlier run of this comparison is published as A 4 GB Laptop GPU Beats a 12-Core CPU by 4.3x on Gemma 4 https://dev.to/gde/a-4-gb-laptop-gpu-beats-a-12-core-cpu-by-43x-on-gemma-4-4150 . This one re-measures it after the laptop moved to Debian sid, with three changes: a newer llama.cpp commit built on gcc 16.2 and CUDA 13.4, thread flags set for this CPU's real core count, and a run order that cancels thermal drift. nvcc 13.4 here cmake and a host compiler CUDA accepts — gcc 16.2.0 here python3 , no virtualenv local-llamacpp-1650ti-2b-q4 0/ and local-llamacpp-cpu-2b-q4 0/ side by side | Machine | Lenovo Yoga 9 15IMH5 | | CPU | Intel Core i7-10750H, 6 cores / 12 threads, AVX2 | | GPU | GeForce GTX 1650 Ti with Max-Q Design, 4096 MiB, compute capability 7.5 | | OS | Debian GNU/Linux forky/sid, kernel 7.2.6 | | Compiler | gcc 16.2.0 | | CUDA | 13.4 V13.4.92 | | llama.cpp | f95b0d9 , build 318 | The CPU and the Max-Q card sit under one cooling system, which is why the run order below matters. A distribution upgrade can move the compiler past what CUDA accepts, and a CUDA upgrade can drop old GPUs. Both were close here. bash $ nvidia-smi --query-gpu=name,compute cap,memory.total,driver version --format=csv name, compute cap, memory.total MiB , driver version NVIDIA GeForce GTX 1650 Ti with Max-Q Design, 7.5, 4096 MiB, 615.71.09 $ nvcc --list-gpu-arch compute 75 compute 80 compute 86 ... The card is compute capability 7.5, and compute 75 is the first entry CUDA 13.4 lists. This laptop's GPU is the oldest architecture the current toolkit still builds for. bash $ gcc --version gcc Debian 16.2.0-3 16.2.0 $ grep -n 'later than 16' /usr/local/cuda/include/crt/host config.h 137: error -- unsupported GNU version gcc versions later than 16 are not supported ... CUDA 13.4 accepts gcc up to 16, and sid ships 16.2. Each rig's Makefile carries its own build target. The GPU build targets only this card's architecture, sm 75; the CPU build turns every GPU backend off. local-llamacpp-1650ti-2b-q4 0 cmake -S ~/llama.cpp -B ~/llama.cpp/build -DGGML CUDA=ON -DCMAKE CUDA ARCHITECTURES=75 -DGGML NATIVE=ON -DCMAKE BUILD TYPE=Release cmake --build ~/llama.cpp/build --config Release -j6 --target llama-server llama-bench local-llamacpp-cpu-2b-q4 0 cmake -S ~/llama.cpp -B ~/llama.cpp/build-cpu -DGGML CUDA=OFF -DGGML VULKAN=OFF -DGGML NATIVE=ON -DCMAKE BUILD TYPE=Release cmake --build ~/llama.cpp/build-cpu --config Release -j6 --target llama-server llama-bench Both binaries report the same commit and compiler: $ ~/llama.cpp/build/bin/llama-server --version version: 0.4.1-dev build 318, commit f95b0d9 built with GNU 16.2.0 for Linux x86 64 $ ~/llama.cpp/build-cpu/bin/llama-server --version version: 0.4.1-dev build 318, commit f95b0d9 built with GNU 16.2.0 for Linux x86 64 And only one of them can see the card: $ ~/llama.cpp/build/bin/llama-server --list-devices Available devices: CUDA0: NVIDIA GeForce GTX 1650 Ti with Max-Q Design 3732 MiB, 3671 MiB free $ CUDA VISIBLE DEVICES= ~/llama.cpp/build-cpu/bin/llama-server --list-devices Available devices: none cmake --build -j with no number starts every CUDA source file at once. llama.cpp has about 188 of them, each nvcc forks cicc and cudafe++ , and a 15 GiB laptop runs out of memory. At -j6 the full CUDA build peaked at 4.0 GiB and never touched swap, so the limit costs nothing. Both Makefiles pass -j$ BUILD JOBS with a default of 6. After a compiler or CUDA upgrade, delete the build directory before configuring. A stale CMakeCache.txt keeps the old compiler, and /usr/local/cuda is a symlink, so the cached path looks current. Both devices serve on 127.0.0.1:8080 , so the endpoint, the prompts and the benchmark script stay fixed while the device changes underneath. The flags match except for -ngl , the number of layers offloaded to the GPU: llama-server -m gemma-4-E2B q4 0-it.gguf --host 127.0.0.1 --port 8080 \ -ngl {0|99} -c 8192 -ctk f16 -ctv f16 -fa 1 -t 6 -tb 12 --parallel 1 --metrics -t 6 -tb 12 matches the CPU's six physical cores and twelve threads, and both devices get it. GPU decode barely responds to thread count, so matching them costs the GPU nothing and removes one difference between the two runs. For the GPU side there is a small wrapper, llamacpp-1650ti , that reads these values out of the rig's tpu.env , starts the server in the background and checks which device answered: bash $ llamacpp-1650ti start llamacpp-1650ti: starting /home/xbill/models/gemma-4-E2B-it-qat-q4 0/gemma-4-E2B q4 0-it.gguf on 127.0.0.1:8080 -ngl 99 llamacpp-1650ti: pid 88166, log /home/xbill/gemma4-dev/local-llamacpp-1650ti-2b-q4 0/run/llama-server.log llamacpp-1650ti: waiting up to 180s for http://127.0.0.1:8080/health 0s VRAM 3 MiB, 0 % llamacpp-1650ti: healthy after 2s -- http://127.0.0.1:8080 llamacpp-1650ti: device=gpu · pid=88166 · -ngl 99 · mapped: ggml-cuda, libcublas, libcuda, libcudart · /home/xbill/llama.cpp/build/bin/llama-server $ llamacpp-1650ti stop llamacpp-1650ti: sent SIGTERM to pid 88166; VRAM is released on exit The wrapper refuses to start if anything already holds the port, and refuses to stop the CPU build if that is what holds it. Both devices answer the same URL with the same JSON, so the HTTP response cannot say which one produced it. The rigs' attest.py reads it from /proc instead: /proc/