Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6261385
CUDA graph implemented
Nafees01 Jul 25, 2025
b156a00
Implement CUDA graphs with fallback system in libCEED
Nafees01 Aug 6, 2025
83c1d9f
Cleaner and Simplified Implementation of CUDA graph
Nafees01 Aug 6, 2025
bbad4e2
add vector pointer tracking to detect memory changes in Graph
Nafees01 Aug 7, 2025
285b661
Add PETSc vector setup for CUDA Graph compatibility in CUDA-gen backend
Nafees01 Aug 19, 2025
10d6dd2
Clean CUDA-graph implementation
Nafees01 Aug 21, 2025
4abc7ad
Simple and clean implementation of CUDA graph but numerically incorrect
Nafees01 Aug 26, 2025
1fa80ed
per-operator CUDA Graph implementation
Nafees01 Sep 11, 2025
5b8e14f
replace cudaMemset with cudaMemsetAsync
Nafees01 Sep 25, 2025
94bdeca
Auto-detect graph capture and use async memset with cudaStreamPerThread
Nafees01 Oct 21, 2025
a00af53
CUDA Graph support for composite operators in cuda-gen backend
Nafees01 Oct 28, 2025
1ca2b5f
CUDA Graph is working fine for composite operators
Nafees01 Oct 28, 2025
719027b
cuda-gen: CUDA Graph capture and replay working for composite operators
Nafees01 Jul 7, 2026
40f8c3e
Merge remote-tracking branch 'upstream/main' into cuda-graph-dev
Nafees01 Jul 8, 2026
91c8d6d
style: apply clang-format-22
Nafees01 Jul 8, 2026
1249673
cuda-gen: address review feedback for composite CUDA graphs
Nafees01 Jul 22, 2026
c7c5b1a
cuda: add CeedOperatorSetEnableCudaGraph and address review feedback
Nafees01 Jul 24, 2026
8f160e9
cuda: add docs for when graph/CUfunction setters aren't supported
Nafees01 Jul 27, 2026
8f717af
cuda-gen: add output pointer check and address review fixes
Nafees01 Jul 28, 2026
8ef2be7
cuda-gen: address review feedback for ceed handling and async memset
Nafees01 Aug 12, 2026
b170ec4
address review feedback
Nafees01 Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 231 additions & 35 deletions backends/cuda-gen/ceed-cuda-gen-operator.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
#include <cuda.h>
#include <cuda_runtime.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "../cuda/ceed-cuda-common.h"
#include "../cuda/ceed-cuda-compile.h"
Expand All @@ -31,7 +33,19 @@ static int CeedOperatorDestroy_Cuda_gen(CeedOperator op) {
if (impl->module_assemble_full) CeedCallCuda(ceed, cuModuleUnload(impl->module_assemble_full));
if (impl->module_assemble_diagonal) CeedCallCuda(ceed, cuModuleUnload(impl->module_assemble_diagonal));
if (impl->module_assemble_qfunction) CeedCallCuda(ceed, cuModuleUnload(impl->module_assemble_qfunction));
if (impl->points.num_per_elem) CeedCallCuda(ceed, cudaFree((void **)impl->points.num_per_elem));
if (impl->points.num_per_elem) CeedCallCuda(ceed, cudaFree((void *)impl->points.num_per_elem));

if (impl->graph_created && impl->graph_launches > 0) {
char *op_name = NULL;
CeedOperatorGetName(op, (const char**)&op_name);
printf("[CUDA Graph] Summary for operator '%s': %d graph launches, %d fallbacks\n",
op_name ? op_name : "unnamed", impl->graph_launches, impl->fallbacks);
}
if (impl->graph_instance) { cudaGraphExecDestroy(impl->graph_instance); impl->graph_instance = NULL; }
if (impl->graph) { cudaGraphDestroy(impl->graph); impl->graph = NULL; }
impl->graph_created = false;
impl->captured_input_ptr = NULL;

CeedCallBackend(CeedFree(&impl));
CeedCallBackend(CeedDestroy(&ceed));
return CEED_ERROR_SUCCESS;
Expand Down Expand Up @@ -224,8 +238,9 @@ static int CeedOperatorApplyAddCore_Cuda_gen(CeedOperator op, CUstream stream, c
block[2] = elems_per_block;
}
CeedInt shared_mem = block[0] * block[1] * block[2] * sizeof(CeedScalar);

CeedCallBackend(CeedTryRunKernelDimShared_Cuda(ceed, data->op, stream, grid, block[0], block[1], block[2], shared_mem, is_run_good, opargs));

CeedCallBackend(
CeedTryRunKernelDimShared_Cuda(ceed, data->op, stream, grid, block[0], block[1], block[2], shared_mem, is_run_good, opargs));

// Restore input arrays
for (CeedInt i = 0; i < num_input_fields; i++) {
Expand Down Expand Up @@ -284,7 +299,11 @@ static int CeedOperatorApplyAdd_Cuda_gen(CeedOperator op, CeedVector input_vec,
// Try to run kernel
if (input_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorGetArrayRead(input_vec, CEED_MEM_DEVICE, &input_arr));
if (output_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorGetArray(output_vec, CEED_MEM_DEVICE, &output_arr));
CeedCallBackend(CeedOperatorApplyAddCore_Cuda_gen(op, NULL, input_arr, output_arr, &is_run_good, request));
// During graph capture use the capturing stream, otherwise the default stream.
enum cudaStreamCaptureStatus capture_status;
cudaStreamIsCapturing(cudaStreamPerThread, &capture_status);
Comment thread
jeremylt marked this conversation as resolved.
Outdated
CUstream stream_to_use = (capture_status != cudaStreamCaptureStatusNone) ? cudaStreamPerThread : NULL;
Comment thread
jeremylt marked this conversation as resolved.
Outdated
CeedCallBackend(CeedOperatorApplyAddCore_Cuda_gen(op, stream_to_use, input_arr, output_arr, &is_run_good, request));
if (input_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorRestoreArrayRead(input_vec, &input_arr));
if (output_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorRestoreArray(output_vec, &output_arr));

Expand All @@ -299,51 +318,216 @@ static int CeedOperatorApplyAdd_Cuda_gen(CeedOperator op, CeedVector input_vec,
return CEED_ERROR_SUCCESS;
}

// Push each suboperator's QFunction context to device. Replay skips the normal
// apply path, so we do this by hand to keep time/load parameters current.
static int CeedCompositeRefreshContexts_Cuda_gen(CeedOperator *sub_operators, CeedInt num_suboperators) {
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedQFunction qf = NULL;
void *d_c = NULL;

CeedCallBackend(CeedOperatorGetQFunction(sub_operators[i], &qf));
CeedCallBackend(CeedQFunctionGetInnerContextData(qf, CEED_MEM_DEVICE, &d_c));
CeedCallBackend(CeedQFunctionRestoreInnerContextData(qf, &d_c));
CeedCallBackend(CeedQFunctionDestroy(&qf));
}
return CEED_ERROR_SUCCESS;
}
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated

static int CeedOperatorApplyAddComposite_Cuda_gen(CeedOperator op, CeedVector input_vec, CeedVector output_vec, CeedRequest *request) {
bool is_run_good[CEED_COMPOSITE_MAX] = {false}, is_sequential;
CeedInt num_suboperators;
const CeedScalar *input_arr = NULL;
CeedScalar *output_arr = NULL;
Ceed ceed;
CeedOperator *sub_operators;
cudaStream_t stream = NULL;
Ceed ceed;
CeedOperator_Cuda_gen *impl;
CeedOperator *sub_operators;
CeedInt num_suboperators;
char *op_name = NULL;

CeedCallBackend(CeedOperatorGetCeed(op, &ceed));
ceed = CeedOperatorReturnCeed(op);
Comment thread
jeremylt marked this conversation as resolved.
Outdated
CeedCall(CeedOperatorCompositeGetNumSub(op, &num_suboperators));
CeedCall(CeedOperatorCompositeGetSubList(op, &sub_operators));
CeedCall(CeedOperatorCompositeIsSequential(op, &is_sequential));
if (input_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorGetArrayRead(input_vec, CEED_MEM_DEVICE, &input_arr));
if (output_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorGetArray(output_vec, CEED_MEM_DEVICE, &output_arr));
if (is_sequential) CeedCallCuda(ceed, cudaStreamCreate(&stream));
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedInt num_elem = 0;
CeedCallBackend(CeedOperatorGetData(op, &impl));
CeedCallBackend(CeedOperatorGetName(op, (const char **)&op_name));

// CEED_FORCE_BASELINE=1: skip graphs, run via /gpu/cuda/ref
static bool force_baseline = false;
static bool force_baseline_checked = false;
if (!force_baseline_checked) {
char *env_val = getenv("CEED_FORCE_BASELINE");
force_baseline = (env_val != NULL && strcmp(env_val, "1") == 0);
force_baseline_checked = true;
}
if (force_baseline) {
CeedOperator op_fallback;
CeedCallBackend(CeedOperatorGetFallback(op, &op_fallback));
CeedCallBackend(CeedOperatorApplyAdd(op_fallback, input_vec, output_vec, request));
return CEED_ERROR_SUCCESS;
}
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated

CeedCall(CeedOperatorGetNumElements(sub_operators[i], &num_elem));
if (num_elem > 0) {
if (!is_sequential) CeedCallCuda(ceed, cudaStreamCreate(&stream));
CeedCallBackend(CeedOperatorApplyAddCore_Cuda_gen(sub_operators[i], stream, input_arr, output_arr, &is_run_good[i], request));
if (!is_sequential) CeedCallCuda(ceed, cudaStreamDestroy(stream));
// CEED_DISABLE_GRAPH=1: run suboperators directly, no capture/replay (for benchmarking).
static bool disable_graph = false;
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated
static bool disable_graph_checked = false;
if (!disable_graph_checked) {
char *env_val = getenv("CEED_DISABLE_GRAPH");
disable_graph = (env_val != NULL && strcmp(env_val, "1") == 0);
disable_graph_checked = true;
}
if (disable_graph) {
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request));
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated
}
return CEED_ERROR_SUCCESS;
}
if (is_sequential) CeedCallCuda(ceed, cudaStreamDestroy(stream));
if (input_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorRestoreArrayRead(input_vec, &input_arr));
Comment thread
zatkins-dev marked this conversation as resolved.
if (output_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorRestoreArray(output_vec, &output_arr));
CeedCallCuda(ceed, cudaDeviceSynchronize());

// Fallback on unsuccessful run
for (CeedInt i = 0; i < num_suboperators; i++) {
if (!is_run_good[i]) {
CeedOperator op_fallback;
// No real I/O buffers to capture; just run directly.
if (input_vec == CEED_VECTOR_NONE || output_vec == CEED_VECTOR_NONE) {
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request));
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated
}
return CEED_ERROR_SUCCESS;
}


