| |
| |
| |
|
|
| #define NOMINMAX |
| #include <unistd.h> |
| #include "cudnn_att.h" |
| #include <cudnn_frontend.h> |
|
|
| namespace fe = cudnn_frontend; |
|
|
| |
| #if defined(ENABLE_FP32) |
| static_assert(false, "cuDNN is not supported in FP32 mode.") |
| |
| #elif defined(ENABLE_FP16) |
| #define CUDNN_16BIT fe::DataType_t::HALF |
| #else |
| #define CUDNN_16BIT fe::DataType_t::BFLOAT16 |
| #endif |
|
|
| static cudnnHandle_t cudnn_handle; |
| static size_t cudnn_workspace_size = 0; |
| static void* cudnn_workspace = NULL; |
|
|
| static void cuDNNCheck(cudnnStatus_t error, const char *file, int line) { |
| if (error != CUDNN_STATUS_SUCCESS) { |
| printf("[CUDNN ERROR] at file %s:%d:\n%s\n", file, line, cudnnGetErrorString(error)); |
| exit(EXIT_FAILURE); |
| } |
| }; |
| #define cuDNNCheck(err) (cuDNNCheck(err, __FILE__, __LINE__)) |
|
|
| static void checkCudnnFE(const fe::error_object& e, const char *file, int line) { |
| if(!e.is_good()) { |
| printf("[CUDNN ERROR] at file %s:%d:\n%s\n", file, line, e.err_msg.c_str()); |
| exit(EXIT_FAILURE); |
| } |
| } |
| #define checkCudnnFE(err) checkCudnnFE(err, __FILE__, __LINE__) |
|
|
| enum UIDs { |
| Q_UID, |
| K_UID, |
| V_UID, |
| Attn_scale_UID, |
| O_UID, |
| Stats_UID, |
| dO_UID, |
| dQ_UID, |
| dK_UID, |
| dV_UID |
| }; |
|
|
| |
| using cache_type_fwd = std::map<std::tuple<int,int,int,int, int>, std::shared_ptr<fe::graph::Graph>>; |
| using cache_type_bwd = std::map<std::tuple<int,int,int,int>, std::shared_ptr<fe::graph::Graph>>; |
|
|
| |
| auto lookup_cache_or_build_graph_fwd(int B,int H,int T,int HS, int is_inference_only) { |
|
|
| static cache_type_fwd user_maintained_cache_fwd; |
|
|
| auto key = std::make_tuple(B, H, T, HS, is_inference_only); |
|
|
| auto it = user_maintained_cache_fwd.find(key); |
| if (it != user_maintained_cache_fwd.end()) { |
| return it->second; |
| } |
|
|
| auto graph = std::make_shared<fe::graph::Graph>(); |
| graph->set_io_data_type(CUDNN_16BIT) |
| .set_intermediate_data_type(fe::DataType_t::FLOAT) |
| .set_compute_data_type(fe::DataType_t::FLOAT); |
|
|
| |
| auto Q = graph->tensor(fe::graph::Tensor_attributes().set_name("Q") |
| .set_dim({B, H, T, HS}) |
| .set_uid(Q_UID) |
| .set_stride({3 * H * HS * T, HS, 3 * H * HS, 1})); |
| auto K = graph->tensor(fe::graph::Tensor_attributes().set_name("K") |
| .set_dim({B, H, T, HS}) |
| .set_uid(K_UID) |
| .set_stride({3 * H * HS * T, HS, 3 * H * HS, 1})); |
| auto V = graph->tensor(fe::graph::Tensor_attributes().set_name("V") |
| .set_dim({B, H, T, HS}) |
| .set_uid(V_UID) |
| .set_stride({3 * H * HS * T, HS, 3 * H * HS, 1})); |
| auto attn_scale = graph->tensor(fe::graph::Tensor_attributes().set_name("attn_scale") |
| .set_dim({1, 1, 1, 1}) |
| .set_stride({1, 1, 1, 1}) |
| .set_uid(Attn_scale_UID) |
| .set_is_pass_by_value(true) |
| .set_data_type(fe::DataType_t::FLOAT)); |
|
|
| auto sdpa_options = fe::graph::SDPA_attributes().set_name("flash_attention"); |
| sdpa_options.set_is_inference(is_inference_only); |
| sdpa_options.set_attn_scale(attn_scale); |
| sdpa_options.set_causal_mask(true); |
|
|
| |
| auto [O, stats] = graph->sdpa(Q, K, V, sdpa_options); |
|
|
| |
| O->set_output(true).set_dim({B, H, T, HS}).set_stride({H * HS * T, HS, H * HS, 1}).set_uid(O_UID); |
|
|
| assert(stats == nullptr || is_inference_only == false); |
| if (is_inference_only == false) { |
| stats->set_output(true).set_data_type(fe::DataType_t::FLOAT) |
| .set_dim({B, H, T, 1}) |
| .set_stride({H * T, T, 1, 1}) |
| .set_uid(Stats_UID); |
| } |
|
|
| checkCudnnFE(graph->validate()); |
|
|
| |
| checkCudnnFE(graph->build_operation_graph(cudnn_handle)); |
| auto plans = graph->create_execution_plans({fe::HeurMode_t::A}); |
| checkCudnnFE(graph->check_support(cudnn_handle)); |
| checkCudnnFE(graph->build_plans(cudnn_handle)); |
| |
| |
| if (graph->get_workspace_size() > cudnn_workspace_size) { |
| if (cudnn_workspace_size > 0) { |
| cudaCheck(cudaFree(cudnn_workspace)); |
| } |
| cudnn_workspace_size = graph->get_workspace_size(); |
| cudaCheck(cudaMalloc(&cudnn_workspace, cudnn_workspace_size)); |
| } |
|
|
| user_maintained_cache_fwd.insert({key, graph}); |
|
|
| return graph; |
| } |
|
|
| auto lookup_cache_or_build_graph_bwd(int B, int NH, int T, int HS) { |
| static cache_type_bwd user_maintained_cache_bwd; |
|
|
| auto key = std::make_tuple(B, NH, T, HS); |
|
|
| auto it = user_maintained_cache_bwd.find(key); |
| if (it != user_maintained_cache_bwd.end()) { |
| return it->second; |
| } |
|
|
| auto graph = std::make_shared<fe::graph::Graph>(); |
| graph->set_io_data_type(CUDNN_16BIT) |
| .set_intermediate_data_type(fe::DataType_t::FLOAT) |
| .set_compute_data_type(fe::DataType_t::FLOAT); |
|
|
| |
| |
| auto Q = graph->tensor(fe::graph::Tensor_attributes().set_name("Q") |
| .set_dim({B, NH, T, HS}) |
| .set_uid(Q_UID) |
| .set_stride({3 * NH * HS * T, HS, 3 * NH * HS, 1})); |
| auto K = graph->tensor(fe::graph::Tensor_attributes().set_name("K") |
| .set_dim({B, NH, T, HS}) |
| .set_uid(K_UID) |
| .set_stride({3 * NH * HS * T, HS, 3 * NH * HS, 1})); |
| auto V = graph->tensor(fe::graph::Tensor_attributes().set_name("V") |
| .set_dim({B, NH, T, HS}) |
| .set_uid(V_UID) |
| .set_stride({3 * NH * HS * T, HS, 3 * NH * HS, 1})); |
| auto O = graph->tensor(fe::graph::Tensor_attributes().set_name("O") |
| .set_dim({B, NH, T, HS}) |
| .set_uid(O_UID) |
| .set_stride({NH * HS * T, HS, NH * HS, 1})); |
| auto dO = graph->tensor(fe::graph::Tensor_attributes().set_name("dO") |
| .set_dim({B, NH, T, HS}) |
| .set_uid(dO_UID) |
| .set_stride({NH * HS * T, HS, NH * HS, 1})); |
|
|
| auto stats = graph->tensor(fe::graph::Tensor_attributes().set_name("stats") |
| .set_dim({B, NH, T, 1}) |
| .set_uid(Stats_UID) |
| .set_stride({NH * T, T, 1, 1}) |
| .set_data_type(fe::DataType_t::FLOAT)); |
| auto attn_scale = graph->tensor(fe::graph::Tensor_attributes().set_name("attn_scale") |
| .set_dim({1, 1, 1, 1}) |
| .set_stride({1, 1, 1, 1}) |
| .set_is_pass_by_value(true) |
| .set_uid(Attn_scale_UID) |
| .set_data_type(fe::DataType_t::FLOAT)); |
| auto sdpa_backward_options = fe::graph::SDPA_backward_attributes().set_name("flash_attention_backward") |
| #if CUDNN_FRONTEND_MAJOR_VERSION > 1 || CUDNN_FRONTEND_MINOR_VERSION >= 5 |
| .set_deterministic_algorithm(true) |
| #endif |
| .set_causal_mask(true) |
| .set_attn_scale(attn_scale); |
|
|
| |
| auto [dQ, dK, dV] = graph->sdpa_backward(Q, K, V, O, dO, stats, sdpa_backward_options); |
|
|
| dQ->set_output(true).set_dim({B, NH, T, HS}).set_stride({3 * NH * HS * T, HS, 3 * NH * HS, 1}).set_uid(dQ_UID); |
| dK->set_output(true).set_dim({B, NH, T, HS}).set_stride({3 * NH * HS * T, HS, 3 * NH * HS, 1}).set_uid(dK_UID); |
| dV->set_output(true).set_dim({B, NH, T, HS}).set_stride({3 * NH * HS * T, HS, 3 * NH * HS, 1}).set_uid(dV_UID); |
|
|
| checkCudnnFE(graph->validate()); |
|
|
| |
| checkCudnnFE(graph->build_operation_graph(cudnn_handle)); |
| auto plans = graph->create_execution_plans({fe::HeurMode_t::A}); |
| checkCudnnFE(graph->check_support(cudnn_handle)); |
| checkCudnnFE(graph->build_plans(cudnn_handle)); |
|
|
| |
| |
| if (graph->get_workspace_size() > cudnn_workspace_size) { |
| if (cudnn_workspace_size > 0) { |
| cudaCheck(cudaFree(cudnn_workspace)); |
| } |
| cudnn_workspace_size = graph->get_workspace_size(); |
| cudaCheck(cudaMalloc(&cudnn_workspace, cudnn_workspace_size)); |
| } |
|
|
| user_maintained_cache_bwd.insert({key, graph}); |
| return graph; |
| } |
|
|
| void attention_forward_cudnn(floatX* out, |
| float* stats, |
| floatX* inp, |
| int B, int T, int NH, int C, cudaStream_t stream) { |
| NVTX_RANGE_FN(); |
| int HS = C / NH; |
| bool is_inference_only = (stats == nullptr); |
|
|
| cuDNNCheck(cudnnSetStream(cudnn_handle, stream)); |
|
|
| |
| auto graph = lookup_cache_or_build_graph_fwd(B, NH, T, HS, is_inference_only); |
|
|
| |
| void* devPtrQ = inp; |
| void* devPtrK = (inp + C); |
| void* devPtrV = (inp + 2 * C); |
| float attn_scale_cpu = 1.0 / sqrtf(HS); |
| void* devPtrO = out; |
|
|
| |
| std::unordered_map<int64_t , void*> variant_pack = { |
| {Q_UID, devPtrQ}, {K_UID, devPtrK}, {V_UID, devPtrV}, {Attn_scale_UID, &attn_scale_cpu}, {O_UID, devPtrO}}; |
|
|
| |
| if (is_inference_only == false) { |
| variant_pack[Stats_UID] = stats; |
| } |
|
|
| |
| checkCudnnFE(graph->execute(cudnn_handle, variant_pack, cudnn_workspace)); |
| cudaCheck(cudaGetLastError()); |
| } |
|
|
| void attention_backward_cudnn(floatX* dqkvr, |
| floatX* dout, floatX* qkvr, floatX* o, float* stats, |
| int B, int T, int NH, int C, cudaStream_t stream) { |
| NVTX_RANGE_FN(); |
| int HS = C / NH; |
|
|
| |
| auto graph = lookup_cache_or_build_graph_bwd(B, NH, T, HS); |
|
|
| |
| void* devPtrQ = qkvr; |
| void* devPtrK = (qkvr + NH * HS); |
| void* devPtrV = (qkvr + 2 * NH * HS); |
| void* devPtrO = o; |
| void* devPtrdO = dout; |
| void* devPtrStats = stats; |
| float attn_scale_cpu = 1.0 / sqrtf(HS); |
|
|
| void* devPtrdQ = dqkvr; |
| void* devPtrdK = (dqkvr + NH * HS); |
| void* devPtrdV = (dqkvr + 2 * NH * HS); |
|
|
| |
| std::unordered_map<int64_t, void*> variant_pack = { |
| {Q_UID, devPtrQ}, {K_UID, devPtrK}, {V_UID, devPtrV}, {O_UID, devPtrO}, {dO_UID, devPtrdO}, {Stats_UID, devPtrStats}, |
| {dQ_UID, devPtrdQ}, {dK_UID, devPtrdK}, {dV_UID, devPtrdV}, |
| {Attn_scale_UID, &attn_scale_cpu}}; |
|
|
| |
| cuDNNCheck(cudnnSetStream(cudnn_handle, stream)); |
| checkCudnnFE(graph->execute(cudnn_handle, variant_pack, cudnn_workspace)); |
| cudaCheck(cudaGetLastError()); |
| } |
|
|
| void create_cudnn() { |
| cuDNNCheck(cudnnCreate(&cudnn_handle)); |
| } |
|
|
| void destroy_cudnn() { |
| if (cudnn_workspace != NULL) { cudaCheck(cudaFree(cudnn_workspace)); } |
| cuDNNCheck(cudnnDestroy(cudnn_handle)); |
| } |