What Actually Happens When You Run MLX on a Mac?

MLX operations do not compute anything when you call them: they record a lazy compute graph. Work happens when the graph is evaluated, either by calling eval() or implicitly, for example by printing an array. Evaluation runs each operation's primitive on the device you chose: Metal kernels on the Apple silicon GPU, or C++ kernels on the CPU. Arrays live in unified memory, so both devices use the same data without copies. With mx.compile, MLX traces the function once, merges and fuses operations, and generates code: a Metal kernel for the GPU, or a C++ shared library for the CPU.
Mode
Device

Showing plain lazy evaluation on the Apple silicon GPU: 6 stages, 5 steps.

Your MLX code calls MLX MLX records Lazy compute graph Lazy compute graph runs when you call Evaluation (eval) Lazy compute graph traced and optimized by mx.compile mx.compile smaller graph runs at Evaluation (eval) Evaluation (eval) calls eval_cpu() of C++ CPU kernels Evaluation (eval) calls eval_gpu() of Metal kernels mx.compile generates and builds Fused C++ library mx.compile generates Fused Metal kernel C++ CPU kernels run on Apple silicon CPU Fused C++ library runs on Apple silicon CPU Metal kernels run on Apple silicon GPU Fused Metal kernel runs on Apple silicon GPU Your MLX code programming language MLX framework component Lazy compute graph intermediate representation mx.compile compiler Evaluation (eval) runtime C++ CPU kernels kernel Fused C++ library machine code Metal kernels kernel Fused Metal kernel kernel Apple silicon CPU hardware Apple silicon GPU hardware

The selected path, step by step

  1. Your MLX code calls MLX: Your code calls MLX operations such as mx.matmul, or runs an mlx.nn model.
  2. MLX records Lazy compute graph: Each operation adds a node to the graph and returns an array that has no data yet.
  3. Lazy compute graph runs when you call Evaluation (eval): The graph runs at mx.eval, or implicitly when you print an array, convert it to NumPy, or call .item().
  4. Evaluation (eval) calls eval_gpu() of Metal kernels: Operations on a GPU stream run their primitive's Metal kernel. In compiled code, operations that were not fused still run here.
  5. Metal kernels run on Apple silicon GPU: Metal kernels run on the GPU, reading the same memory the CPU uses.

Lazy by default

Calling mx.add or a layer of an mlx.nn model only adds a node to a graph. The graph runs when something needs the values: an explicit mx.eval, printing an array, converting it to NumPy, or calling .item(). A training loop typically evaluates once per step, so each step's forward pass, gradients and optimizer update run as one graph.

That is the main difference from PyTorch in eager mode and from JAX without jit, where each operation is dispatched as Python reaches it. mx.compile plays the role that torch.compile and jax.jit play there.

One memory, two devices

On Apple silicon the CPU and GPU share one memory pool. MLX arrays do not belong to a device; you pick the device per operation (the stream argument), and MLX inserts the dependencies when an operation on one device needs the result of another. That is why the device choice above changes only the kernels, not where the data lives.

For running LLMs locally on a Mac with hand-written Metal kernels instead, compare how llama.cpp runs a model.

Every stage

Your MLX code

programming language

Python code using mlx.core arrays, and often mlx.nn models and mlx.optimizers.

In the atlas: Python

MLX

framework component

Apple's array framework. Its operations are thin front ends that add nodes to a graph.

In the atlas: MLX

Lazy compute graph

intermediate representation

The recorded operations, each a primitive with its inputs. Nothing has been computed yet.

mx.compile

compiler

MLX's compiler transformation. On the first call it traces the function with placeholder inputs, shrinks the graph and fuses operations; later calls with the same shapes and types reuse the result.

  • Function compilation results in smaller graphs by merging common work and fusing certain operations.
    Compilation (MLX documentation) Project docs
  • The first time you call a compiled function, MLX will build the compute graph, optimize it, and generate and compile code.
    Compilation (MLX documentation) Project docs

Evaluation (eval)

runtime

When values are needed, MLX schedules the graph and runs each primitive on the device of its stream.