// Phase 1: first call runs directly so lazy allocations happen before capture.
if (!impl->warmup_done) {
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use CeedOperatorApplyAddCore_Cuda_gen directly, we intentionally don't go through the interface here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Warmup now goes through CeedOperatorApplyAddComposite_NoGraph_Cuda_gen, which calls CeedOperatorApplyAddCore_Cuda_gen directly with is_run_good tracking the same as the original composite apply path. The capture loop also uses Core directly rather than the public interface.

}
CeedCallCuda(ceed, cudaDeviceSynchronize());
impl->warmup_done = true;
return CEED_ERROR_SUCCESS;
}
Comment thread
zatkins-dev marked this conversation as resolved.

// Phase 2: capture.
if (!impl->graph_created) {
{
const CeedScalar *in_ptr;
CeedCallBackend(CeedVectorGetArrayRead(input_vec, CEED_MEM_DEVICE, &in_ptr));
impl->captured_input_ptr = in_ptr;
CeedCallBackend(CeedVectorRestoreArrayRead(input_vec, &in_ptr));
}

printf("[CUDA Graph] Phase 2: Recording graph for operator '%s'...\n", op_name ? op_name : "unnamed");
cudaStream_t capture_stream = cudaStreamPerThread;
bool capture_ok = true;

CeedDebug(ceed, "\nFalling back to /gpu/cuda/ref CeedOperator for ApplyAdd\n");
CeedCallBackend(CeedOperatorGetFallback(sub_operators[i], &op_fallback));
cudaError_t err = cudaStreamBeginCapture(capture_stream, cudaStreamCaptureModeThreadLocal);
if (err != cudaSuccess) {
capture_ok = false;
}

if (capture_ok) {
// Check errors by hand (not CeedCallBackend) so we always reach the
// cudaStreamEndCapture below. Some sub-operators (e.g. contact, which calls
// cudaMalloc) invalidate the capture, and we still need to close it out.
for (CeedInt i = 0; i < num_suboperators; i++) {
if (CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, CEED_REQUEST_IMMEDIATE)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CeedOperatorApplyAddCore_Cuda_gen has a bool *is_run_good output parameter for exactly this purpose.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. The capture loop now calls CeedOperatorApplyAddCore_Cuda_gen directly with an is_run_good flag instead of going through the public ApplyAdd interface.

capture_ok = false;
break;
}
}
}

