Hardware-agnostic AI serving — NVIDIA, AMD, and Apple without CUDA lock-in
MAX (Modular Accelerated Xecution) is Modular's AI inference platform. You give it a supported model — commonly from Hugging Face — and it compiles, optimises, and serves it with an OpenAI-compatible HTTP endpoint across NVIDIA, AMD, Apple, and CPU targets, without changing your application code.
MAX is made of three interlocking layers:
max serve command downloads a supported model, compiles it for your hardware, and exposes an OpenAI-compatible REST endpoint. Existing OpenAI clients can generally point at the local endpoint.max package, so kernels import from max.gpu (for example from max.gpu.host import DeviceContext) rather than the Mojo standard library.
Inference is pulled by two forces: models keep getting larger, and the hardware landscape keeps fragmenting. CUDA remains the performance leader on NVIDIA, but AMD data-centre GPUs are now credible alternatives, Apple Silicon runs many models locally, and edge devices span every architecture imaginable. Maintaining a separate serving stack per backend is expensive. MAX's pitch is one stack across all of them — write and deploy once, and let the compiler retarget it per machine.
pixi gives you a reproducible Conda environment with MAX pinned. This is the path Modular officially recommends for production setups:
# Install pixi
curl -fsSL https://pixi.sh/install.sh | sh
# Create a new project pulling from the Modular channel
pixi init quickstart \
-c https://conda.modular.com/max/ \
-c conda-forge
cd quickstart
pixi add max-all # MAX Serve, benchmark, Mojo, and all optional dependencies
pixi shell # activate the environment
If you already use uv for Python project management, install the current MAX extras:
uv init quickstart && cd quickstart
uv venv && source .venv/bin/activate
uv add "max[all]"
MAX downloads gated models from Hugging Face. Export your token before serving:
export HF_TOKEN="hf_your_token_here"
max serveOne command downloads the model, compiles it for your GPU, and starts the server:
max serve --model google/gemma-3-4b-it
MAX will log progress as it fetches weights, runs compilation, and binds the port:
Fetching model weights...
Compiling for NVIDIA H100 (CUDA 12.x)...
Server ready at http://localhost:8000
The endpoint is OpenAI API-compatible. Any code already targeting OpenAI works unchanged — just swap the base URL:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY" # MAX does not require an API key locally
)
completion = client.chat.completions.create(
model="google/gemma-3-4b-it",
messages=[{"role": "user", "content": "Explain SIMD in one paragraph."}]
)
print(completion.choices[0].message.content)
MAX ships a built-in benchmarking command to measure throughput and latency against your running endpoint:
max benchmark \
--model google/gemma-3-4b-it \
--backend modular \
--num-prompts 200 \
--max-concurrency 32
--max-concurrency flag simulates production load — latency at concurrency 1 tells you very little about real-world behaviour.
For models that don't fit on a single GPU, MAX shards them across devices with tensor parallelism. Pass --num-gpus to set how many it uses:
max serve \
--model meta-llama/Llama-3.3-70B-Instruct \
--num-gpus 4
MAX Serve is a standalone HTTP server — right for production deployments and microservices. The Engine API is a Python library you embed directly in your application — right for batch pipelines, custom pre/post-processing, or when you want inference in-process without an HTTP hop.
from max.engine import InferenceSession
from transformers import AutoTokenizer
MODEL = "meta-llama/Llama-3.1-8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(MODEL)
# Load the model — MAX compiles it on first load
session = InferenceSession()
model = session.load(MODEL)
# Run a forward pass
input_ids = tokenizer.encode("Hello, world!")
outputs = model.execute(input_ids=input_ids)
print(tokenizer.decode(outputs["logits"].argmax(-1)))
The MAX Graph API lets you define a computation graph and drop in Mojo-written custom ops at any node. The graph compiler fuses adjacent ops and selects the optimal hardware path automatically:
# Python side — define the graph
from max.graph import Graph, TensorType, ops
with Graph("my_model", input_types=[TensorType(dtype, shape)]) as graph:
x = graph.inputs[0]
y = ops.matmul(x, weight) # built-in op
z = ops.custom["my_mojo_op"](y) # Mojo kernel wired in
graph.output(z)
# Mojo side — the custom kernel
@register_mojo_op("my_mojo_op")
def my_op(x: Tensor[DType.float32]) -> Tensor[DType.float32]:
# runs on whatever hardware MAX targets
...
Vulkan is an open, vendor-neutral GPU API maintained by the Khronos Group — the same standards body behind OpenGL and WebGL. Where CUDA is an NVIDIA proprietary SDK, Vulkan is an industry standard implemented by every major GPU vendor: NVIDIA, AMD, Intel, ARM, Qualcomm, Apple (via the MoltenVK translation layer).
Vulkan was designed primarily for graphics (rasterisation, ray tracing), but it exposes a compute pipeline — arbitrary parallel programs that run on the GPU's shader cores, with no graphics output required. This compute path is what matters for AI inference.
| Dimension | CUDA | Vulkan Compute |
|---|---|---|
| Vendor | NVIDIA only | Any GPU (NVIDIA, AMD, Intel, ARM, Apple) |
| Kernel language | CUDA C++ / PTX | GLSL / HLSL → SPIR-V bytecode |
| Driver requirement | NVIDIA proprietary driver | Any Vulkan-capable driver (open source on AMD/Intel) |
| matmul / tensor ops | cuBLAS, Tensor Cores (WMMA) | Cooperative matrices (VK_KHR_cooperative_matrix) |
| Ecosystem maturity | Very mature — deep ML tooling | Growing — strong in mobile/edge/cross-platform |
| Best for | Data-centre NVIDIA, maximum throughput | Cross-vendor, edge, consumer GPUs, AMD without ROCm |
Vulkan compute kernels are distributed as SPIR-V — a binary intermediate representation, analogous to LLVM IR or CUDA PTX. You write a shader in GLSL or HLSL, compile it to SPIR-V offline, and ship the SPIR-V. The GPU driver JITs it to native machine code on the target device. This is how a single binary runs on an AMD RDNA card and an Intel Arc card without recompilation.
# Compile a GLSL compute shader to SPIR-V
glslc matmul.comp -o matmul.spv
# The .spv file is what you distribute — hardware-agnostic
Several production inference engines use Vulkan compute as their portability path:
transformers.js runs through this path.llama.cpp is often your most practical option today. MAX covers the data-centre AMD story via ROCm; Vulkan covers the long tail of consumer and embedded hardware.
MAX does not require you to select a backend manually. When you run max serve, the MAX compiler inspects available hardware and picks the optimal execution path:
MAX reaches AMD through ROCm and Apple through Metal directly — Vulkan is not one of its documented inference backends, and max serve will never select it. Vulkan matters here as the portability layer for engines outside MAX, such as llama.cpp, which is how you cover hardware MAX does not target at all.
# On a machine with AMD MI300X and the supported AMD driver installed
uv venv && source .venv/bin/activate
uv pip install "max[serve]"
export HF_TOKEN="hf_..."
max serve --model meta-llama/Llama-3.3-70B-Instruct
# MAX detects the AMD GPU automatically — no --device flag needed
# Compiling for AMD gfx942 (MI300X)...
For hardware not covered by MAX (for example, some Intel Arc, consumer AMD, or edge devices), use a separate Vulkan-backed inference engine such as llama.cpp:
# Build llama.cpp with Vulkan support
cmake -B build -DGGML_VULKAN=1
cmake --build build --config Release -j
# Run inference via Vulkan on any Vulkan-capable GPU
./build/bin/llama-cli \
-m Llama-3.1-8B-Instruct.Q4_K_M.gguf \
--gpu-layers 99 \
-p "Explain what Vulkan is"
--gpu-layers flag offloads that many transformer layers to the Vulkan GPU. Setting it to 99 pushes everything to GPU; reduce it if you hit VRAM limits.
A practical production pattern: run MAX Serve for the LLM inference core (NVIDIA or AMD), and a lightweight Vulkan-accelerated sidecar for pre-processing — image tokenisation with CLIP, audio feature extraction. You are wiring two processes together; MAX has no built-in Vulkan mode.
# MAX handles the LLM
max serve --model meta-llama/Llama-3.3-70B-Instruct &
# Vulkan sidecar handles vision tokenisation on an Intel Arc GPU
./clip-encoder --vulkan --input /tmp/image.jpg --output /tmp/tokens.bin
# Application glues them together via HTTP / shared memory
vLLM is an open-source LLM inference and serving library from UC Berkeley. Its core innovation is PagedAttention — a KV-cache management algorithm inspired by OS virtual memory paging that dramatically reduces memory fragmentation during concurrent inference. vLLM is the most widely deployed open-source LLM serving solution in production today.
# Install (NVIDIA GPU assumed)
uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --torch-backend=auto
# Serve a model — blocks in the foreground
vllm serve Qwen/Qwen2.5-7B-Instruct
Then, from a second terminal, query it with the same OpenAI-compatible client MAX uses:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
completion = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": "Hello"}]
)
| Dimension | vLLM + CUDA | MAX (Modular) |
|---|---|---|
| Hardware support | NVIDIA (primary); AMD ROCm, Intel, TPU (experimental) | NVIDIA, AMD (ROCm), Apple Silicon, CPU; AWS Trainium, Google TPUs, Qualcomm Cloud AI 100 Ultra/Dragonfly announced (Aug 2026) |
| Kernel language | CUDA C++ / Triton Python | Mojo (compiles to PTX / AMDGPU ISA / Metal) |
| KV-cache | PagedAttention — best-in-class for NVIDIA | MAX-managed; paged allocation with hardware-specific tuning |
| API compatibility | OpenAI-compatible (Chat + Completions) | OpenAI-compatible (Chat + Completions) |
| Model formats | Hugging Face (safetensors, GGUF via adaptor) | Hugging Face, PyTorch, ONNX |
| Custom ops | Triton kernels (Python DSL → CUDA) | Mojo kernels → cross-hardware via MAX Graph |
| Quantisation | AWQ, GPTQ, FP8 (NVIDIA H100+) | INT4, INT8, FP8; hardware-adaptive selection |
| Ecosystem maturity | Very mature — production-proven at scale | Newer — fast-moving, Modular-backed |
| Licence | Apache 2.0 | Source-available (Modular Community License); Mojo compiler is Apache 2.0 |