In the atlas: MLX

C++ CPU kernels

kernel

Each primitive's CPU implementation (eval_cpu), written in C++.

In the atlas: C++

Fused C++ library

machine code

For the CPU, mx.compile generates C++ source for the fused operations, builds it into a shared library and loads it.

In the atlas: C++

Metal kernels

kernel

Each primitive's GPU implementation (eval_gpu), a kernel written in the Metal shading language and shipped prebuilt in MLX's Metal library.

Apple silicon CPU

hardware

The CPU cores, sharing one memory pool with the GPU.

Apple silicon GPU

hardware

The integrated GPU, sharing one memory pool with the CPU, so arrays need no copies between devices.

Every step

FromStepToWhenWhat happensSource
Your MLX codecallsMLXany mode · any deviceYour code calls MLX operations such as mx.matmul, or runs an mlx.nn model.Source repository
MLXrecordsLazy compute graphany mode · any deviceEach operation adds a node to the graph and returns an array that has no data yet.Project docs
Lazy compute graphruns when you callEvaluation (eval)Lazy (no compile) · any deviceThe graph runs at mx.eval, or implicitly when you print an array, convert it to NumPy, or call .item().Project docs
Project docs
Lazy compute graphtraced and optimized bymx.compilemx.compile · any deviceOn the first call, the compiled function is traced with placeholder inputs and its graph is simplified and fused. Calls with new shapes, types or numbers of inputs compile again.Project docs
Project docs
mx.compilesmaller graph runs atEvaluation (eval)mx.compile · any deviceThe compiled graph is still lazy: it runs when it is evaluated, and later calls reuse the cached compilation.Project docs
Evaluation (eval)calls eval_cpu() ofC++ CPU kernelsany mode · CPUOperations on a CPU stream run their primitive's C++ CPU implementation.Project docs
Evaluation (eval)calls eval_gpu() ofMetal kernelsany mode · GPU (Metal)Operations on a GPU stream run their primitive's Metal kernel. In compiled code, operations that were not fused still run here.Project docs
Project docs
mx.compilegenerates and buildsFused C++ librarymx.compile · CPUFor the CPU, the fused operations become C++ source that is compiled into a shared library and loaded.Project docs
Source repository
mx.compilegeneratesFused Metal kernelmx.compile · GPU (Metal)For the GPU, the fused element-wise operations become one Metal kernel.Project docs
Source repository
C++ CPU kernelsrun onApple silicon CPUany mode · CPUThe CPU kernels read and write arrays in unified memory.Project docs
Fused C++ libraryruns onApple silicon CPUmx.compile · CPUThe compiled library's functions run on the CPU when the graph is evaluated.Source repository
Metal kernelsrun onApple silicon GPUany mode · GPU (Metal)Metal kernels run on the GPU, reading the same memory the CPU uses.Project docs
Fused Metal kernelruns onApple silicon GPUmx.compile · GPU (Metal)The fused kernel runs on the GPU when the graph is evaluated.Project docs

What this does not show

Documented paths for MLX Python code on one Apple silicon Mac, run lazily or with mx.compile, on the GPU or the CPU. MLX's CUDA backend for Linux, distributed training, custom Metal kernels written with mx.fast.metal_kernel, and the C++, C and Swift APIs are out of scope.

How this page is checked

Every stage and step above quotes the source that supports it. The quotes were checked against the MLX documentation (Lazy Evaluation, Unified Memory, Compilation, Custom Extensions, Build and Install), the MLX README, and the Metal and CPU compile sources in ml-explore/mlx on 2026-11-11. The data is a versioned JSON file under the same CC BY 4.0 license as the dataset. Found something wrong or out of date? Open an issue with the better source.

What these tools are written in is in the main atlas: Python, MLX, C++. More execution paths: What Actually Happens When You Run JAX Code, How Does llama.cpp Run a Model on Your Hardware, How Does ONNX Runtime Execute a Model Across Hardware, What Actually Happens When You Run a PyTorch Model, What Actually Happens When vLLM Serves a Request, or compare them all.

Explore the full graph →