// Always end capture so the stream is usable again, even if it failed.
{
cudaGraph_t partial = NULL;
err = cudaStreamEndCapture(capture_stream, &partial);
if (capture_ok && (err != cudaSuccess || !partial)) capture_ok = false;
if (capture_ok) impl->graph = partial;
else if (partial) cudaGraphDestroy(partial);
}

if (capture_ok) {
err = cudaGraphInstantiate(&impl->graph_instance, impl->graph, 0);
if (err != cudaSuccess) {
cudaGraphDestroy(impl->graph);
impl->graph = NULL;
capture_ok = false;
}
}

// Clear the leftover error from a failed capture so later CUDA calls don't inherit it.
if (!capture_ok) {
cudaGetLastError();
cudaDeviceSynchronize();
cudaGetLastError();
}

impl->graph_created = true;
impl->graph_launches = 0;

if (capture_ok) printf("[CUDA Graph] Graph created for operator '%s'\n", op_name ? op_name : "unnamed");
else printf("[CUDA Graph] Capture disabled for operator '%s'; falling back to direct apply\n", op_name ? op_name : "unnamed");

// Capture doesn't run the kernels, so apply directly to get this call's output.
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request));
}
return CEED_ERROR_SUCCESS;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Capture doesn't run the kernels, so apply directly to get this call's output.
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request));
}
return CEED_ERROR_SUCCESS;

