Skip to content

Commit cb83fe7

Browse files
committed
cuda: fix Turbo K classic V FA prefill
Route Turbo K with classic non-q8 V through the Turbo prefill MMA path for Gemma-sized D256/D512 batch attention, while preserving native vec fallback for decode paths and prior classic-K/Turbo-V safeguards. Add a route-policy regression test covering the mixed-pair gaps.
1 parent 60f5926 commit cb83fe7

4 files changed

Lines changed: 200 additions & 26 deletions

File tree

ggml/src/ggml-cuda/fattn.cu

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,53 @@ static __global__ void k_turbo_fwht_forward(
10261026
}
10271027
}
10281028

1029+
static inline bool ggml_cuda_fattn_is_classic_non_q8_type(const ggml_type type) {
1030+
return type == GGML_TYPE_BF16 ||
1031+
type == GGML_TYPE_Q4_0 ||
1032+
type == GGML_TYPE_Q4_1 ||
1033+
type == GGML_TYPE_Q5_0 ||
1034+
type == GGML_TYPE_Q5_1 ||
1035+
type == GGML_TYPE_Q6_0 ||
1036+
type == GGML_TYPE_Q6_1 ||
1037+
type == GGML_TYPE_Q3_0 ||
1038+
type == GGML_TYPE_Q3_1 ||
1039+
type == GGML_TYPE_Q2_0 ||
1040+
type == GGML_TYPE_Q2_1;
1041+
}
1042+
1043+
static void ggml_cuda_fattn_materialize_to_f16(
1044+
const ggml_tensor * src, half * dst, cudaStream_t stream, ggml_tensor & src_f16) {
1045+
const size_t bs = ggml_blck_size(src->type);
1046+
const size_t ts = ggml_type_size(src->type);
1047+
1048+
src_f16 = *src;
1049+
src_f16.type = GGML_TYPE_F16;
1050+
src_f16.data = dst;
1051+
src_f16.nb[0] = sizeof(half);
1052+
1053+
if (ggml_is_contiguously_allocated(src)) {
1054+
const to_fp16_cuda_t to_fp16 = ggml_get_to_fp16_cuda(src->type);
1055+
GGML_ASSERT(to_fp16 != nullptr);
1056+
to_fp16(src->data, dst, ggml_nelements(src), stream);
1057+
1058+
src_f16.nb[1] = src->nb[1] * bs * sizeof(half) / ts;
1059+
src_f16.nb[2] = src->nb[2] * bs * sizeof(half) / ts;
1060+
src_f16.nb[3] = src->nb[3] * bs * sizeof(half) / ts;
1061+
} else {
1062+
GGML_ASSERT(src->nb[0] == ts);
1063+
const to_fp16_nc_cuda_t to_fp16 = ggml_get_to_fp16_nc_cuda(src->type);
1064+
GGML_ASSERT(to_fp16 != nullptr);
1065+
const int64_t s01 = src->nb[1] / ts;
1066+
const int64_t s02 = src->nb[2] / ts;
1067+
const int64_t s03 = src->nb[3] / ts;
1068+
to_fp16(src->data, dst, src->ne[0], src->ne[1], src->ne[2], src->ne[3], s01, s02, s03, stream);
1069+
1070+
src_f16.nb[1] = src->ne[0] * sizeof(half);
1071+
src_f16.nb[2] = src->ne[1] * src_f16.nb[1];
1072+
src_f16.nb[3] = src->ne[2] * src_f16.nb[2];
1073+
}
1074+
}
1075+
10291076
static void ggml_cuda_turbo_prefill_attend(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
10301077
load_tcq_decode_alpha(ctx.device);
10311078
cudaStream_t stream = ctx.stream();
@@ -1034,12 +1081,17 @@ static void ggml_cuda_turbo_prefill_attend(ggml_backend_cuda_context & ctx, ggml
10341081

10351082
const bool turbo_k = K->type == GGML_TYPE_TURBO2_0 || K->type == GGML_TYPE_TURBO3_0 || K->type == GGML_TYPE_TURBO4_0 || K->type == GGML_TYPE_TURBO4_TCQ || K->type == GGML_TYPE_TURBO3_TCQ || K->type == GGML_TYPE_TURBO2_TCQ;
10361083
const bool turbo_v = V->type == GGML_TYPE_TURBO2_0 || V->type == GGML_TYPE_TURBO3_0 || V->type == GGML_TYPE_TURBO4_0 || V->type == GGML_TYPE_TURBO4_TCQ || V->type == GGML_TYPE_TURBO3_TCQ || V->type == GGML_TYPE_TURBO2_TCQ;
1084+
const bool classic_non_q8_v = !turbo_v && ggml_cuda_fattn_is_classic_non_q8_type(V->type);
10371085

10381086
int device;
10391087
CUDA_CHECK(cudaGetDevice(&device));
10401088

10411089
half * k_fp16 = nullptr;
10421090
half * v_fp16 = nullptr;
1091+
bool v_f16_layout_set = false;
1092+
1093+
ggml_tensor K_f16 = *K;
1094+
ggml_tensor V_f16 = *V;
10431095

10441096
// Allocate and dequant K to fp16 (turbo2, turbo3, or turbo4)
10451097
if (turbo_k) {
@@ -1115,8 +1167,8 @@ static void ggml_cuda_turbo_prefill_attend(ggml_backend_cuda_context & ctx, ggml
11151167
}
11161168
}
11171169

1118-
// Allocate and dequant V to fp16 (turbo2, turbo3, or turbo4)
1119-
if (turbo_v) {
1170+
// Allocate and materialize V to fp16 (turbo2, turbo3, turbo4, or classic non-q8).
1171+
if (turbo_v || classic_non_q8_v) {
11201172
// Size for full cache (kv_size from root) so we never realloc mid-session.
11211173
const ggml_tensor * v_root = V;
11221174
while (v_root->view_src) v_root = v_root->view_src;
@@ -1182,16 +1234,15 @@ static void ggml_cuda_turbo_prefill_attend(ggml_backend_cuda_context & ctx, ggml
11821234
}
11831235
k_turbo2_tcq_dequant_f16<<<grid_v, V->ne[0], 0, stream>>>(
11841236
(const char *)V->data, v_fp16, V->ne[0], V->ne[1], V->ne[2], V->nb[1], V->nb[2], V->nb[3], tcq_compute_alpha_v(V->type, V->ne[1]));
1185-
} else {
1237+
} else if (V->type == GGML_TYPE_TURBO4_0) {
11861238
k_turbo4_dequant_f16<<<grid_v, V->ne[0], 0, stream>>>(
11871239
(const char *)V->data, v_fp16, V->ne[0], V->ne[1], V->ne[2], V->nb[1], V->nb[2], V->nb[3]);
1240+
} else {
1241+
ggml_cuda_fattn_materialize_to_f16(V, v_fp16, stream, V_f16);
1242+
v_f16_layout_set = true;
11881243
}
11891244
}
11901245

1191-
// Create fp16 tensor copies on stack
1192-
ggml_tensor K_f16 = *K;
1193-
ggml_tensor V_f16 = *V;
1194-
11951246
if (k_fp16) {
11961247
K_f16.type = GGML_TYPE_F16;
11971248
K_f16.data = k_fp16;
@@ -1201,7 +1252,7 @@ static void ggml_cuda_turbo_prefill_attend(ggml_backend_cuda_context & ctx, ggml
12011252
K_f16.nb[3] = K->ne[0] * K->ne[1] * K->ne[2] * sizeof(half);
12021253
}
12031254

1204-
if (v_fp16) {
1255+
if (v_fp16 && !v_f16_layout_set) {
12051256
V_f16.type = GGML_TYPE_F16;
12061257
V_f16.data = v_fp16;
12071258
V_f16.nb[0] = sizeof(half);
@@ -1380,18 +1431,21 @@ static inline bool ggml_cuda_fattn_pair_compiled(const ggml_type type_K_in, cons
13801431
#endif
13811432
}
13821433

1383-
static inline bool ggml_cuda_fattn_is_classic_non_q8_type(const ggml_type type) {
1384-
return type == GGML_TYPE_BF16 ||
1385-
type == GGML_TYPE_Q4_0 ||
1386-
type == GGML_TYPE_Q4_1 ||
1387-
type == GGML_TYPE_Q5_0 ||
1388-
type == GGML_TYPE_Q5_1 ||
1389-
type == GGML_TYPE_Q6_0 ||
1390-
type == GGML_TYPE_Q6_1 ||
1391-
type == GGML_TYPE_Q3_0 ||
1392-
type == GGML_TYPE_Q3_1 ||
1393-
type == GGML_TYPE_Q2_0 ||
1394-
type == GGML_TYPE_Q2_1;
1434+
static inline bool ggml_cuda_fattn_prefers_native_vec_for_turbo_k_classic_v(
1435+
const ggml_tensor * Q, const ggml_tensor * K, const ggml_tensor * V) {
1436+
return ggml_cuda_fattn_is_turbo_kv_type(K->type) &&
1437+
!ggml_cuda_fattn_is_turbo_kv_type(V->type) &&
1438+
ggml_cuda_fattn_is_classic_non_q8_type(V->type) &&
1439+
Q->ne[0] <= 512 &&
1440+
Q->ne[0] % 64 == 0 &&
1441+
ggml_cuda_fattn_pair_compiled(K->type, V->type);
1442+
}
1443+
1444+
static inline bool ggml_cuda_fattn_prefill_mma_can_materialize_turbo_k_classic_v(
1445+
const ggml_tensor * K, const ggml_tensor * V) {
1446+
return ggml_cuda_fattn_is_turbo_kv_type(K->type) &&
1447+
!ggml_cuda_fattn_is_turbo_kv_type(V->type) &&
1448+
ggml_cuda_fattn_is_classic_non_q8_type(V->type);
13951449
}
13961450

13971451
// Shape guard for the effective K/V pair after Turbo V decode-dequant.
@@ -2317,6 +2371,8 @@ static ggml_cuda_fattn_route_plan ggml_cuda_fattn_make_route_plan(const int devi
23172371
#endif
23182372

23192373
const bool turbo_decode_native = getenv("GGML_TURBO_DECODE_NATIVE") != nullptr;
2374+
const bool prefer_native_vec =
2375+
ggml_cuda_fattn_prefers_native_vec_for_turbo_k_classic_v(Q, K, V);
23202376

23212377
const bool turbo_k_only = ggml_cuda_fattn_is_turbo_kv_type(K->type);
23222378
const bool turbo_v_only = ggml_cuda_fattn_is_turbo_kv_type(V->type);
@@ -2327,6 +2383,8 @@ static ggml_cuda_fattn_route_plan ggml_cuda_fattn_make_route_plan(const int devi
23272383
!turbo_k_only;
23282384

23292385
// Decode-dequant policy:
2386+
// - Compiled Turbo K + classic non-q8 V fallback routes stay native vec
2387+
// instead of taking generic f16 materialization after Turbo K decode.
23302388
// - D <= 256: Turbo K/V sides may be decoded to f16.
23312389
// - D = 512: f16/q8_0/Turbo pairs keep the existing f16 route.
23322390
// - D = 512 classic non-q8 K + Turbo V decodes Turbo V to f16 and keeps
@@ -2340,6 +2398,7 @@ static ggml_cuda_fattn_route_plan ggml_cuda_fattn_make_route_plan(const int devi
23402398
plan.decode_dequant =
23412399
!hip_native_tcq_decode &&
23422400
!turbo_decode_native &&
2401+
!prefer_native_vec &&
23432402
turbo_kv &&
23442403
(Q->ne[0] <= 256 ||
23452404
(Q->ne[0] <= 512 && k_f16_q8_or_turbo && v_f16_q8_or_turbo) ||
@@ -2425,6 +2484,7 @@ static ggml_cuda_fattn_route_plan ggml_cuda_fattn_make_route_plan(const int devi
24252484
"Kshape=[%lld,%lld,%lld,%lld] Vshape=[%lld,%lld,%lld,%lld] "
24262485
"Keff=%s Veff=%s "
24272486
"turbo_kv=%d "
2487+
"prefer_native_vec=%d "
24282488
"decode=%d dk=%d dv=%d "
24292489
"unsafe_vec_after_turbo_v_decode=%d allow_vec=%d "
24302490
"kernel=%s "
@@ -2438,6 +2498,7 @@ static ggml_cuda_fattn_route_plan ggml_cuda_fattn_make_route_plan(const int devi
24382498
(long long) V->ne[0], (long long) V->ne[1], (long long) V->ne[2], (long long) V->ne[3],
24392499
ggml_type_name(plan.effective_type_K), ggml_type_name(plan.effective_type_V),
24402500
(int) turbo_kv,
2501+
(int) prefer_native_vec,
24412502
(int) plan.decode_dequant,
24422503
(int) plan.decode_dequant_K,
24432504
(int) plan.decode_dequant_V,
@@ -2588,12 +2649,19 @@ void ggml_cuda_flash_attn_ext(ggml_backend_cuda_context & ctx, ggml_tensor * dst
25882649
return;
25892650
}
25902651

2652+
const bool turbo_k_classic_v_prefill =
2653+
ggml_cuda_fattn_prefill_mma_can_materialize_turbo_k_classic_v(K, V);
2654+
const bool turbo_prefill_can_make_f16_K = ggml_cuda_turbo_prefill_mma_can_make_f16(K->type);
2655+
const bool turbo_prefill_can_make_f16_V = ggml_cuda_turbo_prefill_mma_can_make_f16(V->type);
25912656
const bool turbo_prefill_mma_safe =
25922657
turbo_kv &&
2593-
ggml_cuda_turbo_prefill_mma_can_make_f16(K->type) &&
2594-
ggml_cuda_turbo_prefill_mma_can_make_f16(V->type);
2658+
((turbo_prefill_can_make_f16_K &&
2659+
turbo_prefill_can_make_f16_V) ||
2660+
turbo_k_classic_v_prefill);
2661+
const bool turbo_prefill_turing_mma =
2662+
turing_mma_available(ggml_cuda_info().devices[ggml_cuda_get_device()].cc);
25952663

2596-
if (turbo_prefill_mma_safe && !turbo_prefill_vec && Q->ne[1] > 1 && Q->ne[0] <= 256 && turing_mma_available(ggml_cuda_info().devices[ggml_cuda_get_device()].cc)) {
2664+
if (turbo_prefill_mma_safe && !turbo_prefill_vec && Q->ne[1] > 1 && Q->ne[0] <= 512 && turbo_prefill_turing_mma) {
25972665
if (turbo_fa_debug) {
25982666
fprintf(stderr,
25992667
"GGML_TURBO_FA_DEBUG: path=prefill-dequant K=%s V=%s Q=[%lld,%lld,%lld,%lld] K=[%lld,%lld,%lld,%lld] V=[%lld,%lld,%lld,%lld]\n",
@@ -2608,11 +2676,19 @@ void ggml_cuda_flash_attn_ext(ggml_backend_cuda_context & ctx, ggml_tensor * dst
26082676
} else {
26092677
if (turbo_fa_debug && turbo_kv) {
26102678
fprintf(stderr,
2611-
"GGML_TURBO_FA_DEBUG: path=decode-dequant-or-vec K=%s V=%s Q=[%lld,%lld,%lld,%lld] K=[%lld,%lld,%lld,%lld] V=[%lld,%lld,%lld,%lld]\n",
2679+
"GGML_TURBO_FA_DEBUG: path=decode-dequant-or-vec K=%s V=%s Q=[%lld,%lld,%lld,%lld] K=[%lld,%lld,%lld,%lld] V=[%lld,%lld,%lld,%lld] prefill_safe=%d can_make_f16_K=%d can_make_f16_V=%d turbo_k_classic_v_prefill=%d vec_override=%d batch_ok=%d dim_ok=%d turing_mma=%d\n",
26122680
ggml_type_name(K->type), ggml_type_name(V->type),
26132681
(long long) Q->ne[0], (long long) Q->ne[1], (long long) Q->ne[2], (long long) Q->ne[3],
26142682
(long long) K->ne[0], (long long) K->ne[1], (long long) K->ne[2], (long long) K->ne[3],
2615-
(long long) V->ne[0], (long long) V->ne[1], (long long) V->ne[2], (long long) V->ne[3]);
2683+
(long long) V->ne[0], (long long) V->ne[1], (long long) V->ne[2], (long long) V->ne[3],
2684+
(int) turbo_prefill_mma_safe,
2685+
(int) turbo_prefill_can_make_f16_K,
2686+
(int) turbo_prefill_can_make_f16_V,
2687+
(int) turbo_k_classic_v_prefill,
2688+
(int) turbo_prefill_vec,
2689+
(int) (Q->ne[1] > 1),
2690+
(int) (Q->ne[0] <= 512),
2691+
(int) turbo_prefill_turing_mma);
26162692
}
26172693
load_tcq_decode_alpha(ctx.device);
26182694

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ llama_build_and_test(test-dflash-plumbing.cpp ARGS ${PROJECT_SOURCE_DIR})
286286
target_include_directories(test-dflash-plumbing PRIVATE ${PROJECT_SOURCE_DIR}/src)
287287
llama_build_and_test(test-mtmd-plumbing.cpp ARGS ${PROJECT_SOURCE_DIR})
288288
llama_build_and_test(test-perplexity-plumbing.cpp ARGS ${PROJECT_SOURCE_DIR})
289+
llama_build_and_test(test-cuda-fattn-route-policy.cpp ARGS ${PROJECT_SOURCE_DIR})
289290

290291
if (NOT LLAMA_SANITIZE_ADDRESS AND NOT GGML_SCHED_NO_REALLOC)
291292
# TODO: repair known memory leaks
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include <cstdio>
2+
#include <cstdlib>
3+
#include <fstream>
4+
#include <sstream>
5+
#include <string>
6+
7+
static std::string read_file(const std::string & path) {
8+
std::ifstream file(path);
9+
if (!file.good()) {
10+
std::fprintf(stderr, "failed to open %s\n", path.c_str());
11+
std::exit(1);
12+
}
13+
14+
std::ostringstream ss;
15+
ss << file.rdbuf();
16+
return ss.str();
17+
}
18+
19+
static bool expect(bool ok, const char * message) {
20+
if (!ok) {
21+
std::fprintf(stderr, "%s\n", message);
22+
}
23+
return ok;
24+
}
25+
26+
static std::string slice_between(const std::string & text, const std::string & begin, const std::string & end) {
27+
const size_t b = text.find(begin);
28+
if (b == std::string::npos) {
29+
return {};
30+
}
31+
const size_t e = text.find(end, b);
32+
if (e == std::string::npos) {
33+
return text.substr(b);
34+
}
35+
return text.substr(b, e - b);
36+
}
37+
38+
int main(int argc, char ** argv) {
39+
bool ok = true;
40+
41+
ok &= expect(argc == 2, "expected repo root argument");
42+
if (!ok) {
43+
return 1;
44+
}
45+
46+
const std::string root = argv[1];
47+
const std::string fattn = read_file(root + "/ggml/src/ggml-cuda/fattn.cu");
48+
const std::string helper = slice_between(fattn,
49+
"static inline bool ggml_cuda_fattn_prefers_native_vec_for_turbo_k_classic_v",
50+
"struct ggml_cuda_fattn_route_plan");
51+
const std::string planner = slice_between(fattn,
52+
"static ggml_cuda_fattn_route_plan ggml_cuda_fattn_make_route_plan",
53+
"size_t ggml_cuda_flash_attn_ext_get_alloc_size");
54+
const std::string prefill = slice_between(fattn,
55+
"static void ggml_cuda_turbo_prefill_attend",
56+
"#define FATTN_VEC_CASE");
57+
const std::string prefill_policy = slice_between(fattn,
58+
"static inline bool ggml_cuda_fattn_prefill_mma_can_materialize_turbo_k_classic_v",
59+
"// Shape guard for the effective K/V pair after Turbo V decode-dequant.");
60+
const std::string exec = slice_between(fattn,
61+
"void ggml_cuda_flash_attn_ext(ggml_backend_cuda_context & ctx, ggml_tensor * dst)",
62+
"bool ggml_cuda_flash_attn_ext_support");
63+
64+
ok &= expect(!helper.empty(),
65+
"CUDA FA routing must have an explicit native vec policy for Turbo K + classic V pairs");
66+
ok &= expect(helper.find("ggml_cuda_fattn_is_turbo_kv_type(K->type)") != std::string::npos &&
67+
helper.find("ggml_cuda_fattn_is_turbo_kv_type(V->type)") != std::string::npos,
68+
"native vec policy must distinguish Turbo K from Turbo V");
69+
ok &= expect(helper.find("ggml_cuda_fattn_is_classic_non_q8_type(V->type)") != std::string::npos,
70+
"native vec policy must be limited to classic non-q8 V types");
71+
ok &= expect(helper.find("ggml_cuda_fattn_pair_compiled(K->type, V->type)") != std::string::npos,
72+
"native vec policy must require a compiled raw K/V vec pair");
73+
ok &= expect(helper.find("Q->ne[0] <= 512") != std::string::npos &&
74+
helper.find("Q->ne[0] % 64 == 0") != std::string::npos,
75+
"native vec policy must preserve the vec kernel head-dimension guard");
76+
77+
ok &= expect(planner.find("const bool prefer_native_vec =") != std::string::npos &&
78+
planner.find("ggml_cuda_fattn_prefers_native_vec_for_turbo_k_classic_v(Q, K, V)") != std::string::npos,
79+
"route planner must compute the Turbo K + classic V native vec preference");
80+
ok &= expect(planner.find("!prefer_native_vec &&") != std::string::npos,
81+
"decode-dequant policy must not override preferred native vec Turbo K + classic V pairs");
82+
83+
ok &= expect(prefill.find("classic_non_q8_v") != std::string::npos &&
84+
prefill.find("ggml_cuda_fattn_materialize_to_f16(V, v_fp16, stream, V_f16)") != std::string::npos,
85+
"Turbo K + classic non-q8 V prefill must materialize classic V to f16 for the MMA path");
86+
ok &= expect(exec.find("turbo_k_classic_v_prefill") != std::string::npos &&
87+
exec.find("ggml_cuda_fattn_prefill_mma_can_materialize_turbo_k_classic_v(K, V)") != std::string::npos,
88+
"Turbo K + classic non-q8 V batch prefill must be eligible for the prefill MMA path");
89+
ok &= expect(exec.find("Q->ne[0] <= 512") != std::string::npos,
90+
"Turbo K + classic non-q8 V batch prefill must cover Gemma D512 layers");
91+
ok &= expect(prefill_policy.find("ggml_cuda_fattn_is_turbo_kv_type(K->type) &&") != std::string::npos &&
92+
prefill_policy.find("!ggml_cuda_fattn_is_turbo_kv_type(V->type)") != std::string::npos &&
93+
prefill_policy.find("ggml_cuda_fattn_is_classic_non_q8_type(V->type)") != std::string::npos,
94+
"Turbo K + classic V prefill eligibility must not broaden classic-K/Turbo-V routing");
95+
96+
return ok ? 0 : 1;
97+
}

tests/test-dflash-plumbing.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ int main(int argc, char ** argv) {
410410
"CUDA FlashAttention all-quant dispatch must include D=512 TCQ mixed q8/turbo3 pairs");
411411
ok &= expect(cuda_fattn.find("hip_native_tcq_decode") != std::string::npos &&
412412
cuda_fattn.find("#if defined(GGML_USE_HIP)") != std::string::npos &&
413-
contains_ws(cuda_fattn, "!hip_native_tcq_decode && !turbo_decode_native && turbo_kv"),
413+
contains_ws(cuda_fattn, "!hip_native_tcq_decode && !turbo_decode_native && !prefer_native_vec && turbo_kv"),
414414
"HIP TCQ decode must stay on the native VEC path instead of dequantizing into generic tile/MMA FlashAttention");
415415
ok &= expect(cuda_fattn.find("turbo_mma_fused && turbo_mma_supported && Q->ne[1] <= 4") != std::string::npos &&
416416
cuda_fattn.find("K->type == GGML_TYPE_TURBO4_0 ||") != std::string::npos &&

0 commit comments

Comments
 (0)