Running large language models locally has become increasingly practical, driven by improvements in quantization and hardware-aware optimization. As models grow in size and complexity, the choice of model format and runtime plays a central role in determining performance, memory usage, and deployment flexibility.
On Apple silicon, two options come up again and again: GGUF and MLX. Both aim to make large models usable on consumer hardware, and each takes a different route to get there. Knowing where those routes diverge is what makes the MLX vs GGUF choice tractable for a given workload.
GGUF: A portable format for quantized models
GGUF is a file format for storing models that GGML-based runtimes load, and llama.cpp is the runtime most practitioners meet first. A GGUF file holds the weights together with the metadata a runtime needs to load them, which is what lets one file travel between operating systems and hardware.
- Structured quantization. Weights are stored at reduced precision, commonly 4-bit, 5-bit or 8-bit.
- Broad compatibility. llama.cpp runs GGUF on CPUs and GPUs across Linux, Windows and macOS.
- Compact footprint. Lower precision lets larger models fit in limited memory.
The flexibility is the point. One model file can usually move between environments with little change, which keeps experimentation and deployment pipelines simple.
MLX: A framework built for Apple silicon
MLX starts from a different premise. It is an array framework for machine learning on Apple silicon, built by Apple machine learning research, and language models reach it through the mlx-lm package, which covers both text generation and fine-tuning. The design targets one hardware family instead of many.
That premise builds on the Apple M-series design, where a single pool of unified memory is shared across the CPU, the GPU and the Neural Engine.
- Hardware-aware execution. Kernels are written for the M-series GPU and dispatched through Metal.
- Group-wise quantization. Weights are quantized in small groups, each carrying its own scale and offset.
- Unified memory. CPU and GPU address the same pool, so tensors need no copy across a device bus.
An MLX build is a conversion of the original weights into MLX’s own quantized representation, distributed as safetensors rather than as a GGUF file. Its default 4-bit setting quantizes weights in groups of 64, each group storing a scale and an offset alongside the packed values. How much throughput that yields on a given machine depends on how well the runtime’s kernels exploit the layout.
Set side by side, the two are routinely treated as competing formats. The sections above show why the two sit at different levels. One is a file, the other is the machinery that runs it. Figure 1 lines the paths up level by level, from the stored weights down to the hardware they share.
Figure 1 Conceptual model
Two paths to the same silicon
Each row is one level of the stack. The two paths differ at the top and meet at the shared hardware.
The local inference stack has four levels: model weights or format, runtime or engine, compute backend, and hardware. MLX is a framework and runtime whose weights load through mlx-lm. GGUF is a file format executed by the llama.cpp engine. At the top two levels the two paths differ. At the compute level both dispatch through Metal, but the kernels differ. At the hardware level they are shared, because on Apple silicon both run mainly on the GPU through Metal and read from the same unified memory. Use the Show control to isolate one path, and select any layer for a detailed comparison.
The top two levels differ between the two paths; the compute level shares Metal but differs in kernels. The hardware at the bottom is shared. Select any layer to see how MLX and GGUF compare there.
Why performance differs
Where the two differ in throughput, the explanation usually lies in how each handles memory and computation.
Before comparing the mechanisms, it helps to see that inference is not a single speed. A run loads the model, processes the prompt, then generates tokens one at a time. Prompt processing is limited by compute and sets the time to first token. Token generation is limited by memory bandwidth and sets the streaming speed that a tokens-per-second figure reports. Figure 2 shows these phases, and how a prompt-heavy or generation-heavy workload shifts the balance between them.
Figure 2 Schematic
Where the seconds go in one run
A run moves through model load, prompt processing, and generation, and each stage is limited by something different.
An inference run has three phases. Model load reads weights from storage. Prompt processing, also called prefill, reads the whole prompt in parallel and is compute-bound. It ends at the first token, which sets the time to first token. Token generation, also called decode, produces tokens one at a time, reads the weights from memory at every step, and is bandwidth-bound. Its speed is the tokens per second figure. Choose a workload to change the balance between prompt processing and generation. A generation-heavy workload, such as the article task, is dominated by decode. The timeline proportions are schematic, drawn to show the balance between the phases.
Workload: Generation-heavy · 30 prompt tokens, 500 output tokens (illustrative)
Token flow (each block represents several tokens, schematic)
Each phase has a different bottleneck and a different metric. Select a phase to see what limits it, or press Play to watch one run from load to the last token.
Memory access patterns
On Apple silicon, both paths can place model weights in the same unified memory and run on the GPU through Metal, so neither depends on copying data across a separate device bus. The difference comes from the compute kernels and the quantization layout. MLX uses kernels and a group-wise quantization scheme written for the M-series GPU, which can improve how efficiently the available memory bandwidth is used during inference.
Compute frameworks
MLX dispatches work to the GPU through Metal, using kernels designed for the M-series GPU. The llama.cpp engine also has a Metal backend on Apple silicon, and it additionally supports CPU execution and other backends such as CUDA on non-Apple hardware. The practical difference is how much of each kernel is specialized for a single GPU family.
Quantization and bandwidth
For a quantized model generating one stream of text, memory bandwidth is usually the limiting factor rather than raw compute. Every new token reads the model weights out of memory, so the bandwidth of the system sets an upper bound on tokens per second. Both runtimes read from the same unified memory on Apple silicon, which feeds both the same bandwidth, but each build has its own ceiling, because that ceiling is the bandwidth divided by the size of that build. How close a run gets to its own ceiling depends on the efficiency of its kernels. Fewer bits per weight shrink the model and lift the ceiling, which is one reason 4-bit quantization is common for local work. Figure 3 shows the relationship.
The figure also carries a caution worth stating plainly. Equal bit width does not mean equal quantization. llama.cpp’s Q4_K_M mixture, the build usually labeled 4-bit medium, keeps some tensors at 6-bit, while MLX’s default 4-bit setting applies uniformly across groups of 64 weights. Two files can both be called 4-bit and still differ in size and in output quality.
Figure 3 Schematic model with article data
Why 4-bit generation is bandwidth-bound
The bars show the ceiling at each quantization level. The measured points show where the two runtimes actually landed.
This chart plots a theoretical decode ceiling in tokens per second against quantization in bits per weight. The ceiling equals memory bandwidth divided by the model size in memory. At four bits per weight the Phi-4-mini model of 3.8 billion parameters holds about 1.9 gigabytes of packed weights, before the scale and offset each group carries, and on an M4 Max at 546 gigabytes per second the ceiling is roughly 287 tokens per second. Eight and sixteen bits per weight give larger files and lower ceilings. The article measured 153 tokens per second for MLX and 89 for GGUF at four bits, both below the ceiling for a uniformly 4-bit build of this model, and each real build, being slightly larger, has a slightly lower ceiling of its own. Measured rates sit below a theoretical bound because of attention, the key and value cache, and the share of peak bandwidth a kernel actually reaches. Higher tokens per second is better. Ceiling values are theoretical upper bounds. The bandwidth figures are Apple specifications.
Ceiling ≈ memory bandwidth ÷ model size in memory. Higher is better. Real throughput sits below the ceiling because of attention, the key and value cache, and the share of peak bandwidth a kernel actually reaches.
Example: Phi-4-mini-reasoning on an M4 Max
GGUF vs MLXLocal LLMs
How GGUF and MLX take different paths to running large language models locally: GGUF favors portability across hardware, while MLX reshapes the model for Apple Silicon and its high-bandwidth unified memory.
GGUF vs MLX Local LLMs. How GGUF and MLX take different paths to running large language models locally: GGUF favors portability across hardware, while MLX reshapes the model for Apple Silicon and its high-bandwidth unified memory. Key topics covered: GGUF format, Quantization, MLX runtime, Unified memory, Compute frameworks, Memory bandwidth, Benchmark setup, Throughput results.
One comparison makes the difference concrete. A GGUF build and an MLX build of Microsoft’s Phi-4-mini-reasoning, a reasoning model of 3.8 billion parameters, were each run once in LM Studio on a MacBook Pro with an M4 Max chip and 128 GB of unified memory. Both were 4-bit builds and both received the same prompt.
What was measured
Held constant
- Application: LM Studio 0.3.16 (Build 8)
- Model: Phi-4-mini-reasoning, 3.8 billion parameters
- Prompt:
Write a 500 word story. - Machine: MacBook Pro, M4 Max, 128 GB unified memory
- Nominal precision: 4-bit on both sides
- Stop reason: both runs ended on the model’s end-of-sequence token
Varied by design
- The packaging: a GGUF build against an MLX build
- The engine that follows from it, since LM Studio runs a GGUF build on its llama.cpp-based engine and an MLX build on its own MLX engine
- The quantization scheme behind the shared 4-bit label
Variables this comparison holds open
The comparison fixes the application, the model and the prompt. A stricter benchmark would also pin down the following. Each can move throughput on its own, so each is an open variable.
- Engine versions inside the application, and the exact quantization variant and group size on each side
- Model file sizes on disk
- Context length setting, system prompt, and sampling parameters such as temperature and top-p
- Warm-up procedure, cache state, and the number of repetitions
- macOS version, power mode, thermal state, and background system load
Software drift matters too. LM Studio 0.3.16 was released in May 2025 and was current when these runs were made. The application has since moved to a 0.4 series that reorganized how engines are packaged, so repeating this test today would not reproduce the same software stack.
Figures 4 and 5 show the two runs as the application reported them. The statistics line under each response carries the numbers the rest of this section works from.