Can we just play out the graph (i.e. don't return, exit if statement, continue) instead of applying the kernels for a second time?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. After a successful capture, we no longer re-apply the suboperators; we set graph_created and fall through to cudaGraphLaunch, so the first post-capture call is served by replaying the graph rather than running the kernels a second time.

}

// Phase 3: replay.
{
// graph_instance == NULL means capture failed; direct-apply every call.
if (!impl->graph_instance) {
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated
for (CeedInt i = 0; i < num_suboperators; i++) {
CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request));
}
return CEED_ERROR_SUCCESS;
}

cudaStream_t stream = NULL;

// Sync contexts so kernels see updated time/load parameters.
CeedCallBackend(CeedCompositeRefreshContexts_Cuda_gen(sub_operators, num_suboperators));

// If the input buffer moved since capture, recapture.
{
const CeedScalar *in_ptr;
bool ptr_ok;

CeedCallBackend(CeedVectorGetArrayRead(input_vec, CEED_MEM_DEVICE, &in_ptr));
ptr_ok = (in_ptr == impl->captured_input_ptr);
CeedCallBackend(CeedVectorRestoreArrayRead(input_vec, &in_ptr));
if (!ptr_ok) goto use_fallback;
}
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated

cudaError_t err = cudaGraphLaunch(impl->graph_instance, stream);
if (err != cudaSuccess) {
printf("CUDA Graph launch failed: %s - using fallback\n", cudaGetErrorString(err));
CeedOperator op_fallback;
CeedCallBackend(CeedOperatorGetFallback(op, &op_fallback));
CeedCallBackend(CeedOperatorApplyAdd(op_fallback, input_vec, output_vec, request));
return CEED_ERROR_SUCCESS;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't fall back here -- we should call the helper I mentioned above and mark the graph as bad. The fallback should be a last case scenario.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed whole-operator ref fallback there too; uses the NoGraph helper.


if (impl->graph_launches == 0) {
printf("[CUDA Graph] ✓ Replaying graph for operator '%s'\n", op_name ? op_name : "unnamed");
}
impl->graph_launches++;

return CEED_ERROR_SUCCESS;
}
CeedCallBackend(CeedDestroy(&ceed));

// Drop the graph and run via /gpu/cuda/ref; next call will recapture.
use_fallback:
if (impl->graph_instance) {
cudaGraphExecDestroy(impl->graph_instance);
impl->graph_instance = NULL;
}
if (impl->graph) {
cudaGraphDestroy(impl->graph);
impl->graph = NULL;
}
impl->graph_created = false;
impl->captured_input_ptr = NULL;
impl->fallbacks++;

CeedOperator op_fallback;
CeedCallBackend(CeedOperatorGetFallback(op, &op_fallback));
CeedCallBackend(CeedOperatorApplyAdd(op_fallback, input_vec, output_vec, request));
Comment thread
zatkins-dev marked this conversation as resolved.
Outdated

return CEED_ERROR_SUCCESS;
Comment thread
jeremylt marked this conversation as resolved.
}


