Skip to content

Commit 35c10e5

Browse files
committed
fix: support DFlash tensor-split Meta placement
Keep auto-detected DFlash drafters on tensor-split placement when target shared tensors are Meta-backed, add Meta-safe capture/ring/recurrent fallbacks, and cover the plumbing with focused regression checks.
1 parent 5ab1c5d commit 35c10e5

9 files changed

Lines changed: 237 additions & 98 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## v0.3.2
4+
5+
- Fixed DFlash on target tensor-split / Meta placement. Auto-detected DFlash draft loading now keeps tensor-split placement when shared target output tensors live in a Meta buffer, instead of reloading the drafter onto one GPU and crashing during draft warmup. Explicit incompatible `--spec-draft-device` placement now fails closed with a direct diagnostic.
6+
- Added the Meta backend support needed by DFlash tensor-split runs: TurboQuant WHT split-state propagation, public Meta device introspection for capture placement, Meta-backed hidden/prefill capture allocation, Meta-safe GPU ring readback fallback, Meta-safe recurrent backup row copies, and a full-logits drafter fallback when compact argmax/top-k cannot run on Meta output.
7+
- Verified the single-RTX-3090 tensor-split crash proxy with DFlash enabled. Forced `-sm tensor` now completes and generates drafts; normal single-GPU DFlash remains on the fast CUDA path. The forced single-GPU tensor-split path is still a correctness fallback and is slower because recurrent backup and hidden-ring transfer avoid unsafe raw Meta pointers.
8+
39
## v0.3.1
410

511
- Merged latest upstream llama.cpp master. This pulls in Gemma 4 12B and Gemma 4 unified multimodal support fixes, including non-causal vision, unified audio/vision projector handling, and FPE fixes; Qwen3.5 post-norm hidden-state behavior for MTP; CUDA KV-cache quantization preallocation and PDL race fixes; WebGPU FlashAttention refactoring with standardized quantization support; CPU backend improvements for RVV/SVE; lower-latency Metal command-buffer status polling; Mermaid diagram rendering and preview support in `tools/ui`; updated BoringSSL, SYCL documentation, save/load-state tests, Docker docs, and small CI/release maintenance.

