Skip to content

metal : use more command buffers on iOS - #427

Merged
bri-prism merged 1 commit into
prismfrom
metal-ios-split-cmd-buf
Nov 6, 2026
Merged

bri-prism merged 1 commit into
prismfrom
metal-ios-split-cmd-buf

Conversation

@bri-prism

Copy link
Copy Markdown
Collaborator

What

Default the Metal backend to 8 command buffers on iOS instead of 1. macOS is unchanged.

Why

iOS can stop a GPU command buffer that runs for more than a few seconds. With n_cb = 1, the graph is split into a small first buffer (about 10% of the nodes) and one buffer with everything else. On a long prompt with n_ubatch = 512, that second buffer can run for 4 to 6 seconds on an A17 Pro. When iOS stops it, the buffer is discarded with kIOGPUCommandBufferCallbackErrorInnocentVictim (status 5), the backend goes into its error state, and llama_decode returns -3. Lowering n_ubatch avoids it, at a large cost in prefill speed.

How

ggml_backend_metal_set_n_cb is now called with GGML_METAL_DEFAULT_N_CB, which is 8 when TARGET_OS_IPHONE and 1 otherwise. 8 is the existing GGML_METAL_MAX_COMMAND_BUFFERS cap. At startup on iOS you will see the existing "n_cb > 2 is not recommended" warning; it is expected here.

Testing

iPhone 15 Pro Max, a small quantized model, 16,553-token prompt, n_ctx 33,280, n_batch and n_ubatch 512, flash attention on, F16 K/V, all layers on GPU. Same upstream base for all runs; per-buffer GPU time logged from GPUStartTime/GPUEndTime.

Setup Result Prefill Largest command buffer
n_cb = 1 (current) 1 of 2 failed, rc=-3 after 11,776 tokens 105.7 tok/s (passing run) 5.0 s (stopped), 6.1 s
n_cb = 8 via a local override 2 of 2 passed 110.5, 93.0 tok/s 0.8 s
n_cb = 8 via this change 1 of 1 passed 98.5 tok/s 1.5 s

These are one or two runs per setup, and the phone's thermal state varied between them, so the prefill numbers are noisy. Over the same token range of the first pair, n_cb = 8 was not slower (139 vs 134 tok/s).

The device runs used upstream llama.cpp from 2026-10-15 with this same change applied, not this branch. On this branch, ggml-metal.cpp passes a syntax-only compile for iOS and macOS, and the define resolves to 8 and 1 respectively. Not built or run end to end on this branch.

iOS can stop a GPU command buffer that runs for more than a few
seconds. With n_cb = 1, about 90% of each graph goes into a single
command buffer, so a long prompt batch on a slow device can hit that
limit. The buffer is then discarded with
kIOGPUCommandBufferCallbackErrorInnocentVictim and llama_decode
returns -3.

Default to 8 command buffers on iOS. macOS is unchanged.
@bri-prism
bri-prism merged commit 6447906 into prism Nov 6, 2026
10 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant