Skip to content

Commit ecc90d2

Browse files
author
gbo
committed
DFlash Vulkan fixes: rollback return type, reduced verify fallback, b- tensor alias, cross-ring infrastructure
- Change llama_dflash_rollback/tape_replay return types from void to int, returning positions needing re-decode when tape replay is unavailable - Add dflash_reduced_verify_broken flag to disable reduced verify on backends lacking TOPK support (Vulkan), falling back to full logits - Add b- beta tensor alias for Qwen3Next tape recording - Add linear_attn_qkv_mixed- tape alias for Qwen3-Coder-Next conv replay - Add GGML_DFLASH_FORCE_REDECODE env var for testing re-decode paths - Add Vulkan get_proc_address resolver for DFlash cross-ring functions - Add Vulkan dflash_is_cuda_compatible_tensor support - Include placeholder ggml-vulkan-cross-ring.cpp (not used at runtime) - Add docs/vulkan-cross-ring-plan.md with Approach A/C documentation Fixes crash on Vulkan: GGML_ASSERT(logits != nullptr) in get_logits_ith when reduced verify failed due to missing TOPK support.
1 parent 7689a40 commit ecc90d2

8 files changed

Lines changed: 607 additions & 35 deletions

File tree

docs/vulkan-cross-ring-plan.md

Lines changed: 368 additions & 0 deletions
Large diffs are not rendered by default.

ggml/src/ggml-vulkan/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ if (Vulkan_FOUND)
6161

6262
ggml_add_backend_library(ggml-vulkan
6363
ggml-vulkan.cpp
64+
ggml-vulkan-cross-ring.cpp
6465
../../include/ggml-vulkan.h
6566
)
6667

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Vulkan UMA DFlash GPU cross-ring — Approach A (placeholder)
2+
//
3+
// Placeholder implementation. The Vulkan GPU ring is currently not used
4+
// (dflash_gpu_backend_reg does not return Vulkan). This file exists to
5+
// provide symbols for the get_proc_address resolver but is not actually
6+
// invoked at runtime.
7+
//
8+
// When Approach A is fully implemented, this will use host-visible coherent
9+
// Vulkan buffers for the ring + interleave, and ggml_backend_tensor_set for
10+
// the final H2D upload to the drafter's target_hidden tensor.
11+
12+
#include <cstdlib>
13+
#include <cstdio>
14+
#include <cstring>
15+
16+
struct dflash_cross_ring_vk {
17+
int n_layers;
18+
int n_embd;
19+
int ring_size;
20+
float ** h_layer_ptrs;
21+
float * h_staging;
22+
};
23+
24+
extern "C" void * dflash_cross_ring_gpu_alloc(int n_layers, int n_embd, int ring_size) {
25+
(void)n_layers; (void)n_embd; (void)ring_size;
26+
return nullptr; // not used — Vulkan ring is disabled
27+
}
28+
29+
extern "C" void * dflash_cross_ring_gpu_alloc_device(int, int n_layers, int n_embd, int ring_size) {
30+
return dflash_cross_ring_gpu_alloc(n_layers, n_embd, ring_size);
31+
}
32+
33+
extern "C" void dflash_cross_ring_gpu_free(void * handle) {
34+
if (!handle) return;
35+
auto * ring = (dflash_cross_ring_vk *)handle;
36+
if (ring->h_staging) free(ring->h_staging);
37+
if (ring->h_layer_ptrs) {
38+
for (int l = 0; l < ring->n_layers; l++) {
39+
if (ring->h_layer_ptrs[l]) free(ring->h_layer_ptrs[l]);
40+
}
41+
delete[] ring->h_layer_ptrs;
42+
}
43+
delete ring;
44+
}
45+
46+
extern "C" void dflash_cross_ring_gpu_write(void *, int, int, const float *, int, int) {}
47+
extern "C" bool dflash_cross_ring_gpu_write_d2d(void *, int, int, const void *, int, int) { return false; }
48+
extern "C" void dflash_cross_ring_gpu_synchronize(void *) {}
49+
extern "C" bool dflash_cross_ring_gpu_snapshot(void *, int, int, int, float *, int, int, int) { return false; }
50+
extern "C" const float * dflash_cross_ring_gpu_interleave(void *, int, int, int) { return nullptr; }
51+
extern "C" void dflash_cross_ring_gpu_set_tensor(void *, const void *, size_t, size_t) {}

ggml/src/ggml-vulkan/ggml-vulkan.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17112,11 +17112,38 @@ static ggml_backend_dev_t ggml_backend_vk_reg_get_device(ggml_backend_reg_t reg,
1711217112
return devices[device];
1711317113
}
1711417114

17115+
// Forward declarations for DFlash cross-ring functions (defined in ggml-vulkan-cross-ring.cpp)
17116+
extern "C" {
17117+
void * dflash_cross_ring_gpu_alloc(int, int, int);
17118+
void * dflash_cross_ring_gpu_alloc_device(int, int, int, int);
17119+
void dflash_cross_ring_gpu_free(void *);
17120+
void dflash_cross_ring_gpu_write(void *, int, int, const float *, int, int);
17121+
bool dflash_cross_ring_gpu_write_d2d(void *, int, int, const void *, int, int);
17122+
void dflash_cross_ring_gpu_synchronize(void *);
17123+
bool dflash_cross_ring_gpu_snapshot(void *, int, int, int, float *, int, int, int);
17124+
const float * dflash_cross_ring_gpu_interleave(void *, int, int, int);
17125+
void dflash_cross_ring_gpu_set_tensor(void *, const void *, size_t, size_t);
17126+
}
17127+
17128+
// DFlash cross-ring function pointer resolver for Vulkan UMA
17129+
static void * ggml_backend_vk_reg_get_proc_address(ggml_backend_reg_t /* reg */, const char * name) {
17130+
if (strcmp(name, "dflash_cross_ring_gpu_alloc") == 0) return (void *)(void *)dflash_cross_ring_gpu_alloc;
17131+
if (strcmp(name, "dflash_cross_ring_gpu_alloc_device") == 0) return (void *)(void *)dflash_cross_ring_gpu_alloc_device;
17132+
if (strcmp(name, "dflash_cross_ring_gpu_free") == 0) return (void *)dflash_cross_ring_gpu_free;
17133+
if (strcmp(name, "dflash_cross_ring_gpu_write") == 0) return (void *)dflash_cross_ring_gpu_write;
17134+
if (strcmp(name, "dflash_cross_ring_gpu_write_d2d") == 0) return (void *)dflash_cross_ring_gpu_write_d2d;
17135+
if (strcmp(name, "dflash_cross_ring_gpu_synchronize") == 0) return (void *)dflash_cross_ring_gpu_synchronize;
17136+
if (strcmp(name, "dflash_cross_ring_gpu_snapshot") == 0) return (void *)dflash_cross_ring_gpu_snapshot;
17137+
if (strcmp(name, "dflash_cross_ring_gpu_interleave") == 0) return (void *)dflash_cross_ring_gpu_interleave;
17138+
if (strcmp(name, "dflash_cross_ring_gpu_set_tensor") == 0) return (void *)dflash_cross_ring_gpu_set_tensor;
17139+
return nullptr;
17140+
}
17141+
1711517142
static const struct ggml_backend_reg_i ggml_backend_vk_reg_i = {
1711617143
/* .get_name = */ ggml_backend_vk_reg_get_name,
1711717144
/* .get_device_count = */ ggml_backend_vk_reg_get_device_count,
1711817145
/* .get_device = */ ggml_backend_vk_reg_get_device,
17119-
/* .get_proc_address = */ NULL,
17146+
/* .get_proc_address = */ ggml_backend_vk_reg_get_proc_address,
1712017147
};
1712117148

1712217149
ggml_backend_reg_t ggml_backend_vk_reg() {

include/llama.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,10 @@ extern "C" {
12051205
// - KV cache: trims rejected draft positions (keeps accepted tokens' KV entries)
12061206
// - Recurrent state: restores from backup + tape replay for accepted tokens
12071207
// This replaces the manual seq_rm/seq_cp + tape_replay sequence
1208-
LLAMA_API void llama_dflash_rollback(
1208+
// Returns the number of positions that were NOT advanced by tape replay
1209+
// and need re-decoding (e.g., when tape replay is unavailable on the backend).
1210+
// A return value of 0 means the rollback was fully successful.
1211+
LLAMA_API int llama_dflash_rollback(
12091212
struct llama_context * ctx,
12101213
llama_seq_id seq_id,
12111214
llama_seq_id seq_backup,

src/llama-context.cpp

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "ggml-alloc.h"
2323

2424
#include <algorithm>
25+
#include <atomic>
2526
#include <cinttypes>
2627
#include <cmath>
2728
#include <cstdlib>
@@ -76,7 +77,8 @@ static bool dflash_is_cuda_compatible_tensor(const ggml_tensor * t) {
7677
return false;
7778
}
7879
const char * name = ggml_backend_buffer_name(t->buffer);
79-
return name && (std::strncmp(name, "CUDA", 4) == 0 || std::strncmp(name, "ROCm", 4) == 0);
80+
return name && (std::strncmp(name, "CUDA", 4) == 0 || std::strncmp(name, "ROCm", 4) == 0 ||
81+
std::strncmp(name, "Vulkan", 6) == 0);
8082
}
8183

8284
static bool dflash_tensor_span_in_bounds(const ggml_tensor * t, size_t offset_bytes, size_t n_bytes) {
@@ -2003,10 +2005,18 @@ void llama_context::dflash_ensure_recurrent_setup() {
20032005
dflash_capture->tape_name_map["v_conv_predelta-" + il_str] = {idx, DFLASH_TAPE_V};
20042006
dflash_capture->tape_name_map["gate-" + il_str] = {idx, DFLASH_TAPE_GATE};
20052007
dflash_capture->tape_name_map["beta-" + il_str] = {idx, DFLASH_TAPE_BETA};
2008+
// qwen3next (Qwen3-Coder-Next) names the beta tensor "b" instead of "beta"
2009+
dflash_capture->tape_name_map["b-" + il_str] = {idx, DFLASH_TAPE_BETA};
20062010
dflash_capture->tape_name_map["qkv_mixed_pretranspose-" + il_str] = {idx, DFLASH_TAPE_QKV};
2007-
// Qwen3Next emits pre-conv QKV under these names. Without one of
2008-
// them, DFlash conv replay skips every recurrent layer and leaves
2009-
// r_l frozen at the pre-draft backup state.
2011+
// qwen3next (Qwen3-Coder-Next) builds the pre-conv (pre-transpose) QKV via two
2012+
// code paths in build_qkvz(), both projecting the layer INPUT (pre-conv1d):
2013+
// wqkv path: "linear_attn_qkv_mixed-" (qwen3next.cpp:304) [used by Qwen3-Coder-Next]
2014+
// ssm_in legacy: "qkv_mixed-" (qwen3next.cpp:364) [concat of q/k/v flats]
2015+
// Both are the input to build_conv_state() (qwen3next.cpp:440), i.e. PRE-conv.
2016+
// Without recording one of these, tape_replay_conv skips every layer (empty
2017+
// qkv_mixed) and the conv state (r_l) is never advanced, so the conv state stays
2018+
// frozen at the backup value and the target output is garbled (0% acceptance).
2019+
// Verified: 504/504 conv layers OK after this fix; conv state matches re-decode ref.
20102020
dflash_capture->tape_name_map["linear_attn_qkv_mixed-" + il_str] = {idx, DFLASH_TAPE_QKV};
20112021
dflash_capture->tape_name_map["qkv_mixed-" + il_str] = {idx, DFLASH_TAPE_QKV};
20122022
}
@@ -2624,9 +2634,13 @@ void llama_context::set_active_dflash_slot(int slot_idx) {
26242634
}
26252635
}
26262636

2627-
void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
2637+
int llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
26282638
if (!dflash_capture || n_accepted <= 0) {
2629-
return;
2639+
return 0;
2640+
}
2641+
2642+
if (const char * env = std::getenv("GGML_DFLASH_FORCE_REDECODE"); env && std::atoi(env) != 0) {
2643+
return n_accepted;
26302644
}
26312645

26322646
// ensure any previous async replay is complete before launching a new one
@@ -2639,7 +2653,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
26392653
n_accepted <= gpu_tape->n_tokens);
26402654

26412655
if (!use_gpu_tape && dflash_capture->tape_layers.empty()) {
2642-
return;
2656+
return 0;
26432657
}
26442658

26452659
auto * mem_recurrent = dynamic_cast<llama_memory_recurrent *>(memory.get());
@@ -2651,7 +2665,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
26512665
}
26522666
if (!mem_recurrent) {
26532667
LLAMA_LOG_WARN("%s: tape replay requires recurrent memory\n", __func__);
2654-
return;
2668+
return n_accepted;
26552669
}
26562670

26572671
const auto & hparams = model.hparams;
@@ -2668,7 +2682,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
26682682
}
26692683
if (cell_idx < 0) {
26702684
LLAMA_LOG_WARN("%s: no active cell for seq %d\n", __func__, seq_id);
2671-
return;
2685+
return n_accepted;
26722686
}
26732687

