From a3332ed727409f89e2a53726d0b70f1d21975bf2 Mon Sep 17 00:00:00 2001 From: Brian Manthos Date: Tue, 4 Aug 2026 11:48:19 -0700 Subject: [PATCH 1/2] Make OSS engine registration RTTI-free Graph::register_oss_engine_() and Graph::register_oss_rms_norm_silu_engine_() use dynamic_cast to locate nodes in sub_nodes. Both are inline members of Graph, so every translation unit that includes cudnn_frontend.h compiles them, and GCC/Clang reject the header outright when RTTI is disabled: graph_interface.h:425:35: error: 'dynamic_cast' not permitted with '-fno-rtti' This makes the headers unusable for any consumer building with -fno-rtti or /GR-, a common configuration for libraries that ship binaries. It has been the case since these engines were introduced in v1.19.0. MSVC does not error, so the problem is invisible on Windows: it emits C4541 ("unpredictable behavior may result") and compiles. RTTI-disabled Windows builds therefore reach these casts with no guarantee they behave correctly. Replace both cast sites with RTTI-free equivalents: - SDPA lookup: add a virtual INode::get_sdpa_attributes() returning nullptr by default, overridden once in SDPANodeBase. CompositeSDPANode and UnifiedSDPANode both inherit `attributes` from that base, so a single override covers both and the two cast branches collapse into one. - RMSNorm+SiLU pattern match: gate on getType() and static_cast. RMSNORM and POINTWISE are distinct Type values, so this is an exact substitute for the check the dynamic_casts performed. Both replacements are cheaper than the casts they replace: a virtual dispatch and an enum comparison rather than an RTTI walk. The static_cast downcasts are sound. NodeCRTP derives from INode via public non-virtual single inheritance, and NodeCRTP already relies on the same property internally via static_cast(this). No functional change for RTTI-enabled builds. --- include/cudnn_frontend/graph_interface.h | 25 ++++++++++--------- .../node/scaled_dot_product_flash_attention.h | 5 ++++ include/cudnn_frontend/node_interface.h | 8 ++++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/include/cudnn_frontend/graph_interface.h b/include/cudnn_frontend/graph_interface.h index be54b51d8..3e36cb0ec 100644 --- a/include/cudnn_frontend/graph_interface.h +++ b/include/cudnn_frontend/graph_interface.h @@ -419,15 +419,13 @@ class Graph : public ICudnn, public INode { // Register an OSS NVRTC engine for SDPA by extracting tensor metadata from the SDPA node's attributes error_t register_oss_engine_() { - // Find the SDPA node in the graph's sub_nodes via dynamic_cast + // Find the SDPA node in the graph's sub_nodes. Uses the virtual get_sdpa_attributes() + // accessor rather than dynamic_cast so this header compiles under -fno-rtti / /GR-. + // Covers both CompositeSDPANode and UnifiedSDPANode, which share SDPANodeBase. SDPA_attributes const *sdpa_attrs = nullptr; for (auto const &sub_node : sub_nodes) { - if (auto *composite = dynamic_cast(sub_node.get())) { - sdpa_attrs = &composite->attributes; - break; - } - if (auto *unified = dynamic_cast(sub_node.get())) { - sdpa_attrs = &unified->attributes; + if (auto const *attrs = sub_node->get_sdpa_attributes()) { + sdpa_attrs = attrs; break; } } @@ -539,11 +537,14 @@ class Graph : public ICudnn, public INode { std::shared_ptr swish_output; for (size_t i = 0; i + 1 < sub_nodes.size(); ++i) { - auto *rmsnorm_node = dynamic_cast(sub_nodes[i].get()); - if (!rmsnorm_node) continue; - - auto *pointwise_node = dynamic_cast(sub_nodes[i + 1].get()); - if (!pointwise_node) continue; + // getType() + static_cast rather than dynamic_cast so this header compiles under + // -fno-rtti / /GR-. RMSNORM and POINTWISE are distinct Type values, so this is an + // exact substitute for the type-check the dynamic_casts performed. + if (sub_nodes[i]->getType() != Type::RMSNORM) continue; + auto *rmsnorm_node = static_cast(sub_nodes[i].get()); + + if (sub_nodes[i + 1]->getType() != Type::POINTWISE) continue; + auto *pointwise_node = static_cast(sub_nodes[i + 1].get()); if (pointwise_node->attributes.get_mode() != PointwiseMode_t::SWISH_FWD) continue; diff --git a/include/cudnn_frontend/node/scaled_dot_product_flash_attention.h b/include/cudnn_frontend/node/scaled_dot_product_flash_attention.h index 79f91a554..942b5c9bc 100644 --- a/include/cudnn_frontend/node/scaled_dot_product_flash_attention.h +++ b/include/cudnn_frontend/node/scaled_dot_product_flash_attention.h @@ -137,6 +137,11 @@ class SDPANodeBase : public NodeCRTP { SDPANodeBase(SDPA_attributes&& attributes_, detail::Context const& context) : NodeCRTP(context), attributes(std::move(attributes_)) {} + SDPA_attributes const* + get_sdpa_attributes() const override { + return &attributes; + } + bool is_paged_v() const { auto page_table_v_it = attributes.inputs.find(input_names::Page_table_V); diff --git a/include/cudnn_frontend/node_interface.h b/include/cudnn_frontend/node_interface.h index dad9b5987..8e2ef8ac2 100644 --- a/include/cudnn_frontend/node_interface.h +++ b/include/cudnn_frontend/node_interface.h @@ -402,6 +402,14 @@ class INode { virtual Type getType() = 0; + // Returns this node's SDPA attributes if it is an SDPA node, nullptr otherwise. + // Provides an RTTI-free alternative to dynamic_cast for SDPA node discovery so that + // these headers compile under -fno-rtti (GCC/Clang) and /GR- (MSVC). + virtual SDPA_attributes const* + get_sdpa_attributes() const { + return nullptr; + } + virtual std::pair> override_heuristics_query() const { return {-1, {}}; From 597a3d8214ec3f7db3c98c4b85727557af88f471 Mon Sep 17 00:00:00 2001 From: Brian Manthos Date: Tue, 4 Aug 2026 11:48:34 -0700 Subject: [PATCH 2/2] Build in-tree targets without RTTI by default Adds CUDNN_FRONTEND_ENABLE_RTTI (default OFF), which passes -fno-rtti (GCC/Clang) or /GR- (MSVC) to samples and tests, so a dynamic_cast added to the headers fails the build instead of only breaking downstream consumers that disable RTTI. The python bindings opt back in: pybind11's type registry is typeid-based and requires RTTI. --- CMakeLists.txt | 7 +++++++ python/CMakeLists.txt | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 357f7969b..33ad50028 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,11 +6,18 @@ option(CUDNN_FRONTEND_SKIP_JSON_LIB "Defines whether FE should not include nlohm option(CUDNN_FRONTEND_BUILD_SAMPLES "Defines if samples are built or not." ON) option(CUDNN_FRONTEND_BUILD_TESTS "Defines if unittests are built or not." ON) option(CUDNN_FRONTEND_BUILD_PYTHON_BINDINGS "Defines if python bindings are built or not." OFF) +option(CUDNN_FRONTEND_ENABLE_RTTI "Build in-tree targets with RTTI. Off by default so the headers stay compilable for consumers that disable it." OFF) if(MSVC OR MSYS OR MINGW) add_compile_options(/W4 /WX) + if(NOT CUDNN_FRONTEND_ENABLE_RTTI) + add_compile_options(/GR-) + endif() else() add_compile_options(-Wall -Wextra -Wpedantic -Werror -Wno-error=attributes -Wno-attributes -Wno-error=unused-function -Wno-unused-function) + if(NOT CUDNN_FRONTEND_ENABLE_RTTI) + add_compile_options(-fno-rtti) + endif() endif() add_library(cudnn_frontend INTERFACE) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index f0de87734..6792a4bd3 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -72,6 +72,14 @@ target_link_libraries(_compiled_module PRIVATE pybind11::headers) target_compile_features(_compiled_module PRIVATE cxx_std_20) +# pybind11's type registry is typeid-based, so this module requires RTTI even when +# CUDNN_FRONTEND_ENABLE_RTTI is off for the rest of the build. +if(MSVC) + target_compile_options(_compiled_module PRIVATE /GR) +else() + target_compile_options(_compiled_module PRIVATE -frtti) +endif() + target_include_directories( _compiled_module PRIVATE $