common/speculative.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4296,6 +4296,14 @@ llama_context * common_speculative_create_ctx_dft(const common_params_speculativ
42964296
if (params.sample_temp > 0.0f) {
42974297
llama_set_dflash_sample_temp(ctx_dft, params.sample_temp);
42984298
}
4299+
bool draft_reduced_logits = params.draft.backend_sampling;
4300+
ggml_backend_dev_t draft_output_dev = llama_model_dev_output(params.model_dft);
4301+
if (draft_output_dev && ggml_backend_dev_type(draft_output_dev) == GGML_BACKEND_DEVICE_TYPE_META) {
4302+
draft_reduced_logits = false;
4303+
LOG_WRN("%s",
4304+
"dflash: draft backend sampling disabled on Meta output placement; using full-logits fallback\n");
4305+
}
4306+
llama_set_dflash_consume_reduced(ctx_dft, draft_reduced_logits);
42994307

43004308
// warmup the draft context
43014309
{

ggml/include/ggml-backend.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ extern "C" {
401401
// express this as a backend registry functionality instead
402402
GGML_API ggml_backend_dev_t ggml_backend_meta_device(
403403
ggml_backend_dev_t * devs, size_t n_devs, ggml_backend_meta_get_split_state_t get_split_state, void * get_split_state_ud);
404+
GGML_API bool ggml_backend_dev_is_meta(ggml_backend_dev_t dev);
405+
GGML_API size_t ggml_backend_meta_dev_n_devs(ggml_backend_dev_t meta_dev);
406+
GGML_API ggml_backend_dev_t ggml_backend_meta_dev_simple_dev(ggml_backend_dev_t meta_dev, size_t index);
404407

405408
//
406409
// Utils

ggml/src/ggml-backend-meta.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ struct ggml_backend_meta_device_context {
8282
}
8383
};
8484

85-
static bool ggml_backend_dev_is_meta(ggml_backend_dev_t dev);
86-
8785
static const char * ggml_backend_meta_device_get_name(ggml_backend_dev_t dev) {
8886
GGML_ASSERT(ggml_backend_dev_is_meta(dev));
8987
const ggml_backend_meta_device_context * meta_dev_ctx = (const ggml_backend_meta_device_context *) dev->context;
@@ -193,17 +191,17 @@ static const ggml_backend_device_i ggml_backend_meta_device_iface = {
193191
/* .event_synchronize = */ nullptr,
194192
};
195193

196-
static bool ggml_backend_dev_is_meta(ggml_backend_dev_t dev) {
194+
bool ggml_backend_dev_is_meta(ggml_backend_dev_t dev) {
197195
return dev != nullptr && dev->iface.get_name == ggml_backend_meta_device_iface.get_name;
198196
}
199197

200-
static size_t ggml_backend_meta_dev_n_devs(ggml_backend_dev_t meta_dev) {
198+
size_t ggml_backend_meta_dev_n_devs(ggml_backend_dev_t meta_dev) {
201199
GGML_ASSERT(ggml_backend_dev_is_meta(meta_dev));
202200
const ggml_backend_meta_device_context * meta_dev_ctx = (const ggml_backend_meta_device_context *) meta_dev->context;
203201
return meta_dev_ctx->simple_devs.size();
204202
}
205203

206-
static ggml_backend_dev_t ggml_backend_meta_dev_simple_dev(ggml_backend_dev_t meta_dev, size_t index) {
204+
ggml_backend_dev_t ggml_backend_meta_dev_simple_dev(ggml_backend_dev_t meta_dev, size_t index) {
207205
GGML_ASSERT(ggml_backend_dev_is_meta(meta_dev));
208206
const ggml_backend_meta_device_context * meta_dev_ctx = (const ggml_backend_meta_device_context *) meta_dev->context;
209207
GGML_ASSERT(index < meta_dev_ctx->simple_devs.size());
@@ -538,7 +536,10 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
538536

539537
// Some ops process data on a per-row bases:
540538
auto handle_per_row = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
541-
GGML_ASSERT(src_ss[0].axis != GGML_BACKEND_SPLIT_AXIS_0);
539+
if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) {
540+
GGML_ABORT("meta backend per-row op %s (%s) cannot use axis-0 split src0 %s (%s)",
541+
tensor->name, ggml_op_name(tensor->op), tensor->src[0]->name, ggml_op_name(tensor->src[0]->op));
542+
}
542543
return src_ss[0];
543544
};
544545

@@ -782,6 +783,20 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
782783
return {GGML_BACKEND_SPLIT_AXIS_0, {0}, {1}, 1};
783784
};
784785

786+
auto handle_turbo_wht = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
787+
ggml_backend_meta_split_state ret = handle_generic(src_ss, /*scalar_only =*/ false);
788+
if (ret.axis == GGML_BACKEND_SPLIT_AXIS_0) {
789+
const size_t n_bufs = ggml_backend_meta_buffer_n_bufs(tensor->buffer);
790+
for (size_t s = 0; s < ret.n_segments; ++s) {
791+
for (size_t j = 0; j < n_bufs; ++j) {
792+
const int64_t turbo_wht_split_axis_ne = ret.ne[s*n_bufs + j];
793+
GGML_ASSERT(turbo_wht_split_axis_ne % 128 == 0);
794+
}
795+
}
796+
}
797+
return ret;
798+
};
799+
785800
auto calculate_split_state = [&]() -> ggml_backend_meta_split_state {
786801
if (ggml_nelements(tensor) == 0) {
787802
return {GGML_BACKEND_SPLIT_AXIS_UNKNOWN, {0}, {1}, 1};
@@ -1002,6 +1017,9 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
10021017
case GGML_OP_GLU: {
10031018
split_state = handle_generic(src_ss, /*scalar_only =*/ false);
10041019
} break;
1020+
case GGML_OP_TURBO_WHT: {
1021+
split_state = handle_turbo_wht(src_ss);
1022+
} break;
10051023
default: {
10061024
GGML_ABORT("ggml op not implemented: %s", ggml_op_name(tensor->op));
10071025
split_state = {GGML_BACKEND_SPLIT_AXIS_UNKNOWN, {0}, {1}, 1};

src/llama-context.cpp

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,64 @@ static void dflash_log_backend_layout(
170170

171171
static ggml_backend_t dflash_backend_for_dev(
172172
const std::vector<ggml_backend_ptr> & backends,
173-
ggml_backend_dev_t want_dev) {
173+
ggml_backend_dev_t want_dev,
174+
bool allow_meta = false) {
174175
for (const auto & backend : backends) {
175176
auto * dev = ggml_backend_get_device(backend.get());
176-
if (dev == want_dev && dflash_backend_dev_is_gpu(dev)) {
177+
if (dev == want_dev && (dflash_backend_dev_is_gpu(dev) ||
178+
(allow_meta && ggml_backend_dev_is_meta(dev)))) {
177179
return backend.get();
178180
}
179181
}
180182
return nullptr;
181183
}
182184

185+
struct dflash_capture_backend {
186+
ggml_backend_t backend = nullptr;
187+
ggml_backend_dev_t dev = nullptr;
188+
bool from_meta = false;
189+
};
190+
191+
static dflash_capture_backend dflash_capture_backend_for_layer(
192+
const std::vector<ggml_backend_ptr> & backends,
193+
ggml_backend_dev_t layer_dev) {
194+
dflash_capture_backend result;
195+
196+
result.backend = dflash_backend_for_dev(backends, layer_dev, layer_dev && ggml_backend_dev_is_meta(layer_dev));
197+
if (result.backend) {
198+
result.dev = layer_dev;
199+
result.from_meta = layer_dev && ggml_backend_dev_is_meta(layer_dev);
200+
return result;
201+
}
202+
203+
if (!layer_dev || !ggml_backend_dev_is_meta(layer_dev)) {
204+
return result;
205+
}
206+
207+
const size_t n_devs = ggml_backend_meta_dev_n_devs(layer_dev);
208+
for (size_t i = 0; i < n_devs; ++i) {
209+
ggml_backend_dev_t simple_dev = ggml_backend_meta_dev_simple_dev(layer_dev, i);
210+
result.backend = dflash_backend_for_dev(backends, simple_dev);
211+
if (result.backend) {
212+
result.dev = simple_dev;
213+
result.from_meta = true;
214+
return result;
215+
}
216+
}
217+
218+
return result;
219+
}
220+
221+
static bool dflash_tensor_buffer_is_meta(const ggml_tensor * tensor) {
222+
if (!tensor || !tensor->buffer) {
223+
return false;
224+
}
225+
226+
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(tensor->buffer);
227+
ggml_backend_dev_t dev = buft ? ggml_backend_buft_get_device(buft) : nullptr;
228+
return ggml_backend_dev_is_meta(dev);
229+
}
230+
183231
static void dflash_capture_add_wait_backend(
184232
dflash_capture_data & cap,
185233
ggml_backend_t backend,
@@ -2161,8 +2209,8 @@ void llama_context::allocate_tape_gpu(int n_slots, int max_tokens) {
21612209
for (int li = 0; li < n_rec; ++li) {
21622210
const int il = rec_ids[li];
21632211
ggml_backend_dev_t layer_dev = model.dev_layer(il);
2164-
ggml_backend_t layer_backend = dflash_backend_for_dev(backends, layer_dev);
2165-
if (!layer_backend) {
2212+
const dflash_capture_backend capture_backend = dflash_capture_backend_for_layer(backends, layer_dev);
2213+
if (!capture_backend.backend) {
21662214
LLAMA_LOG_WARN("%s: no GPU backend for recurrent layer %d device %s; falling back to CPU tape\n",
21672215
__func__, il, layer_dev ? ggml_backend_dev_name(layer_dev) : "<null>");
21682216
dflash_capture->tapes.clear();
@@ -2193,29 +2241,29 @@ void llama_context::allocate_tape_gpu(int n_slots, int max_tokens) {
21932241
tl.qkv = ggml_new_tensor_2d(tape_ctx, GGML_TYPE_F32, conv_ch, (int64_t)max_tokens);
21942242

21952243
tl.ctx = tape_ctx;
2196-
tl.dev = layer_dev;
2197-
tl.buf = ggml_backend_alloc_ctx_tensors(tape_ctx, layer_backend);
2244+
tl.dev = capture_backend.dev;
2245+
tl.buf = ggml_backend_alloc_ctx_tensors(tape_ctx, capture_backend.backend);
21982246

21992247
if (!tl.buf) {
22002248
LLAMA_LOG_WARN("%s: failed to allocate GPU tape buffer for slot %d layer %d device %s, falling back to CPU tape\n",
2201-
__func__, slot, il, layer_dev ? ggml_backend_dev_name(layer_dev) : "<null>");
2249+
__func__, slot, il, capture_backend.dev ? ggml_backend_dev_name(capture_backend.dev) : "<null>");
22022250
dflash_capture->tapes.clear();
22032251
return;
22042252
}
22052253

2206-
dflash_capture_add_wait_backend(*dflash_capture, layer_backend, ggml_backend_dev_backend_reg(layer_dev));
2254+
dflash_capture_add_wait_backend(*dflash_capture, capture_backend.backend, ggml_backend_dev_backend_reg(capture_backend.dev));
22072255
total_size += ggml_backend_buffer_get_size(tl.buf);
22082256

22092257
bool found = false;
22102258
for (auto & dc : dev_counts) {
2211-
if (dc.dev == layer_dev) {
2259+
if (dc.dev == capture_backend.dev) {
22122260
++dc.count;
22132261
found = true;
22142262
break;
22152263
}
22162264
}
22172265
if (!found) {
2218-
dev_counts.push_back({ layer_dev, 1 });
2266+
dev_counts.push_back({ capture_backend.dev, 1 });
22192267
}
22202268
}
22212269

@@ -2275,8 +2323,8 @@ void llama_context::allocate_hidden_gpu(int n_slots, int max_tokens) {
22752323
for (int i = 0; i < n_layers; ++i) {
22762324
const int il = dflash_capture->layer_ids[i];
22772325
ggml_backend_dev_t layer_dev = model.dev_layer(il);
2278-
ggml_backend_t layer_backend = dflash_backend_for_dev(backends, layer_dev);
2279-
if (!layer_backend) {
2326+
const dflash_capture_backend capture_backend = dflash_capture_backend_for_layer(backends, layer_dev);
2327+
if (!capture_backend.backend) {
22802328
LLAMA_LOG_WARN("%s: no GPU backend for hidden layer %d device %s; using callback hidden fallback\n",
22812329
__func__, il, layer_dev ? ggml_backend_dev_name(layer_dev) : "<null>");
22822330
dflash_capture->hidden_gpu.clear();
@@ -2296,15 +2344,15 @@ void llama_context::allocate_hidden_gpu(int n_slots, int max_tokens) {
22962344

22972345
hidden->layers[i] = ggml_new_tensor_2d(hidden_ctx, GGML_TYPE_F32, n_embd, (int64_t) max_tokens);
22982346

2299-
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(hidden_ctx, layer_backend);
2347+
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(hidden_ctx, capture_backend.backend);
23002348
if (!buf) {
23012349
LLAMA_LOG_WARN("%s: failed to allocate GPU hidden buffer for slot %d layer %d; using callback hidden fallback\n",
23022350
__func__, slot, il);
23032351
dflash_capture->hidden_gpu.clear();
23042352
return;
23052353
}
23062354
hidden->bufs.push_back(buf);
2307-
dflash_capture_add_wait_backend(*dflash_capture, layer_backend, ggml_backend_dev_backend_reg(layer_dev));
2355+
dflash_capture_add_wait_backend(*dflash_capture, capture_backend.backend, ggml_backend_dev_backend_reg(capture_backend.dev));
23082356
total_size += ggml_backend_buffer_get_size(buf);
23092357
}
23102358

@@ -2361,8 +2409,8 @@ bool llama_context::allocate_prefill_gpu(int n_slots, int max_tokens) {
23612409
for (int i = 0; i < n_layers; ++i) {
23622410
const int il = dflash_capture->layer_ids[i];
23632411
ggml_backend_dev_t layer_dev = model.dev_layer(il);
2364-
ggml_backend_t layer_backend = dflash_backend_for_dev(backends, layer_dev);
2365-
if (!layer_backend) {
2412+
const dflash_capture_backend capture_backend = dflash_capture_backend_for_layer(backends, layer_dev);
2413+
if (!capture_backend.backend) {
23662414
LLAMA_LOG_WARN("%s: no GPU backend for prefill layer %d device %s; using callback fallback\n",
23672415
__func__, il, layer_dev ? ggml_backend_dev_name(layer_dev) : "<null>");
23682416
dflash_capture->prefill_gpu.clear();
@@ -2384,7 +2432,7 @@ bool llama_context::allocate_prefill_gpu(int n_slots, int max_tokens) {
23842432

23852433
hidden->layers[i] = ggml_new_tensor_2d(hidden_ctx, GGML_TYPE_F32, n_embd, (int64_t) max_tokens);
23862434

2387-
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(hidden_ctx, layer_backend);
2435+
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(hidden_ctx, capture_backend.backend);
23882436
if (!buf) {
23892437
LLAMA_LOG_WARN("%s: failed to allocate prefill GPU buffer for slot %d layer %d; using callback fallback\n",
23902438
__func__, slot, il);
@@ -2393,7 +2441,7 @@ bool llama_context::allocate_prefill_gpu(int n_slots, int max_tokens) {
23932441
return false;
23942442
}
23952443
hidden->bufs.push_back(buf);
2396-
dflash_capture_add_wait_backend(*dflash_capture, layer_backend, ggml_backend_dev_backend_reg(layer_dev));
2444+
dflash_capture_add_wait_backend(*dflash_capture, capture_backend.backend, ggml_backend_dev_backend_reg(capture_backend.dev));
23972445
total_size += ggml_backend_buffer_get_size(buf);
23982446
}
23992447

@@ -8792,7 +8840,8 @@ bool llama_context::cross_ring_gpu_write_hidden(void * handle, int layer, int ri
87928840
}
87938841

87948842
auto * tensor = hgpu->layers[layer];
8795-
if (!tensor || !tensor->data) {
8843+
const bool tensor_is_meta = dflash_tensor_buffer_is_meta(tensor);
8844+
if (!tensor || !tensor->buffer || (!tensor_is_meta && !tensor->data)) {
87968845
return false;
87978846
}
87988847
if (!dflash_gpu_hidden_span_in_bounds(tensor, src_offset, n_tokens, n_embd, __func__)) {
@@ -8801,15 +8850,17 @@ bool llama_context::cross_ring_gpu_write_hidden(void * handle, int layer, int ri
88018850

88028851
auto * h = (dflash_cross_ring_handle *)handle;
88038852
const size_t src_offset_bytes = (size_t) src_offset * (size_t) n_embd * sizeof(float);
8804-
const void * src = (const char *) tensor->data + src_offset_bytes;
8805-
if (h->fn_write_d2d(h->gpu_ring, layer, ring_pos, src, n_tokens, n_embd)) {
8806-
return true;
8853+
if (!tensor_is_meta) {
8854+
const void * src = (const char *) tensor->data + src_offset_bytes;
8855+
if (h->fn_write_d2d(h->gpu_ring, layer, ring_pos, src, n_tokens, n_embd)) {
8856+
return true;
8857+
}
88078858
}
88088859

88098860
static bool warned_d2h_fallback = false;
88108861
if (!warned_d2h_fallback) {
8811-
LLAMA_LOG_WARN("%s: GPU hidden D2D ring write unavailable; falling back to GPU readback + H2D ring upload\n",
8812-
__func__);
8862+
LLAMA_LOG_WARN("%s: GPU hidden D2D ring write unavailable%s; falling back to backend readback + H2D ring upload\n",
8863+
__func__, tensor_is_meta ? " for Meta capture tensor" : "");
88138864
warned_d2h_fallback = true;
88148865
}
88158866

@@ -8843,7 +8894,8 @@ bool llama_context::prefill_gpu_write_hidden(void * handle, int slot, int layer,
88438894
}
88448895

88458896
auto * tensor = pgpu->layers[layer];
8846-
if (!tensor || !tensor->data) {
8897+
const bool tensor_is_meta = dflash_tensor_buffer_is_meta(tensor);
8898+
if (!tensor || !tensor->buffer || (!tensor_is_meta && !tensor->data)) {
88478899
return false;
88488900
}
88498901
if (!dflash_gpu_hidden_span_in_bounds(tensor, src_offset, n_tokens, n_embd, __func__)) {
@@ -8852,15 +8904,17 @@ bool llama_context::prefill_gpu_write_hidden(void * handle, int slot, int layer,
88528904

88538905
auto * h = (dflash_cross_ring_handle *)handle;
88548906
const size_t src_offset_bytes = (size_t) src_offset * (size_t) n_embd * sizeof(float);
8855-
const void * src = (const char *) tensor->data + src_offset_bytes;
8856-
if (h->fn_write_d2d(h->gpu_ring, layer, ring_pos, src, n_tokens, n_embd)) {
8857-
return true;
8907+
if (!tensor_is_meta) {
8908+
const void * src = (const char *) tensor->data + src_offset_bytes;
8909+
if (h->fn_write_d2d(h->gpu_ring, layer, ring_pos, src, n_tokens, n_embd)) {
8910+
return true;
8911+
}
88588912
}
88598913

88608914
static bool warned_prefill_d2h_fallback = false;
88618915
if (!warned_prefill_d2h_fallback) {
8862-
LLAMA_LOG_WARN("%s: prefill GPU D2D ring write unavailable; falling back to GPU readback + H2D ring upload\n",
8863-
__func__);
8916+
LLAMA_LOG_WARN("%s: prefill GPU D2D ring write unavailable%s; falling back to backend readback + H2D ring upload\n",
8917+
__func__, tensor_is_meta ? " for Meta capture tensor" : "");
88648918
warned_prefill_d2h_fallback = true;
88658919
}
88668920

0 commit comments

Comments
 (0)