//------------------------------------------------------------------------------
// QFunction assembly
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -465,7 +649,7 @@ static int CeedOperatorLinearAssembleQFunctionCore_Cuda_gen(CeedOperator op, boo

// Assemble QFunction
void *opargs[] = {(void *)&num_elem, &qf_data->d_c, &data->indices, &data->fields, &data->B, &data->G, &data->W, &data->points, &assembled_array};
bool is_tensor = false;
bool is_tensor;
int max_threads_per_block, min_grid_size, grid;

CeedCallBackend(CeedOperatorHasTensorBases(op, &is_tensor));
Expand Down Expand Up @@ -874,6 +1058,7 @@ static int CeedOperatorAssembleSingleAtPoints_Cuda_gen(CeedOperator op, CeedInt
return CEED_ERROR_SUCCESS;
}


//------------------------------------------------------------------------------
// Create operator
//------------------------------------------------------------------------------
Expand All @@ -885,6 +1070,16 @@ int CeedOperatorCreate_Cuda_gen(CeedOperator op) {
CeedCallBackend(CeedOperatorGetCeed(op, &ceed));
CeedCallBackend(CeedCalloc(1, &impl));
CeedCallBackend(CeedOperatorSetData(op, impl));

impl->graph_created = false;
impl->warmup_done = false;
impl->graph = NULL;
impl->graph_instance = NULL;
impl->graph_launches = 0;
impl->fallbacks = 0;
impl->captured_input_ptr = NULL;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl->graph_created = false;
impl->warmup_done = false;
impl->graph = NULL;
impl->graph_instance = NULL;
impl->graph_launches = 0;
impl->fallbacks = 0;
impl->captured_input_ptr = NULL;

All of these assignments are implicitly done via CeedCalloc(1, &impl);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the redundant zero-inits. CeedCalloc already zeroes the struct, so only use_graph is set explicitly (from CEED_DISABLE_GRAPH). Also dropped the graph_launches and fallbacks counters from the struct.


CeedCall(CeedOperatorIsComposite(op, &is_composite));
if (is_composite) {
CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "ApplyAddComposite", CeedOperatorApplyAddComposite_Cuda_gen));
Expand All @@ -897,6 +1092,7 @@ int CeedOperatorCreate_Cuda_gen(CeedOperator op) {
CeedOperatorLinearAssembleAddDiagonalAtPoints_Cuda_gen));
CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "LinearAssembleSingle", CeedOperatorAssembleSingleAtPoints_Cuda_gen));
}

Comment thread
jeremylt marked this conversation as resolved.
Outdated
if (!is_at_points) {
CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "LinearAssembleQFunction", CeedOperatorLinearAssembleQFunction_Cuda_gen));
CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "LinearAssembleQFunctionUpdate",
Expand Down
11 changes: 11 additions & 0 deletions backends/cuda-gen/ceed-cuda-gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <ceed/backend.h>
#include <ceed/jit-source/cuda/cuda-types.h>
#include <cuda.h>
#include <cuda_runtime.h>


typedef struct {
bool use_fallback, use_assembly_fallback;
Expand All @@ -25,6 +27,15 @@ typedef struct {
Fields_Cuda G;
CeedScalar *W;
Points_Cuda points;

// CUDA graph state
bool graph_created;
bool warmup_done;
cudaGraph_t graph;
cudaGraphExec_t graph_instance;
int graph_launches;
int fallbacks;
const CeedScalar *captured_input_ptr; // device address at capture; checked before each replay
} CeedOperator_Cuda_gen;

typedef struct {
Expand Down
11 changes: 10 additions & 1 deletion backends/cuda-ref/ceed-cuda-ref-qfunctioncontext.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,16 @@ static inline int CeedQFunctionContextSyncH2D_Cuda(const CeedQFunctionContext ct
CeedCallCuda(ceed, cudaMalloc((void **)&impl->d_data_owned, ctx_size));
impl->d_data = impl->d_data_owned;
}
CeedCallCuda(ceed, cudaMemcpy(impl->d_data, impl->h_data, ctx_size, cudaMemcpyHostToDevice));

// Use async memcpy during CUDA Graph capture for compatibility
enum cudaStreamCaptureStatus capture_status;
cudaStreamIsCapturing(cudaStreamPerThread, &capture_status);
if (capture_status != cudaStreamCaptureStatusNone) {
CeedCallCuda(ceed, cudaMemcpyAsync(impl->d_data, impl->h_data, ctx_size, cudaMemcpyHostToDevice, cudaStreamPerThread));
} else {
CeedCallCuda(ceed, cudaMemcpy(impl->d_data, impl->h_data, ctx_size, cudaMemcpyHostToDevice));
}
Comment thread
jeremylt marked this conversation as resolved.

CeedCallBackend(CeedDestroy(&ceed));
return CEED_ERROR_SUCCESS;
}
Expand Down
Loading
Loading