26742688
const uint32_t n_embd_s = hparams.n_embd_s();
@@ -2686,7 +2700,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
26862700
if (!gpu_backend) {
26872701
tape_replay_cpu(mem_recurrent, cell_idx, n_accepted);
26882702
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2689-
return;
2703+
return 0;
26902704
}
26912705

26922706
// partial offload: if any recurrent layer's state lives on CPU, fall back to CPU replay
@@ -2696,7 +2710,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
26962710
if (s_tensor && s_tensor->buffer && ggml_backend_buffer_is_host(s_tensor->buffer)) {
26972711
tape_replay_cpu(mem_recurrent, cell_idx, n_accepted);
26982712
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2699-
return;
2713+
return 0;
27002714
}
27012715
}
27022716

@@ -2709,14 +2723,14 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
27092723
dflash_capture->replay_cell_idx = cell_idx;
27102724
dflash_capture->replay_seq_id = seq_id;
27112725
dflash_capture->replay_mem_recurrent = mem_recurrent;
2712-
return;
2726+
return 0;
27132727
}
27142728

27152729
const bool multi_gpu_target = model.n_devices() > 1;
27162730
if (multi_gpu_target) {
27172731
if (tape_replay_gdn_direct_from_cpu_tape(mem_recurrent, cell_idx, n_accepted)) {
27182732
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2719-
return;
2733+
return 0;
27202734
}
27212735
if (!dflash_capture->multi_gpu_replay_fallback_logged) {
27222736
LLAMA_LOG_WARN("%s: multi-GPU target detected (%zu devices); exact CUDA DFlash replay unavailable, using CPU recurrent replay fallback\n",
@@ -2725,7 +2739,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
27252739
}
27262740
tape_replay_cpu(mem_recurrent, cell_idx, n_accepted);
27272741
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2728-
return;
2742+
return 0;
27292743
}
27302744

27312745
// GPU tape replay: build a ggml graph with GDN ops for all recurrent layers
@@ -2872,7 +2886,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
28722886
if (!use_gpu_tape) {
28732887
tape_replay_cpu(mem_recurrent, cell_idx, n_accepted);
28742888
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2875-
return;
2889+
return 0;
28762890
}
28772891
goto conv_rebuild;
28782892
}
@@ -2900,7 +2914,7 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
29002914
ggml_free(ctx);
29012915
tape_replay_cpu(mem_recurrent, cell_idx, n_accepted);
29022916
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2903-
return;
2917+
return 0;
29042918
}
29052919

29062920
// assign tensors within the persistent buffer
@@ -2955,8 +2969,9 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
29552969
if (!use_gpu_tape) {
29562970
tape_replay_cpu(mem_recurrent, cell_idx, n_accepted);
29572971
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2972+
return 0;
29582973
}
2959-
return;
2974+
return n_accepted;
29602975
}
29612976
if (dflash_capture->profile) {
29622977
const uint64_t elapsed = ggml_time_us() - t_replay_enqueue_us;
@@ -2976,11 +2991,12 @@ void llama_context::tape_replay(llama_seq_id seq_id, int n_accepted) {
29762991
dflash_capture->replay_cell_idx = cell_idx;
29772992
dflash_capture->replay_seq_id = seq_id;
29782993
dflash_capture->replay_mem_recurrent = mem_recurrent;
2979-
return; // conv rebuild deferred to tape_replay_sync()
2994+
return 0; // conv rebuild deferred to tape_replay_sync()
29802995
}
29812996

29822997
conv_rebuild:
29832998
tape_replay_conv(mem_recurrent, cell_idx, n_accepted, seq_id);
2999+
return 0;
29843000
}
29853001

29863002
bool llama_context::tape_replay_gdn_direct_gpu(llama_memory_recurrent * mem_recurrent, int32_t cell_idx, int n_accepted) {
@@ -3944,11 +3960,11 @@ void llama_context::tape_replay_cpu(llama_memory_recurrent * mem_recurrent, int3
39443960
}
39453961
}
39463962

