Skip to content

Commit c472f92

Browse files
authored
ggml-cuda: enable cuda-graphs for n-cpu-moe (ggml-org#18934)
* ggml-cuda: add split-wise cuda graph * add n-cpu-moe compare_llama_bench.py * fix hip/musa builds
1 parent 020ff03 commit c472f92

4 files changed

Lines changed: 105 additions & 51 deletions

File tree

ggml/src/ggml-cuda/common.cuh

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,10 +1327,44 @@ struct ggml_backend_cuda_context {
13271327
cudaStream_t streams[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS] = { { nullptr } };
13281328
cublasHandle_t cublas_handles[GGML_CUDA_MAX_DEVICES] = {nullptr};
13291329

1330-
std::unique_ptr<ggml_cuda_graph> cuda_graph;
1331-
13321330
int curr_stream_no = 0;
13331331

1332+
#ifdef USE_CUDA_GRAPH
1333+
// Map from first_node_ptr to cuda_graph - allows multiple graphs per context
1334+
// when the computation is split across CPU/GPU (e.g., with --n-cpu-moe)
1335+
std::unordered_map<const void *, std::unique_ptr<ggml_cuda_graph>> cuda_graphs;
1336+
1337+
ggml_cuda_graph * cuda_graph(const void * first_node_ptr) {
1338+
auto it = cuda_graphs.find(first_node_ptr);
1339+
if (it == cuda_graphs.end()) {
1340+
cuda_graphs[first_node_ptr] = std::make_unique<ggml_cuda_graph>();
1341+
return cuda_graphs[first_node_ptr].get();
1342+
}
1343+
return it->second.get();
1344+
}
1345+
1346+
// Check if any CUDA graph is enabled for this context (used by kernels that need to know
1347+
// if graphs are in use without having access to the specific graph key)
1348+
bool any_cuda_graph_enabled() const {
1349+
for (const auto & [key, graph] : cuda_graphs) {
1350+
if (graph && graph->is_enabled()) {
1351+
return true;
1352+
}
1353+
}
1354+
return false;
1355+
}
1356+
1357+
// Check if any CUDA graph has an instance for this context
1358+
bool any_cuda_graph_has_instance() const {
1359+
for (const auto & [key, graph] : cuda_graphs) {
1360+
if (graph && graph->instance != nullptr) {
1361+
return true;
1362+
}
1363+
}
1364+
return false;
1365+
}
1366+
#endif // USE_CUDA_GRAPH
1367+
13341368
explicit ggml_backend_cuda_context(int device) :
13351369
device(device),
13361370
name(GGML_CUDA_NAME + std::to_string(device)) {

ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2969,56 +2969,64 @@ static bool ggml_cuda_graph_node_properties_match(ggml_tensor * node, ggml_cuda_
29692969
return true;
29702970
}
29712971

2972+
static const void * ggml_cuda_graph_get_key(ggml_cgraph * cgraph) {
2973+
return cgraph->nodes[0];
2974+
}
2975+
29722976
static bool ggml_cuda_graph_update_required(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph * cgraph) {
29732977

29742978
bool res = false;
29752979

2976-
if (cuda_ctx->cuda_graph->instance == nullptr) {
2980+
const void * graph_key = ggml_cuda_graph_get_key(cgraph);
2981+
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
2982+
2983+
if (graph->instance == nullptr) {
29772984
res = true;
29782985
}
29792986

29802987
// Check if the graph size has changed
2981-
if (cuda_ctx->cuda_graph->props.size() != (size_t)cgraph->n_nodes + cgraph->n_leafs) {
2988+
if (graph->props.size() != (size_t)cgraph->n_nodes + cgraph->n_leafs) {
29822989
res = true;
2983-
cuda_ctx->cuda_graph->props.resize(cgraph->n_nodes + cgraph->n_leafs);
2990+
graph->props.resize(cgraph->n_nodes + cgraph->n_leafs);
29842991
}
29852992

29862993
// Loop over nodes in GGML graph to determine if CUDA graph update is required
29872994
// and store properties to allow this comparison for the next token
29882995
for (int i = 0; i < cgraph->n_nodes; i++) {
29892996
bool props_match = true;
29902997
if (!res) {
2991-
props_match = ggml_cuda_graph_node_properties_match(cgraph->nodes[i], &cuda_ctx->cuda_graph->props[i]);
2998+
props_match = ggml_cuda_graph_node_properties_match(cgraph->nodes[i], &graph->props[i]);
29922999
}
29933000
if (!props_match) {
29943001
res = true;
29953002
}
2996-
ggml_cuda_graph_node_set_properties(&cuda_ctx->cuda_graph->props[i], cgraph->nodes[i]);
3003+
ggml_cuda_graph_node_set_properties(&graph->props[i], cgraph->nodes[i]);
29973004
}
29983005

29993006
for (int i = 0; i < cgraph->n_leafs; i++) {
3000-
bool props_match= true;
3007+
bool props_match = true;
30013008
if (!res) {
3002-
props_match = ggml_cuda_graph_node_properties_match(cgraph->leafs[i], &cuda_ctx->cuda_graph->props[cgraph->n_nodes + i]);
3009+
props_match = ggml_cuda_graph_node_properties_match(cgraph->leafs[i], &graph->props[cgraph->n_nodes + i]);
30033010
}
30043011
if (!props_match) {
30053012
res = true;
30063013
}
3007-
ggml_cuda_graph_node_set_properties(&cuda_ctx->cuda_graph->props[cgraph->n_nodes + i], cgraph->leafs[i]);
3014+
ggml_cuda_graph_node_set_properties(&graph->props[cgraph->n_nodes + i], cgraph->leafs[i]);
30083015
}
30093016

30103017
return res;
30113018
}
30123019

3013-
static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_ctx) {
3020+
static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_ctx, const void * graph_key) {
3021+
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
30143022

30153023
#if CUDART_VERSION >= 12000
30163024
cudaGraphExecUpdateResultInfo result_info;
3017-
cudaError_t stat = cudaGraphExecUpdate(cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, &result_info);
3025+
cudaError_t stat = cudaGraphExecUpdate(graph->instance, graph->graph, &result_info);
30183026
#else
30193027
cudaGraphNode_t errorNode;
30203028
cudaGraphExecUpdateResult result_info;
3021-
cudaError_t stat = cudaGraphExecUpdate(cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, &errorNode, &result_info);
3029+
cudaError_t stat = cudaGraphExecUpdate(graph->instance, graph->graph, &errorNode, &result_info);
30223030
#endif // CUDART_VERSION >= 12000
30233031

30243032
if (stat == cudaErrorGraphExecUpdateFailure) {
@@ -3029,14 +3037,14 @@ static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_c
30293037
// The pre-existing graph exec cannot be updated due to violated constraints
30303038
// so instead clear error and re-instantiate
30313039
(void)cudaGetLastError();
3032-
CUDA_CHECK(cudaGraphExecDestroy(cuda_ctx->cuda_graph->instance));
3033-
cuda_ctx->cuda_graph->instance = nullptr;
3034-
CUDA_CHECK(cudaGraphInstantiate(&cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, NULL, NULL, 0));
3040+
CUDA_CHECK(cudaGraphExecDestroy(graph->instance));
3041+
graph->instance = nullptr;
3042+
CUDA_CHECK(cudaGraphInstantiate(&graph->instance, graph->graph, NULL, NULL, 0));
30353043
} else {
30363044
GGML_ASSERT(stat == cudaSuccess);
30373045
}
30383046
}
3039-
#endif
3047+
#endif // USE_CUDA_GRAPH
30403048

30413049
static bool ggml_cuda_should_fuse_rope_set_rows(const ggml_tensor * rope,
30423050
const ggml_tensor * view,
@@ -3241,7 +3249,7 @@ static bool ggml_cuda_can_fuse(const struct ggml_cgraph * cgraph, int node_idx,
32413249
return false;
32423250
}
32433251

3244-
static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph * cgraph, const bool use_cuda_graph, const bool cuda_graph_update_required) {
3252+
static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph * cgraph, const bool use_cuda_graph, const bool cuda_graph_update_required, const void * graph_key) {
32453253
bool graph_evaluated_or_captured = false;
32463254

32473255
// flag used to determine whether it is an integrated_gpu
@@ -3695,13 +3703,14 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
36953703
}
36963704

36973705
#ifdef USE_CUDA_GRAPH
3706+
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
36983707
if (use_cuda_graph && cuda_graph_update_required) { // End CUDA graph capture
3699-
if (cuda_ctx->cuda_graph->graph != nullptr) {
3700-
CUDA_CHECK(cudaGraphDestroy(cuda_ctx->cuda_graph->graph));
3701-
cuda_ctx->cuda_graph->graph = nullptr;
3708+
if (graph->graph != nullptr) {
3709+
CUDA_CHECK(cudaGraphDestroy(graph->graph));
3710+
graph->graph = nullptr;
37023711
}
37033712

3704-
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &cuda_ctx->cuda_graph->graph));
3713+
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &graph->graph));
37053714
graph_evaluated_or_captured = true; // CUDA graph has been captured
37063715

37073716
std::lock_guard<std::mutex> lock(ggml_cuda_lock);
@@ -3714,40 +3723,39 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
37143723
}
37153724

37163725
if (use_cuda_graph) {
3717-
if (cuda_ctx->cuda_graph->instance == nullptr) { // Create executable graph from captured graph.
3718-
CUDA_CHECK(cudaGraphInstantiate(&cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, NULL, NULL, 0));
3726+
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
3727+
if (graph->instance == nullptr) { // Create executable graph from captured graph.
3728+
CUDA_CHECK(cudaGraphInstantiate(&graph->instance, graph->graph, NULL, NULL, 0));
37193729
}
37203730
if (cuda_graph_update_required) { // Update graph executable
3721-
ggml_cuda_graph_update_executable(cuda_ctx);
3731+
ggml_cuda_graph_update_executable(cuda_ctx, graph_key);
37223732
}
37233733
// Launch graph
3724-
CUDA_CHECK(cudaGraphLaunch(cuda_ctx->cuda_graph->instance, cuda_ctx->stream()));
3734+
CUDA_CHECK(cudaGraphLaunch(graph->instance, cuda_ctx->stream()));
37253735
#else
37263736
graph_evaluated_or_captured = true;
37273737
#endif // USE_CUDA_GRAPH
37283738
}
37293739
}
37303740

3731-
static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx) {
3741+
static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx, const void * graph_key) {
37323742

37333743
#ifdef USE_CUDA_GRAPH
3744+
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
37343745

3735-
if (cuda_ctx->cuda_graph == nullptr) {
3736-
cuda_ctx->cuda_graph.reset(new ggml_cuda_graph());
3737-
}
3738-
3739-
if (cuda_ctx->cuda_graph->graph == nullptr) {
3746+
if (graph->graph == nullptr) {
37403747
if (ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_AMPERE) {
3741-
if (!cuda_ctx->cuda_graph->disable_due_to_gpu_arch) {
3748+
if (!graph->disable_due_to_gpu_arch) {
37423749
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to GPU architecture\n", __func__);
37433750
}
3744-
cuda_ctx->cuda_graph->disable_due_to_gpu_arch = true;
3751+
graph->disable_due_to_gpu_arch = true;
37453752
}
37463753
}
37473754

3748-
return cuda_ctx->cuda_graph->is_enabled();
3755+
return graph->is_enabled();
37493756
#else
37503757
GGML_UNUSED(cuda_ctx);
3758+
GGML_UNUSED(graph_key);
37513759
return false;
37523760
#endif // USE_CUDA_GRAPH
37533761
}
@@ -3759,15 +3767,19 @@ static enum ggml_status ggml_backend_cuda_graph_compute(ggml_backend_t backend,
37593767

37603768
bool use_cuda_graph = false;
37613769
bool cuda_graph_update_required = false;
3770+
const void * graph_key = nullptr;
37623771

37633772
#ifdef USE_CUDA_GRAPH
3764-
use_cuda_graph = ggml_cuda_graph_set_enabled(cuda_ctx);
3773+
graph_key = ggml_cuda_graph_get_key(cgraph);
3774+
3775+
use_cuda_graph = ggml_cuda_graph_set_enabled(cuda_ctx, graph_key);
37653776

3766-
if (cuda_ctx->cuda_graph->is_enabled()) {
3777+
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
3778+
if (graph->is_enabled()) {
37673779
cuda_graph_update_required = ggml_cuda_graph_update_required(cuda_ctx, cgraph);
37683780
use_cuda_graph = ggml_cuda_graph_check_compability(cgraph);
37693781

3770-
cuda_ctx->cuda_graph->record_update(use_cuda_graph, cuda_graph_update_required);
3782+
graph->record_update(use_cuda_graph, cuda_graph_update_required);
37713783
}
37723784
#endif // USE_CUDA_GRAPH
37733785

@@ -3781,7 +3793,7 @@ static enum ggml_status ggml_backend_cuda_graph_compute(ggml_backend_t backend,
37813793
CUDA_CHECK(cudaStreamBeginCapture(cuda_ctx->stream(), cudaStreamCaptureModeRelaxed));
37823794
}
37833795

3784-
ggml_cuda_graph_evaluate_and_capture(cuda_ctx, cgraph, use_cuda_graph, cuda_graph_update_required);
3796+
ggml_cuda_graph_evaluate_and_capture(cuda_ctx, cgraph, use_cuda_graph, cuda_graph_update_required, graph_key);
37853797

37863798
return GGML_STATUS_SUCCESS;
37873799
}
@@ -3814,7 +3826,14 @@ static void ggml_backend_cuda_event_wait(ggml_backend_t backend, ggml_backend_ev
38143826
static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph) {
38153827
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *) backend->context;
38163828

3817-
const bool use_cuda_graph = ggml_cuda_graph_set_enabled(cuda_ctx);
3829+
#ifdef USE_CUDA_GRAPH
3830+
const void * graph_key = ggml_cuda_graph_get_key(cgraph);
3831+
const bool use_cuda_graph = ggml_cuda_graph_set_enabled(cuda_ctx, graph_key);
3832+
#else
3833+
const bool use_cuda_graph = false;
3834+
GGML_UNUSED(cuda_ctx);
3835+
GGML_UNUSED(cgraph);
3836+
#endif
38183837

38193838
static bool enable_graph_optimization = [] {
38203839
const char * env = getenv("GGML_CUDA_GRAPH_OPT");

ggml/src/ggml-cuda/mean.cu

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ void ggml_cuda_op_mean(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
3131
#endif // USE_CUDA_GRAPH
3232
if ((nrows == 1) &&
3333
#ifdef USE_CUDA_GRAPH
34-
// CUDA_GRAPHS_DISABLED
35-
((ncols > 65536) &&
36-
((ctx.cuda_graph->instance == nullptr) && (iscapturing == cudaStreamCaptureStatusNone) ||
37-
ctx.cuda_graph->is_enabled())) ||
38-
// CUDA_GRAPHS ENABLED
39-
((ncols > 32768) &&
40-
!((ctx.cuda_graph->instance == nullptr) && (iscapturing == cudaStreamCaptureStatusNone) ||
41-
ctx.cuda_graph->is_enabled()))) {
34+
// Determine if CUDA graphs are effectively disabled for this context
35+
// (no graph instance exists and we're not capturing, OR graphs are explicitly enabled)
36+
(((ncols > 65536) &&
37+
(((!ctx.any_cuda_graph_has_instance()) && (iscapturing == cudaStreamCaptureStatusNone)) ||
38+
ctx.any_cuda_graph_enabled())) ||
39+
// CUDA graphs are enabled - use lower threshold
40+
((ncols > 32768) &&
41+
!(((!ctx.any_cuda_graph_has_instance()) && (iscapturing == cudaStreamCaptureStatusNone)) ||
42+
ctx.any_cuda_graph_enabled())))) {
4243
#else
4344
(ncols > 65536)) {
4445
#endif // USE_CUDA_GRAPH

scripts/compare-llama-bench.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"cpu_mask", "cpu_strict", "poll", "type_k", "type_v", "n_gpu_layers",
3030
"split_mode", "main_gpu", "no_kv_offload", "flash_attn", "tensor_split", "tensor_buft_overrides",
3131
"use_mmap", "embeddings", "no_op_offload", "n_prompt", "n_gen", "n_depth",
32-
"test_time", "avg_ns", "stddev_ns", "avg_ts", "stddev_ts",
32+
"test_time", "avg_ns", "stddev_ns", "avg_ts", "stddev_ts", "n_cpu_moe"
3333
]
3434

3535
LLAMA_BENCH_DB_TYPES = [
@@ -38,7 +38,7 @@
3838
"TEXT", "INTEGER", "INTEGER", "TEXT", "TEXT", "INTEGER",
3939
"TEXT", "INTEGER", "INTEGER", "INTEGER", "TEXT", "TEXT",
4040
"INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER",
41-
"TEXT", "INTEGER", "INTEGER", "REAL", "REAL",
41+
"TEXT", "INTEGER", "INTEGER", "REAL", "REAL", "INTEGER",
4242
]
4343

4444
# All test-backend-ops SQL fields
@@ -59,7 +59,7 @@
5959

6060
# Properties by which to differentiate results per commit for llama-bench:
6161
LLAMA_BENCH_KEY_PROPERTIES = [
62-
"cpu_info", "gpu_info", "backends", "n_gpu_layers", "tensor_buft_overrides", "model_filename", "model_type",
62+
"cpu_info", "gpu_info", "backends", "n_gpu_layers", "n_cpu_moe", "tensor_buft_overrides", "model_filename", "model_type",
6363
"n_batch", "n_ubatch", "embeddings", "cpu_mask", "cpu_strict", "poll", "n_threads", "type_k", "type_v",
6464
"use_mmap", "no_kv_offload", "split_mode", "main_gpu", "tensor_split", "flash_attn", "n_prompt", "n_gen", "n_depth"
6565
]

0 commit comments

Comments
 (0)