Repository navigation
fit : account for skipped NextN layers when setting n_gpu_layers - #31569
Conversation
Without MTP, fit counts n_layer + 1 layers while the model loader counts
n_layer_all + 1 for n_gpu_layers, including the NextN layers which hold no
weights. A fit result that offloads all layers then leaves layer 0 on the CPU
("offloaded 65/66 layers" for Qwen3.8-27B on two GPUs).
Add the skipped NextN layers to n_gpu_layers and to the tensor split of the
device that holds the output layer so the loader assigns the layers that
fit measured.
Fixes #31531
| uint32_t & hp_ngl, | ||
| uint32_t & hp_n_ctx_train, | ||
| uint32_t & hp_n_expert, | ||
| ggml_log_level log_level) { | ||
| ggml_log_level log_level, | ||
| uint32_t * hp_ngl_skip = nullptr) { |
There was a problem hiding this comment.
Please keep the handling of hyperparameters consistent. It's fine to make them optional via pointers but then please do this consistently for all of them.
|
@JohannesGaessler Would you like to take one more look at the changes? |
|
I can confirm this fix works on the ROCm setup that the PR lists as untested — 2× AMD Radeon VII (gfx906), Qwen3.8-27B Q6_K, Layer distribution (before the fix): After (this PR): Why the throughput halvesIt's more than just "one layer on the CPU". With layer 0 left on the host, its weights are copied to the GPU as split inputs on every graph evaluation, and the resulting buffer layout (many CPU↔GPU split boundaries) does not match the layout reserved at startup. The scheduler then re-reserves the compute buffers on (nearly) every step — the
The layout the scheduler sees is also dynamic — the input-embedding buffer resizes between a single generation token and a full prompt batch: The 20-split layout (
|
Overview
Fixes #31531 ("--fit on" leaves one layer on the CPU, and performance halves).
Since #26605,
common_get_device_memory_data_implsetshp_ngl = n_layerwhen MTP is not loaded, so fit assignsn_layer + 1layers across the devices. The model loader still countsn_layer_all + 1forn_gpu_layers, including the NextN layers, which hold no weights without MTP (i_gpu_start = n_layer_all + 1 - n_gpu_layers). When fit offloads everything to more than one device, it setsn_gpu_layers = 65for Qwen3.8-27B (n_layer = 64,n_layer_all = 65). The loader then starts at layer 1, which leaves layer 0 on the CPU. The tensor-buft-override indices computed fromhp_nglwere off by the same amount.In
set_ngl_tensor_split_tbo, add the skipped NextN layers ton_gpu_layersand to the tensor split of the last device that has layers. That device also holds the output layer, and the NextN layers sit right before it. The layers the loader assigns then match the layers fit measured and the override indices. Nothing changes when MTP is loaded or when the model has no NextN layers.Additional information
Reproduction (GB10, CUDA0 + local RPC0 as a second device, Qwen3.8-27B-UD-Q8_K_L):
offloaded 65/66 layersoffloaded 66/66 layersSingle-device partial offload (
--fit-target 100000, no RPC) stays consistent. Fit's test allocation for k layers now reports the same memory as master's k+1, because master's count included the empty NextN slot. The loaded layer count matches what fit printed.Not tested: ROCm (the reporter's 2x Radeon VII setup), and models with more than one NextN layer.
Requirements