3947-
void llama_context::dflash_rollback(llama_seq_id seq_id, llama_seq_id seq_backup, int n_past_before, int n_accepted) {
3963+
int llama_context::dflash_rollback(llama_seq_id seq_id, llama_seq_id seq_backup, int n_past_before, int n_accepted) {
39483964
auto * mem_hybrid = dynamic_cast<llama_memory_hybrid *>(memory.get());
39493965
if (!mem_hybrid) {
39503966
LLAMA_LOG_WARN("%s: dflash_rollback requires hybrid memory\n", __func__);
3951-
return;
3967+
return n_accepted;
39523968
}
39533969

39543970
const bool profile = dflash_capture && dflash_profile_has(dflash_capture->profile_flags, DFLASH_PROFILE_COPY);
@@ -3997,7 +4013,7 @@ void llama_context::dflash_rollback(llama_seq_id seq_id, llama_seq_id seq_backup
39974013
profile_lap(recurrent_restore_us);
39984014

39994015
// Replay DeltaNet state updates for accepted tokens
4000-
tape_replay(seq_id, n_accepted);
4016+
const int n_redecode = tape_replay(seq_id, n_accepted);
40014017
profile_lap(tape_launch_us);
40024018

40034019
if (profile) {
@@ -4019,6 +4035,7 @@ void llama_context::dflash_rollback(llama_seq_id seq_id, llama_seq_id seq_backup
40194035
tape_launch_us / 1e3,
40204036
(ggml_time_us() - t_start_us) / 1e3);
40214037
}
4038+
return n_redecode;
40224039
}
40234040

40244041
void llama_context::dflash_prepare_branch(llama_seq_id seq_id, llama_seq_id seq_backup, int depth) {
@@ -8644,8 +8661,8 @@ bool llama_dflash_memory_seq_cp_recurrent_ordered(
86448661
return ctx ? ctx->dflash_memory_seq_cp_recurrent_ordered(seq_id_src, seq_id_dst, p0, p1) : false;
86458662
}
86468663

8647-
void llama_dflash_rollback(llama_context * ctx, llama_seq_id seq_id, llama_seq_id seq_backup, int n_past_before, int n_accepted) {
8648-
ctx->dflash_rollback(seq_id, seq_backup, n_past_before, n_accepted);
8664+
int llama_dflash_rollback(llama_context * ctx, llama_seq_id seq_id, llama_seq_id seq_backup, int n_past_before, int n_accepted) {
8665+
return ctx->dflash_rollback(seq_id, seq_backup, n_past_before, n_accepted);
86498666
}
86508667

86518668
void llama_dflash_prepare_branch(llama_context * ctx, llama_seq_id seq_id, llama_seq_id seq_backup, int depth) {

src/llama-context.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,9 @@ struct llama_context {
738738
void set_active_dflash_slot(int slot_idx);
739739

740740
// DFlash: replay tape data to reconstruct DeltaNet state for n_accepted tokens
741-
void tape_replay(llama_seq_id seq_id, int n_accepted);
741+
// Returns the number of positions that were NOT advanced by tape replay
742+
// and need re-decoding. A return value of 0 means the rollback was fully successful.
743+
int tape_replay(llama_seq_id seq_id, int n_accepted);
742744
void tape_replay_sync();
743745
bool tape_replay_conv_gpu(llama_memory_recurrent * mem_recurrent, int32_t cell_idx, int n_accepted);
744746
bool tape_replay_conv_gpu(llama_memory_recurrent * mem_recurrent, int32_t cell_idx, int n_accepted, bool advance_pos);
@@ -750,7 +752,9 @@ struct llama_context {
750752
bool dflash_memory_seq_cp_recurrent_ordered(llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1);
751753

752754
// DFlash: complete rollback for hybrid models (KV trim + recurrent restore + tape replay)
753-
void dflash_rollback(llama_seq_id seq_id, llama_seq_id seq_backup, int n_past_before, int n_accepted);
755+
// Returns the number of positions that were NOT advanced by tape replay
756+
// and need re-decoding. A return value of 0 means the rollback was fully successful.
757+
int dflash_rollback(llama_seq_id seq_id, llama_seq_id seq_backup, int n_past_before, int n_accepted);
754758

755759
// DFlash: prepare DeltaNet state for branch verification (recurrent restore + tape replay, no KV touch)
756760
void dflash_prepare_branch(llama_seq_id seq_id, llama_seq_id seq_backup, int depth);

0 commit comments

Comments
 (0)