Repository navigation
Run CUDA graph kernels one at a time on DGX Spark - #4697
yanghaojin wants to merge 1 commit into
Conversation
Kernels a graph lets run concurrently slow each other down on a GB10 more than they overlap. Chaining every node after the previous one: 2048-token prefill of a 27B 4-bit model 6.70 -> 3.05 s, decode of a 35B-A3B MoE 62.2 -> 68.3 tok/s, Flash-Next 22.3 -> 24.3, 27B decode unchanged. MLX_CUDA_GRAPH_SERIAL=0/1 overrides the default (on for compute capability 12.1 only).
There was a problem hiding this comment.
CUDA graph being slower because of parallel works seems like a bug in GPU scheduler that that should fixed in driver level, if you could make a standalone repro I would be happy to pass to our contact at NVIDIA.
I don't think we should accept this change because it is not guaranteed to be faster for all kinds of workloads, and a future update of CUDA toolkit could turn it into a performance regression.
In the meanwhile a good way to work around this on client side is to only disable CUDA graphs for prefill, and #4647 will provide an API to allow that.
Also ping @dhiltgen since this looks like a thing that should have special care in inference engines.
|
The "Reproducing with plain mlx_lm" section in the PR description can be run directly on stock MLX without gbx. The model is publicly available at [GreenBitAI/Qwen3.8-27B-4bit](https://huggingface.co/GreenBitAI/Qwen3.8-27B-4bit), and we're using One important detail: on stock MLX, use On stock MLX main ( From We tried reproducing this in standalone CUDA with two large kernels or a large + small kernel running concurrently, but couldn't reproduce the slowdown. Once #4647 is merged, we plan to disable CUDA graphs only for prefill at the caller level. Until then, we'll keep the serialization patch in our own build. |
On GB10, kernels that a CUDA graph runs concurrently slow each other down more than they overlap. Serializing the graph roughly halves the 2048-token prefill of a 27B 4-bit model (6.70 -> 3.05 s) and makes MoE decode about 10% faster. On by default for compute capability 12.1 only,
MLX_CUDA_GRAPH_SERIAL=0/1overrides.CommandEncoderonly adds graph edges from input buffers, so kernels with no data dependency between them run concurrently inside a graph. On a GB10 that costs more than it saves. The same 4-bit quantized matmul (2048 x 17408 x 5120) takes 21.4 ms inside a model's prefill graph and 7.6 ms with graphs off.This change adds an edge from each node to the one added before it. It only adds dependencies, so outputs are bit-identical.
Turning graphs off gives the same prefill numbers but loses 14-18% on MoE decode (see below), so this keeps the graphs and just serializes them. The existing knobs don't get there either.
MLX_MAX_OPS_PER_BUFFER=1matches the prefill, but MoE decode drops to 36.7 tok/s on the 35B. Larger graphs (MLX_MAX_OPS_PER_BUFFER=100,MLX_MAX_MB_PER_BUFFER=1000) don't change anything.DGX Spark, 4-bit models (tok/s unless noted):
Reproducing with plain mlx_lm
2048-token prefill of a Qwen3.5-architecture 27B 4-bit model with mlx_lm 0.30.8, three runs each. mlx_lm's own gated-delta loop takes a big chunk of the time here, so the gain is smaller than in the table above. The first run includes warm-up.
I couldn't reproduce the slowdown with a synthetic stack of independent quantized matmuls. It seems to need a real model's graph. I also haven't measured any other GPU, which is why the default is limited to sm_121.