-
Notifications
You must be signed in to change notification settings - Fork 73
cuda-gen: Add CUDA Graph capture and replay for composite operators #1987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6261385
b156a00
83c1d9f
bbad4e2
285b661
10d6dd2
4abc7ad
1fa80ed
5b8e14f
94bdeca
a00af53
1ca2b5f
719027b
40f8c3e
91c8d6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,8 @@ | |||||||||||||||||||||||||||||
| #include <cuda.h> | ||||||||||||||||||||||||||||||
| #include <cuda_runtime.h> | ||||||||||||||||||||||||||||||
| #include <stddef.h> | ||||||||||||||||||||||||||||||
| #include <stdint.h> | ||||||||||||||||||||||||||||||
| #include <stdlib.h> | ||||||||||||||||||||||||||||||
| #include <string.h> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| #include "../cuda/ceed-cuda-common.h" | ||||||||||||||||||||||||||||||
|
|
@@ -31,7 +33,25 @@ 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; | ||||||||||||||||||||||||||||||
|
|
@@ -284,7 +304,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); | ||||||||||||||||||||||||||||||
| CUstream stream_to_use = (capture_status != cudaStreamCaptureStatusNone) ? cudaStreamPerThread : NULL; | ||||||||||||||||||||||||||||||
| 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)); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -299,48 +323,215 @@ 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; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||
| 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 on lines
+354
to
+367
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't the right way to benchmark against
Suggested change
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||
| 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)); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This call pattern prevents each suboperator from running on separate streams. You won't see a perf difference until you have large, multioperator applications, but the perf difference can be significant.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The disabled graph case should restore the previous implementation, with multi-stream operators, sequential checks, etc
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given you do this a bunch of places, I'd recommend a helper function which looks like: static int CeedOperatorApplyAddComposite_NoGraph_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;
CeedCallBackend(CeedOperatorGetCeed(op, &ceed));
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;
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));
}
}
if (is_sequential) CeedCallCuda(ceed, cudaStreamDestroy(stream));
if (input_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorRestoreArrayRead(input_vec, &input_arr));
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;
CeedDebug(ceed, "\nFalling back to /gpu/cuda/ref CeedOperator for ApplyAdd\n");
CeedCallBackend(CeedOperatorGetFallback(sub_operators[i], &op_fallback));
CeedCallBackend(CeedOperatorApplyAdd(op_fallback, input_vec, output_vec, request));
}
}
CeedCallBackend(CeedDestroy(&ceed));
return CEED_ERROR_SUCCESS;
} |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return CEED_ERROR_SUCCESS; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if (is_sequential) CeedCallCuda(ceed, cudaStreamDestroy(stream)); | ||||||||||||||||||||||||||||||
| if (input_vec != CEED_VECTOR_NONE) CeedCallBackend(CeedVectorRestoreArrayRead(input_vec, &input_arr)); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This removed the |
||||||||||||||||||||||||||||||
| 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) { | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be?
Suggested change
It's possible to have a passive input and active output |
||||||||||||||||||||||||||||||
| for (CeedInt i = 0; i < num_suboperators; i++) { | ||||||||||||||||||||||||||||||
| CeedCallBackend(CeedOperatorApplyAdd(sub_operators[i], input_vec, output_vec, request)); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to use the same helper here |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| 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)); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should use |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| CeedCallCuda(ceed, cudaDeviceSynchronize()); | ||||||||||||||||||||||||||||||
| impl->warmup_done = true; | ||||||||||||||||||||||||||||||
| return CEED_ERROR_SUCCESS; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 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)) { | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||
|
Comment on lines
+467
to
+471
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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? |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Phase 3: replay. | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| // graph_instance == NULL means capture failed; direct-apply every call. | ||||||||||||||||||||||||||||||
| if (!impl->graph_instance) { | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use helper here |
||||||||||||||||||||||||||||||
| 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 on lines
+490
to
+498
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check seems like it should happen before the graph is built, that way you can rebuild the graph if needed instead of waiting until the next apply call |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+500
to
507
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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 on lines
+531
to
+533
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here -- use the helper, not fall back. |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return CEED_ERROR_SUCCESS; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -465,7 +656,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)); | ||||||||||||||||||||||||||||||
|
|
@@ -885,6 +1076,15 @@ 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; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| CeedCall(CeedOperatorIsComposite(op, &is_composite)); | ||||||||||||||||||||||||||||||
| if (is_composite) { | ||||||||||||||||||||||||||||||
| CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "ApplyAddComposite", CeedOperatorApplyAddComposite_Cuda_gen)); | ||||||||||||||||||||||||||||||
|
|
@@ -897,6 +1097,7 @@ int CeedOperatorCreate_Cuda_gen(CeedOperator op) { | |||||||||||||||||||||||||||||
| CeedOperatorLinearAssembleAddDiagonalAtPoints_Cuda_gen)); | ||||||||||||||||||||||||||||||
| CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "LinearAssembleSingle", CeedOperatorAssembleSingleAtPoints_Cuda_gen)); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!is_at_points) { | ||||||||||||||||||||||||||||||
| CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "LinearAssembleQFunction", CeedOperatorLinearAssembleQFunction_Cuda_gen)); | ||||||||||||||||||||||||||||||
| CeedCallBackend(CeedSetBackendFunction(ceed, "Operator", op, "LinearAssembleQFunctionUpdate", | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This same issue is going to happen for any passive inputs which have been updated on the CPU -- there should be a more robust way to handle this