Results
| Metric | GGUF build | MLX build | Reading |
|---|---|---|---|
| Generation throughput higher is better | 89.32 tok/s | 153.04 tok/s | MLX +71% |
| Time to first token lower is better | 0.12 s | 0.27 s | GGUF lower |
| Tokens reported context only | 1,895 | 1,095 | Different amount of work |
| Reported thinking time context only | 15.40 s | 3.59 s | Different amount of work |
| RAM shown in the status bar context only | 3.03 GB | 2.70 GB | Undocumented reading |
Throughput counts output tokens per second during generation and excludes the prompt-processing time that the first-token figure covers. The GGUF run produced roughly 73 percent more tokens, and throughput drifts down as a sequence grows, so part of the 71 percent gap may come down to sequence length. At this model size the key and value cache is small next to the weights, which holds that share to a few percent. The application leaves the contents of the status-bar RAM reading undocumented, so it reads as indicative.
On generation throughput the MLX build led by a wide margin, at 153.04 tokens per second against 89.32, an increase of about 71 percent, though the GGUF run produced roughly 73 percent more tokens and throughput drifts down as a sequence grows, so sequence length may account for a small slice of that gap, a few percent of it. The first-token figures run the other way. The GGUF build started emitting text after 0.12 seconds and the MLX build after 0.27, though a five-word prompt leaves prefill very little to do, and both delays are short enough to feel immediate.
The two responses were not the same length. The GGUF run reported 1,895 tokens and 15.40 seconds of thinking, against 1,095 tokens and 3.59 seconds for MLX. Both returned a 500-word story, so the extra tokens went into a longer reasoning trace. That length varies from one run to the next, which is why the comparison rests on the throughput rate rather than on how long each run took from start to finish.
What the measurement supports
The measurement above is a single observation per build, with several variables that move throughput held open. The points below set out what it supports.
- One run each. Each figure is a single observation, and repeated runs are what would show run-to-run variation.
- The two runs did different amounts of work. The GGUF response ran to 1,895 tokens against 1,095 for MLX, and its reported thinking time was more than four times longer. The length of a reasoning trace varies between runs, so tokens per second is the figure that can be credited to the engine.
- Longer runs tend to decode more slowly. Throughput usually drifts down as the context grows. The GGUF run produced roughly 73 percent more tokens, so the longer sequence may explain a small part of its lower average rate. At this model size and context length the key and value cache is small next to the weights, which caps that share at a few percent.
- Quantization varies at a fixed bit width. The two 4-bit builds round weights on different schemes, which affects file size and output quality.
- Output quality is a separate axis. Both runs returned a 500-word story, and the throughput gap speaks to speed alone.
- Energy is a separate measurement. Power and battery figures need a run instrumented for them.
- One model, one machine, one release. Another chip, another model size or another version of the application could produce a different result.
MLX vs GGUF: which to choose
The decision is less a question of which wins a benchmark and more a question of which constraints bind. GGUF gives a dependable baseline on any hardware without tuning. MLX narrows the target to Apple silicon and tries to convert that focus into speed.
Reach for GGUF when
Portability is the binding constraint
- Your machines span Windows, Linux and macOS, or carry different GPUs
- CPU execution is required, or a GPU backend other than Metal
- One file that many runtimes can load is required
- You are shipping a model to hardware you do not control
Reach for MLX when
The target is Apple silicon and stays there
- Every machine you deploy to runs Apple silicon
- Generation speed drives the experience, and your own testing shows a gain for your model
- Fine-tuning is required on the same framework used for serving
- A build of the required model is published for MLX
Both paths reach the GPU on Apple silicon, so the choice rarely rests on speed alone. Where a build exists for both, measure on the machine you intend to deploy on.
Key Takeaways
GGUF is a file that stores quantized weights and metadata. The llama.cpp engine executes it. MLX is an array framework for Apple silicon, and language models reach it through mlx-lm. A comparison has to name the level it is comparing.
The MLX build generated 153.04 tokens per second against 89.32 for GGUF, about 71 percent faster. The GGUF build reached its first token in 0.12 seconds against 0.27 for MLX. A single number does not settle which is quicker.
Each build was run once, so each figure is a single observation and repeated runs would show run-to-run variation. One result comes from one model, one chip and one application release. Format choice tracks deployment constraints more closely than any published rate.
The GGUF run logged 1,895 tokens and 15.40 s of thinking against 1,095 and 3.59 s for MLX, yet both returned a 500-word story. The extra tokens went into a longer reasoning trace. Decoding slows as a sequence grows, so length may explain a few percent of the rate gap.
A tokens-per-second figure reports decode, which reads the weights from memory at every step. Both runtimes read the same unified memory, so bandwidth over model size bounds each of them, at about 287 tokens per second for a uniformly 4-bit build of this model on an M4 Max. Each real build comes in lower, since both carry per-group scales and the GGUF mixture keeps some tensors at 6-bit.
llama.cpp’s Q4_K_M mixture keeps some tensors at 6-bit, while MLX’s default 4-bit setting applies uniformly across groups of 64 weights. Two builds labeled 4-bit can differ in size and in output quality, and neither run here was scored on it.
Data & License
No third-party dataset. The benchmarks and measurements shown are the author’s own, recorded on the hardware and software described in this article.
© 2025 Philip Sarajlic. All rights reserved for the article’s original text and figures.
Code examples in this article are licensed under the Common Public Attribution License Version 1.0 (CPAL-1.0), an OSI-approved copyleft license based on the Mozilla Public License 1.1. Initial Developer: Philip Sarajlic.
Attribution required by CPAL Exhibit B: © 2025 Philip Sarajlic · “Based on code by Philip Sarajlic” · philipsarajlic.com · no graphic image. This attribution must be displayed in Larger Works.
Modifications must be released in Source Code form under CPAL-1.0. Making the code usable by anyone other than you over a network is External Deployment under the license and is treated as distribution, so the Source Code must be made available to those users.
Full text: opensource.org/license/cpal-1-0 (SPDX identifier CPAL-1.0)


















