63 KiB
RFC: cuDNN shim for hipDNN
- Status: Accepted
- Authors: Mitch Ousdahl
Table of Contents
- Executive Summary
- Requirements
- Glossary
- Proposed Design
- Key Design Decisions
- Risks
- Execution Plan
- Testing Plan
- Future Considerations
1. Executive Summary
Why
There are many barriers to adoption of a new API interface, especially in the cases where an incumbent already exists. In our case we are not only working against an incumbent competitor interface (cuDNN), but also our own older interface (MIOpen). This RFC documents the process to aid in integrating with consumer code with cuDNN incumbency; another RFC will address MIOpen incumbency. A direct example from PyTorch is our initial motivator. The difficulty in creating a hipDNN-native backend for PyTorch has been met with a great deal of administration friction, whereas using hipify (or an equivalent) to convert an existing cuDNN backend is seen as much lower risk and therefore more desirable.
What
This RFC describes the addition of a cuDNN-equivalent frontend for hipDNN. This will be implemented as an aid to conversion of cuDNN frontend v9 consuming code to be 'hipified' to instead consume hipDNN. The new cuDNN-style frontend graph will primarily forward work to a wrapped hipDNN frontend graph object. In cases where it makes sense, missing cuDNN functionality that can be replicated by alternative hipDNN interfaces will be implemented inside the wrapper. In cases where missing cuDNN functionality has no current implementation, one will either be created in hipDNN to match or provide an adequate error.
Not
This RFC supports cuDNN frontend v9 translation units only. The shim's
cudnn_frontend.h is a hand-curated v9-only umbrella; it does not
reconstruct the v0.x / v8 interface (not even as compile-only stubs), so
source using v0.x types (TensorBuilder, OperationBuilder,
cudnnException, etc. — e.g. PyTorch's Conv_v8.cpp and the quantized
cuDNN ops) will not compile against it. The only carve-out is where a v9
signature itself names a v0.x symbol: the shim provides just enough to keep
v9 TUs compiling — chiefly the small C-API stub cudnn.h
(cudnnHandle_t, cudnnStatus_t, etc.; see §4.7). A v0.x stub layer is a
candidate follow-up RFC (§9).
Also out of scope: ABI compatibility (consumers must recompile) and Python bindings — this is a C++-only shim, and the motivating consumer (PyTorch) uses the C++ API directly. A Python-binding layer would be a separate future RFC (§9).
2. Requirements
- Header-Only: The interface must be entirely header-only.
- Interface parity: Must supply the cuDNN frontend v9 graph API
surface, plus all additional API needed to use it (the C-API types and
the handful of handle/error/version entry points it depends on — §4.7).
This is intentionally phrased as "the V9 API and what it needs to run"
rather than "100% of everything cuDNN FE ships", since the v0.x surface is
explicitly out of scope (§1).
- Some small wiggle-room is expected for things that don't map cleanly (heuristic mode enums, for example).
- Functional parity: Must supply as much of cuDNN's functionality as
possible.
- Any un-implemented portions of that surface area must be at least backed
with a compile-time
static_assert(or, where the call must remain reachable at runtime for hipification to compile, with a runtime error that maps to a documentedHIPDNN_FRONTEND_*status code). - No extension methods from hipDNN will be extended into this interface.
- We target the cuDNN frontend v1.24 release (the upstream NVIDIA/cudnn-frontend library version — not a hipDNN release version; the two are independent, see §4.8). Pin details below.
- Any un-implemented portions of that surface area must be at least backed
with a compile-time
- Performance parity: Must minimally add overhead above-and-beyond
hipDNN's existing overhead. Concrete budget: < 1% additional
build()time, < 1 µs added perexecute(), both measured against nativehipdnn_frontendfor the same graph. See §8.4 for how this is enforced. - MIT Licensed: With credit to NVIDIA where appropriate (e.g., for any comments, enum value names, or documentation that closely mirror upstream cuDNN frontend headers).
The pinned upstream reference is cuDNN FE v1.24.0, verified in
include/cudnn_frontend_version.h:
CUDNN_FRONTEND_MAJOR_VERSION=1, MINOR=24, PATCH=0,
CUDNN_FRONTEND_VERSION = 12400.
3. Glossary
- cuDNN: NVIDIA's implementation of a graph-based API for processing tensor data.
- cuDNN frontend: NVIDIA's MIT-licensed header-only C++ wrapper around the cuDNN backend C API (github.com/NVIDIA/cudnn-frontend). Hereafter just "cuDNN FE" when context is clear.
- frontend: Describes the interface that a consumer of an API would primarily interface with. In hipDNN / cuDNN's world, this is a header-only library that lifts / lowers data to a backend component.
- backend: Describes the component with a common C interface that does the heavy lifting of managing object state, plugins, and descriptor components.
- descriptor: A descriptor is a consumer-opaque object. The backend provides a common C interface for managing lifecycle and properties for any descriptor.
- plugin: In hipDNN's case, this refers to an engine provider plugin that does the work of processing tensor data.
- shim: The translation layer described by this RFC; cuDNN-shaped C++ types
that wrap and forward to
hipdnn_frontend. - hipify: AMD's source-to-source CUDA→HIP conversion tooling. In this RFC, used loosely to mean "the process of rewriting cuDNN identifiers to hipDNN identifiers", regardless of the specific tool.
- v0.x API / v8 backend descriptor API: cuDNN frontend's pre-v1 C++
surface, built around builder classes (
TensorBuilder,OperationBuilder,ExecutionPlanBuilder,VariantPackBuilder,EngineConfigGenerator,EngineHeuristicsBuilder, etc.) that wrap the underlying cuDNN v8 backend descriptor C API (cudnnBackendCreateDescriptor,cudnnBackendSetAttribute,cudnnBackendFinalize). These types still ship in upstreamcudnn_frontend.hasusingaliases for*_v8legacy classes, even in v1.24. - v9 graph API: cuDNN frontend's v1.x C++ surface (also referred to
by NVIDIA as "FE 1.0" and the cuDNN v9 graph API). Built around
cudnn_frontend::graph::Graphand the*_attributesfamily. This is the primary (and only) target of this RFC.
4. Proposed Design
4.1 Layout and packaging
The shim will primarily reside under
projects/hipdnn/frontend/include/hipdnn_compatibility/cudnn/ and be exposed as an
exported header-only CMake target. The objects will be namespaced
hipdnn_frontend::compatibility::cudnn_frontend (referred to below as
<shim_ns>), mirroring upstream's cudnn_frontend namespace as closely as
possible so a consumer can write namespace cudnn_frontend = hipdnn_frontend::compatibility::cudnn_frontend; and have nested namespaces
(graph, detail, eventually experimental) line up name-for-name. Any
internal details will be placed in a detail/ subdirectory and additionally
namespaced as <shim_ns>::detail.
Proposed source layout (filenames mirror upstream cuDNN FE exactly, all
.h per §5.1):
projects/hipdnn/frontend/include/hipdnn_compatibility/cudnn/
cudnn.h # Stub: v9-required C-API types only (see §4.7)
cudnn_frontend.h # Umbrella; matches upstream filename
cudnn_frontend_version.h
cuda_runtime_compat.h # OPT-IN; not auto-included
cudnn_frontend/ # Mirrors upstream include/cudnn_frontend/
graph_helpers.h
graph_interface.h
graph_properties.h
node_interface.h
plans.h
...
detail/ # Shim-internal; namespaced <shim_ns>::detail
type_mapping.h # cuDNN enum/struct ↔ hipDNN mapping
status_translation.h # cudnnStatus_t ↔ hipdnnStatus_t
graph_wrapper.h # Wraps hipdnn_frontend::graph::Graph
tensor_wrapper.h
node_wrappers/...
handle_wrapper.h
logging_bridge.h
Per the aliasing-first strategy (§4.3.1), much of the detail/ wrapper set
above is expected to be small: most public headers
(cudnn_frontend/*.h) reduce to thin umbrellas of using declarations
that re-export 1:1 hipDNN types, and detail/ carries real code only for
the composition wrapper (graph_wrapper.h), the residual non-aliasable
enum mappings (type_mapping.h), and the error/logging bridges. The
node_wrappers/ directory holds Tier-2 error-stubs and Tier-3 composition
wrappers only — not one file per node.
A separate, opt-in header,
<hipdnn_compatibility/cudnn/cuda_runtime_compat.h>, will provide #define
overrides for the small fixed set of CUDA runtime symbols that frequently
appear in cuDNN-consuming code and have direct HIP equivalents:
| CUDA symbol | HIP equivalent |
|---|---|
cudaMalloc |
hipMalloc |
cudaFree |
hipFree |
cudaMemcpy / cudaMemcpyAsync |
hipMemcpy / hipMemcpyAsync |
cudaStream_t / cudaStreamCreate / cudaStreamDestroy / cudaStreamSynchronize |
hipStream_t / hipStreamCreate / hipStreamDestroy / hipStreamSynchronize |
cudaEvent_t / cudaEventCreate / cudaEventDestroy / cudaEventRecord |
hipEvent_t / hipEventCreate / hipEventDestroy / hipEventRecord |
cudaGetDevice / cudaSetDevice |
hipGetDevice / hipSetDevice |
cudaError_t / cudaSuccess |
hipError_t / hipSuccess |
This header is pinned to that set. New symbols are added only on
documented downstream-consumer demand. The header is not included by
the umbrella shim header — consumers must #include it explicitly to
opt in. This avoids polluting the global macro namespace for projects
that already use a different HIP-shim strategy (e.g., hipify-perl
conversion, or manually #ifdef'd code).
4.2 Target audience and conversion path
The shim is designed for two distinct workflows, both of which require the consumer source to be v9-only (per §1):
- Textual hipification — a user (or tool) globally rewrites
cudnn_frontend::→hipdnn_frontend::compatibility::cudnn_frontend::(or, vianamespace cudnn_frontend = hipdnn_frontend::compatibility::cudnn_frontend;, leaves the in-code symbols unchanged) and swaps#include <cudnn_frontend.h>for the shim header. - Gradual migration — a downstream project (e.g., PyTorch's
aten/src/ATen/native/cudnn/MHA.cpp) keeps a single source tree that compiles against either real cuDNN or the shim, with their existing platform-gating mechanism (typically__HIP_PLATFORM_AMD__/USE_ROCM) selecting which include path is active. Wiring this is the consumer's responsibility; the shim takes no stance on which flag is used.
To support workflow (2), the shim's public symbols must be name-for-name and
signature-for-signature compatible with cuDNN FE within the limits described
in §4.7. To support workflow (1), the umbrella header must be includable by
itself and must not require any additional #defines or CMake variables to
function.
Consumer source that mixes v9 graph API calls with v0.x backend
descriptor calls in the same TU (e.g., PyTorch's Conv_v8.cpp) is not
supported by this shim. Such files must either be rewritten to v9 first,
or wait for the follow-up v0.x stub RFC (§9). Consumer projects can keep
v0.x TUs in their tree as long as those TUs are excluded from the build
when the shim is in use.
4.3 Namespace and type mapping
The top-level cuDNN FE namespace is cudnn_frontend. The shim's top-level
namespace is <shim_ns> =
hipdnn_frontend::compatibility::cudnn_frontend, chosen so the leaf
matches upstream's name and namespace cudnn_frontend = <shim_ns>; is the
only aliasing step a consumer needs. Nested namespaces line up
correspondingly: <shim_ns>::graph ↔ cudnn_frontend::graph,
<shim_ns>::detail ↔ cudnn_frontend::detail, and (if/when wrapped)
<shim_ns>::experimental ↔ cudnn_frontend::experimental. Performing the
alias is the consumer's responsibility — the shim does not ship a
namespace cudnn_frontend = ... line so as not to collide with consumers
that include real cuDNN FE elsewhere in the same translation unit.
cuDNN FE uses two parallel type families. C-API types (cudnnHandle_t,
cudnnStatus_t, cudnnDataType_t, cudnnTensorFormat_t,
cudnnConvolutionMode_t, cudnnReduceTensorOp_t, cudnnNormFwdPhase_t,
cudnnBackendHeurMode_t, cudnnBackendNumericalNote_t,
cudnnBackendBehaviorNote_t, cudnnBackendDescriptorType_t, ...) come from
<cudnn.h>, which cudnn_frontend.h includes (cudnn_frontend.h:106).
FE-native C++ enums in namespace cudnn_frontend (verified in
include/cudnn_frontend_utils.h) are: BuildPlanPolicy_t (line 334),
TensorReordering_t (348), ResampleMode_t (363), PaddingMode_t (383),
ReshapeMode_t (391), ConvolutionMode_t (405), NormFwdPhase_t (426),
MoeGroupedMatmulMode_t (440), DescriptorType_t (456), NormMode_t (503),
PointwiseMode_t (525), HeurMode_t (635), BehaviorNote_t (650),
NumericalNote_t (670), DataType_t (700), ReductionMode_t (751),
RngDistribution_t (779), DiagonalAlignment_t (996),
AttentionImplementation_t (1003).
| Family | Symbol | Shim mapping |
|---|---|---|
C-API (<cudnn.h>) |
cudnnHandle_t |
Alias declared in the shim's stub cudnn.h: using cudnnHandle_t = hipdnn_frontend::hipdnnHandle_t; — see §4.7. |
| C-API | cudnnStatus_t, cudnnTensorFormat_t, cudnnConvolutionMode_t, cudnnDataType_t, cudnnBackend*_t |
Defined in the shim's stub cudnn.h (see §4.7), mapped to hipDNN equivalents on the way through |
| FE namespace | cudnn_frontend::DataType_t |
Aliased to hipdnn_frontend::DataType_t. cuDNN has 20 values incl. FP8/FP4/INT4 variants; any the hipDNN enum is missing are added to it so the alias stays a name-superset (§4.3) |
| FE namespace | cudnn_frontend::HeurMode_t |
Aliased to hipdnn_frontend::HeurMode_t — values A, B, FALLBACK, OPENSOURCE (added on the hipDNN side if missing). See §4.5 for runtime behavior |
| FE namespace | cudnn_frontend::PointwiseMode_t |
Aliased to hipdnn_frontend::PointwiseMode_t |
| FE namespace | cudnn_frontend::ReductionMode_t |
Aliased to hipdnn_frontend::ReductionMode_t |
| FE namespace | cudnn_frontend::ResampleMode_t, PaddingMode_t, ConvolutionMode_t, NormFwdPhase_t, NormMode_t, RngDistribution_t, DiagonalAlignment_t, AttentionImplementation_t, MoeGroupedMatmulMode_t, BuildPlanPolicy_t, TensorReordering_t, ReshapeMode_t, DescriptorType_t |
Aliased to the hipDNN _t equivalents; missing values added on the hipDNN side to keep each a name-superset |
| FE namespace | cudnn_frontend::NumericalNote_t |
Aliased to hipdnn_frontend::NumericalNote_t — values: NOT_SET, TENSOR_CORE, DOWN_CONVERT_INPUTS, REDUCED_PRECISION_REDUCTION, FFT, NONDETERMINISTIC, WINOGRAD, WINOGRAD_TILE_4x4, WINOGRAD_TILE_6x6, WINOGRAD_TILE_13x13, STRICT_NAN_PROP (added on the hipDNN side as needed). Per-note handling is triaged in §4.5 |
| FE namespace | cudnn_frontend::BehaviorNote_t |
Aliased to hipdnn_frontend::BehaviorNote_t — values: NOT_SET, RUNTIME_COMPILATION, REQUIRES_FILTER_INT8x32_REORDER, REQUIRES_BIAS_INT8x32_REORDER, SUPPORTS_CUDA_GRAPH_NATIVE_API, CUBLASLT_DEPENDENCY (added on the hipDNN side as needed). These are CUDA-specific; per-note handling is triaged in §4.5 |
| FE namespace | cudnn_frontend::error_code_t |
Aliased to hipdnn_frontend::error_code_t; the cuDNN-only values are added to hipDNN's enum so it is a name-superset (§4.6). 16 values incl. OK, ATTRIBUTE_NOT_SET, SHAPE_DEDUCTION_FAILED, INVALID_TENSOR_NAME, INVALID_VARIANT_PACK, GRAPH_NOT_SUPPORTED, GRAPH_EXECUTION_PLAN_CREATION_FAILED, GRAPH_EXECUTION_FAILED, HEURISTIC_QUERY_FAILED, UNSUPPORTED_GRAPH_FORMAT, CUDA_API_FAILED, CUDNN_BACKEND_API_FAILED, INVALID_CUDA_DEVICE, HANDLE_ERROR, INVALID_VALUE, NVRTC_COMPILATION_FAILED (verified at graph_helpers.h:36) |
| FE namespace | cudnn_frontend::error_object (struct) |
Aliased to hipdnn_frontend::error_object, with the full error_code_t value set present on the hipDNN side (§4.6) so the type is 1:1. Public members: code (field), err_msg (field), get_code(), get_message(), is_good(), is_bad(), operator==(error_code_t), operator!=(error_code_t). Verified at graph_helpers.h:55. |
| FE namespace | cudnn_frontend::error_t |
Aliased to hipdnn_frontend::error_t (typedef for error_object). Both names must be exposed by the shim. |
cudnn_frontend::graph |
Graph |
Class wrapping hipdnn_frontend::graph::Graph (see §4.4) |
cudnn_frontend::graph |
Tensor_attributes |
Wraps hipdnn_frontend::TensorAttributes. Verified at graph_properties.h:49 |
cudnn_frontend::graph |
All *_attributes classes |
Wrappers; defined in upstream graph_properties.h. Full list verified there — see §4.4.2 |
cudnn_frontend::graph |
*Node / INode / NodeCRTP (graph_properties.h:455, node_interface.h:45) |
Internal; nominally in the public namespace but not user-facing (verified — no sample / consumer constructs them directly). Not wrapped initially. Since hipDNN has matching internals, these could be aliased for completeness later, but that is not required for any known consumer. |
| FE namespace, v0.x | ConvDesc, ConvDescBuilder, EngineHeuristics, EngineConfig, EngineFallbackList, ResampleDesc, ... |
using aliases for *_v8 legacy types (verified at cudnn_frontend.h:137-152). These are pulled into the umbrella header unconditionally. See §4.7 for the v0.x compatibility problem. |
These FE-namespace enums are not re-declared by the shim at all — they
are aliased. hipDNN already publishes cuDNN-named _t typedefs for them
(hipdnn_frontend::DataType_t, HeurMode_t, ConvolutionMode_t,
PointwiseMode_t, ReductionMode_t, BehaviorNote_t, BuildPlanPolicy_t,
NormFwdPhase_t, …, several carrying an explicit "Matches the
cudnn-frontend … for API compatibility" comment in
frontend/include/hipdnn_frontend/Types.hpp). The shim aliases each hipDNN
type into <shim_ns> (using hipdnn_frontend::DataType_t;) rather than
re-declaring it. See §4.3.1.
Aliasing requires hipDNN's enum to be a name-superset of the cuDNN one —
every enumerator a consumer can name must exist on the hipDNN side, with
matching semantics. Where hipDNN is currently missing values (for example
DataType_t's FP8 / FP4 / INT4 variants), the go-forward is to add the
missing values to the hipDNN enum so it becomes a strict superset and the
alias holds. This is the same pattern as the error-code enum (§4.6): close
the gap on the hipDNN side and alias, rather than re-declare a parallel enum
and translate. A value that hipDNN's enum carries but no engine yet supports
is a runtime GRAPH_NOT_SUPPORTED at build time (§4.4.2), not an
enum-mapping concern.
Bidirectional static constexpr mapping (to_hipdnn() / from_hipdnn() in
detail/type_mapping.h) is therefore needed only for the C-API enum
family — the <cudnn.h> stub types (cudnnDataType_t, etc., §4.7) that
map to their hipDNN C equivalents — not for the FE-namespace enums, which are
aliases.
The shim never static_casts between the cuDNN and hipDNN enum families.
Even where the integer values happen to coincide, the equivalence is only
nominal; a cast like static_cast<HeurMode_t>(1) is forbidden because it
silently breaks the moment either side reorders or inserts a value. Identity
is established either by aliasing (the values are the same type) or by a
named mapping function — never by a numeric cast.
4.3.1 Three-tier mapping strategy
A central goal is to minimize wrappers. For every cuDNN FE symbol the shim picks the lowest tier that is correct:
-
Alias (preferred). The symbol is exactly 1:1 with a hipDNN type (same shape, same values, same semantics). Expose it with a
usingdeclaration in the matching shim namespace — no wrapper class, no conversion, zero overhead:namespace hipdnn_frontend::compatibility::cudnn_frontend { using hipdnn_frontend::ConvolutionMode_t; using hipdnn_frontend::DataType_t; using hipdnn_frontend::HeurMode_t; using hipdnn_frontend::error_t; } namespace hipdnn_frontend::compatibility::cudnn_frontend::graph { using hipdnn_frontend::graph::Tensor_attributes; using hipdnn_frontend::graph::Conv_fprop_attributes; // …one per 1:1 type }All FE-namespace enums (hipDNN already publishes them as
_ttypedefs; where it is missing values, those values are added to the hipDNN enum so it stays a name-superset — §4.3, §4.6),error_t/error_object, and every*_attributesclass that maps 1:1 (Reference §2) fall here. The guiding move when a type is almost 1:1 is to close the gap on the hipDNN side so it can be aliased, rather than to introduce a parallel re-declared type. -
Error-wrapper. The symbol exists in cuDNN FE but hipDNN has no support yet. Declare a minimal type/method with the exact cuDNN FE signature so consumer source compiles, but any use returns
error_code_t::GRAPH_NOT_SUPPORTEDat runtime (orstatic_asserts for non-type template parameters), with a message pointing the user to file a GitHub issue for the missing feature. To keep this cheap at scale, the shim provides a small macro / generator that stamps out a fail-stub from the upstream signature (see Risk §6.5). Every "(none)" row in Reference §2 is an error-wrapper. -
Composition-wrapper (fallback only). The symbol exists on both sides but the surfaces genuinely don't line up, so neither aliasing nor a pure forward is possible. Contain the hipDNN object by value and forward method-by-method. In practice the principal case is
graph::Graph, whose plan-filtering, notes, knobs, and heuristics surface has no clean hipDNN counterpart (§4.5, §5.2).
The default is to climb to the lowest applicable tier; a wrapper is a signal that the underlying surface isn't yet 1:1, and closing that gap on the hipDNN side (so the type can be demoted to an alias) is preferred over carrying the wrapper indefinitely.
4.4 Graph and node wrapping
The principal class is <shim_ns>::graph::Graph. It is the main Tier-3
composition wrapper (§4.3.1): it contains a hipdnn_frontend::graph::Graph
by value and forwards calls, holding extra state only for the surface that
isn't 1:1 (notes, knobs, heuristics — §4.5).
The *_attributes classes follow the three-tier rule (§4.3.1), not a
blanket "wrap everything" policy:
- Where a
*_attributesclass is exactly 1:1 with its hipDNN counterpart (the majority — see Reference §2), the shim aliases the hipDNN type into<shim_ns>::graphwith ausingdeclaration. No wrapper, no per-setter translation. - Where hipDNN has no equivalent ("(none)" rows in Reference §2), the shim ships a Tier-2 error-wrapper that compiles but fails at use.
- Only a class whose surface exists on both sides but doesn't line up gets a Tier-3 composition wrapper that translates on the way in and materialises the inner hipDNN attribute object on calls into hipDNN.
Three surface details that drive the wrapper implementation:
-
Every node-adding method on
Graphtakes its*_attributesparameter by value, not by reference. -
Graph::execute()has four overloads (two key-type variants of the variant pack, plus shape-override and flat-pointer-array forms); the variant-pack map is passed by non-const reference. The shim wraps all four; internally they all funnel through the same hipDNN execute path after key-type / pointer-form translation. -
The graph-level configuration setters are triaged individually, because silently ignoring some of them would change correctness or resource behavior. Disposition:
Setter Disposition set_override_shape_enabledForward — hipDNN supports it now (matching) set_dynamic_shape_enabledForward if supported; else no-op + debug log (re-confirm during impl) set_kernel_cacheLog and ignore — kernel reuse is still valid without it set_sm_countError — ignoring partial-device allocation can cause runtime issues; not safe to silently drop set_device_propertiesError until AOT / deviceless compilation support lands (its primary use) set_sm_versionInvestigate — disposition TBD; lean toward error if it affects codegen target Where a setter errors, it does so via the recorded-error mechanism so the fluent chain isn't broken mid-expression (the error surfaces at the next
validate()/build_operation_graph(); see §4.4.2). Where it is a benign no-op, it logs at debug level and returns*this. This keeps portable consumer code compiling while refusing to silently mis-execute.
For the full upstream Graph signature with line-number citations against
include/cudnn_frontend/graph_interface.h, see
Supporting Reference §1.
4.4.1 Tensor identity
cuDNN FE uses std::shared_ptr<Tensor_attributes> as the stable identity for
a tensor across a graph. hipDNN uses a uid (int64_t) and a node's
input/output role to identify a tensor in the variant pack.
This requires no special handling in the shim: hipdnn_frontend
already replicates cuDNN FE's identity semantics 1:1. Specifically:
- A UID the user sets via
set_uid()is honored; a tensor left without a UID is auto-assigned a unique one at build time — never overlapping a pre-set UID — byhipdnn_frontend::graph::Graph::assignUnsetTensorUids()(verified atGraph.hpp:293). This matches cuDNN FE, which is also late-bound:set_uid()only records the request, and uniqueness is enforced during build, not at the call site. - Duplicate UIDs are detected at build/validation time (UIDs determine
graph edges, so a collision yields a validation failure), again matching
cuDNN FE's behavior of failing during
build_operation_graphon duplicate UIDs. - hipDNN already exposes a
shared_ptr<TensorAttributes>-keyedexecute()overload that internally lowers to themap<int64_t, void*>variant pack (verified atGraph.hpp:1760), so the shim does not need to maintain its ownTensor_attributes*→uidmap.
Consequently there is no shim-side UID allocator, no construction-time
assignment, and no auxiliary identity map. UIDs need not be Graph-scoped —
a Tensor_attributes may be shared across graphs, and the only error
condition is a duplicate UID within a single graph, which both libraries
already reject at build time. The shim's Tensor_attributes setters
(set_uid, set_output, etc.) forward directly to the aliased/wrapped
hipDNN type.
4.4.2 Node coverage
Upstream graph_properties.h defines 39 *_attributes classes for the v9
graph API; roughly half have a current hipdnn_frontend equivalent and
half do not. For each class with a 1:1 hipDNN equivalent, the shim
aliases the hipDNN type (§4.3.1) — no wrapper. For each class
without one, the shim declares the attribute class and Graph::*
method with the cuDNN FE signature (so source compiles) but reports
error_code_t::GRAPH_NOT_SUPPORTED, and the omission is noted in the shim
header's Doxygen.
Where the error surfaces. Node-adding methods return
std::shared_ptr<Tensor_attributes>, not error_t, so an unsupported node
cannot return the error code directly from the method call. Instead, adding
an unsupported node (or using an errored setter, §4.4) records an error on
the composition Graph, and that error is returned from the next
validate() / build_operation_graph() call. This matches how a consumer
already checks graph construction (PyTorch wraps these in
AT_CUDNN_FRONTEND_CHECK), so no special-casing is needed on the consumer
side. The error message points the user to file a GitHub issue for the
missing node.
The full class-by-class table (39 entries, with line numbers in
graph_properties.h and the matching hipDNN attribute type — or "none" —
for each) is in
Supporting Reference §2.
4.4.3 Verified PyTorch consumer surface (cuDNN FE v9 graph API)
aten/src/ATen/native/cudnn/MHA.cpp is the only file in PyTorch that
consumes the v9 graph API; all other cuDNN consumers (Conv_v8.cpp,
aten/src/ATen/native/quantized/cudnn/*) use the v0.x / v8 backend
descriptor API and are out of scope per §1 "Not". The v9 surface that
MHA.cpp actually exercises is narrow: SDPA forward and backward; a small
set of Graph lifecycle, Tensor_attributes, SDPA_attributes, and
SDPA_backward_attributes methods; a handful of DataType_t values; and
HeurMode_t::A. PyTorch also uses the UID-keyed variant pack throughout —
the simplest path, though per §4.4.1 the shim needs no special tensor-identity
handling for either keying style.
For the precise symbol-by-symbol table with line-number citations, plus the explicit list of cuDNN FE features PyTorch does not use, see Supporting Reference §3.
The shim ships the full v9 surface so hipified source compiles, and fails
loudly at use (build/validate time, §4.4.2), with hipDNN node work
prioritized separately. Waiting for full engine support would block
otherwise valid use cases, and adding the frontend surface without engines
would become a hidden runtime error either way — surfacing it as an
explicit, documented GRAPH_NOT_SUPPORTED (pointing at a GitHub issue) is
strictly better than a silent gap.
4.5 Heuristics and plan selection
v9 exposes plan selection and filtering as methods directly on Graph:
create_execution_plans(vector<HeurMode_t>), check_support,
build_plans, get_execution_plan_count, the select_* / deselect_*
notes filters, the deselect_workspace_greater_than /
deselect_shared_mem_greater_than resource caps, and the
get_behavior_notes* introspection methods. There is no user-facing
Plans class (an internal Execution_plan_list holds the data but is
not part of the API).
hipDNN's current selection model (RFC 0007) is engine-knob driven and does not present heuristic modes. Some of this surface is being filled in by the hipDNN auto-tuning work (PR #7217), which the shim should build on as it lands.
HeurMode. Accept all HeurMode_t values; until real hipDNN heuristics
exist, map every mode (A, B, FALLBACK, OPENSOURCE) to hipDNN's
fallback/default selection and log a WARN on first use per process so a
user debugging a perf regression sees that their heuristic choice was not
honored. As hipDNN heuristics arrive, remap the modes properly. The
benchmarking and auto-tuning paths are explicit user choices and are handled
separately.
Notes — per-note triage, not blanket no-op. A blanket "accept and
ignore" is unsafe: silently running without a requested filter can be
wrong, not merely slower. Each NumericalNote_t / BehaviorNote_t is
triaged into one of three handlings:
- Map to engine capability — when hipDNN can honor the constraint through its engine/knob system. The deterministic ("non-deterministic" filter) case is the worked example: now that the note system exists, hipDNN can likely express it, and the shim should wire it through.
- Warn and ignore — for notes that are advisory and safe to drop (logged at WARN on first use).
- Error — for correctness-critical notes hipDNN cannot honor. If a user requests deterministic behavior and the shim cannot guarantee it, it is wrong to run anyway; the shim must error rather than silently produce non-deterministic results. (Recorded-error mechanism, §4.4.2.)
The per-note classification table lives in Supporting Reference §4 and must be reviewed note-by-note before Phase 4 lands.
Resource caps. The workspace-size cap is enforced post-hoc against
hipDNN's existing get_workspace_size() (and the workspace knobs need a
concrete mapping plan — Reference §4). The shared-memory cap depends on
per-plan shared-memory metadata hipDNN does not yet expose; until it does,
the shim rejects a non-zero deselect_shared_mem_greater_than rather
than silently ignoring it (Reference §4).
For the full upstream method enumeration (with graph_interface.h line
numbers), behaviour table, and the open question about hipDNN-side
metadata extensions, see
Supporting Reference §4.
4.6 Error handling and logging
Per the aliasing-first strategy (§4.3.1), the preferred outcome is to make
hipdnn_frontend's error type exactly 1:1 with cuDNN FE's and alias it,
rather than ship a wrapper. cuDNN FE's error_object exposes public fields
code and err_msg and methods get_code, get_message, is_good,
is_bad, operator==/!=, with error_t a typedef for it; hipDNN's Error
is structurally the same. The one gap is the error_code_t enum: cuDNN FE
has 16 values, a few of which (e.g. NVRTC_COMPILATION_FAILED,
INVALID_CUDA_DEVICE, CUDNN_BACKEND_API_FAILED) have no hipDNN
counterpart. The missing values are added to hipDNN's error-code enum so
the whole error type is aliasable rather than wrapped. The cuDNN FE
error/log macros
(CHECK_CUDNN_FRONTEND_ERROR, RETURN_CUDNN_FRONTEND_ERROR_IF,
CUDNN_FE_LOG*) are also re-exported, since PyTorch's
AT_CUDNN_FRONTEND_CHECK expands to them.
Logging in cuDNN FE is off by default (controlled by the integer env
var CUDNN_FRONTEND_LOG_INFO; output target via CUDNN_FRONTEND_LOG_FILE;
compile-time disable via NV_CUDNN_FRONTEND_DISABLE_LOGGING). The shim
re-exports these names and delegates to hipdnn_frontend::Logging,
preserving the off-by-default behaviour. Concretely, the shim reads the
cuDNN FE env vars and forwards them into the equivalent hipDNN Logging API
(level and output target), rather than only re-exporting the names — so
setting CUDNN_FRONTEND_LOG_INFO actually drives hipDNN's logger.
(Alternatively, hipDNN's own env-var handling could be extended to recognize
the cuDNN names directly; the forward-into-API approach is preferred as it
keeps the bridge in the shim.)
For the verified error_object structure, the full macro list, and the
detailed logging API surface (with cudnn_frontend_Logging.h references),
see
Supporting Reference §5.
4.7 Compatibility scope (and the <cudnn.h> / v0.x problem)
The shim provides source-level compatibility for cuDNN frontend v9 TUs only; ABI compatibility with cuDNN-compiled binaries is out of scope and consumers must recompile.
Upstream cudnn_frontend.h unconditionally #includes <cudnn.h> and 14
v0.x/v8 backend-descriptor headers, and exposes the v0.x class names
(ConvDesc, ConvDescBuilder, EngineHeuristics, Operation, Tensor,
...) via using aliases in namespace cudnn_frontend. Any consumer that
includes the upstream umbrella gets these names whether they use them
or not.
Decision (per §1): the shim's cudnn_frontend.h is a hand-curated
v9-only umbrella. It does not mirror upstream's include pattern, does
not pull in any v0.x descriptor header, and does not declare the v0.x
classes or using aliases. The stub cudnn.h it ships covers the C-API
types that the v9 graph API surface refers to in its own method
signatures:
cudnnHandle_t(used byGraph::execute,build_operation_graph,check_support, etc.), declared as an alias:using cudnnHandle_t = hipdnn_frontend::hipdnnHandle_t;cudnnStatus_tcudnnDataType_t,cudnnTensorFormat_t,cudnnConvolutionMode_t,cudnnReduceTensorOp_t,cudnnNormFwdPhase_t,cudnnBackendHeurMode_t,cudnnBackendNumericalNote_t,cudnnBackendBehaviorNote_t,cudnnBackendDescriptorType_t
The stub also ships a small set of C-API entry points. These are used outside the frontend — for handle init, stream binding, error handling, and version checks — and are needed to build and run the cuDNN-mirrored samples (§8.3):
- Handle lifecycle:
cudnnCreate,cudnnDestroy,cudnnSetStream,cudnnGetStream— forwarding to the corresponding hipDNN handle API. cudnnGetErrorString— forwarding to hipDNN's error-string lookup.cudnnGetVersion— returning the version the shim claims compatibility with (§4.8).
cuDNN FE also provides a create_cudnn_handle() helper that calls directly
into the handle API; the shim re-exports an equivalent so sample code that
uses it works unchanged. (Implementation note / open item: confirm which of
these hipDNN C-API entry points already exist to forward to, and add any
that are missing. The full cuDNN C library beyond this set — convolution
descriptor APIs, etc. — remains out of scope.)
If, during implementation, the v9 graph API surface is found to refer to
a v0.x C++ type by name (e.g., cudnn_frontend::Tensor as used by some
internal-but-public INode signatures), the shim will expose only that
type, scoped to make v9 TUs compile. Where that legacy name was simply
reused by the new API and corresponds to a v9 type the shim already
supports, alias it to the matching hipDNN type (per §4.3.1) rather than
stubbing it; in practice Tensor is the likely — possibly only — case. The
full v0.x C++ surface (TensorBuilder, OperationBuilder,
EngineConfigGenerator, ExecutionPlanBuilder, VariantPackBuilder,
EngineHeuristicsBuilder, EngineFallbackListBuilder,
EngineConfigBuilder, ConvDescBuilder, MatMulDescBuilder,
PointWiseDescBuilder, ResampleDescBuilder, ReductionDescBuilder,
the *_v8 types, the free functions filter, hasNumericalNote<>,
time_sorted_plan<>, check_errata, load_from_config, and
cudnnException) is out of scope for this RFC.
Consequence: source files that use v0.x types are not buildable against
this shim. The most prominent affected consumers are PyTorch's
aten/src/ATen/native/cudnn/Conv_v8.cpp and
aten/src/ATen/native/quantized/cudnn/* — both must be excluded from
the build (or rewritten to v9) for any tree that hipifies against this
shim. The v9 PyTorch consumer aten/src/ATen/native/cudnn/MHA.cpp is
fully supported (see §4.4.3, §7.5).
A follow-up RFC for a v0.x compile-only stub layer (or a fuller v0.x shim) is enumerated in §9 as deferred future work.
For the verbatim upstream cudnn_frontend.h include list, the
enumeration of v0.x aliases, and the considered-but-deferred options for
v0.x source compatibility, see
Supporting Reference §6.
4.8 Versioning
The shim is part of hipdnn_frontend's versioning surface (see RFC 0005).
That implies:
- Its public headers live under
frontend/include/hipdnn_compatibility/cudnn/and are versioned together withhipdnn_frontend(singleversion.json). - Within a major version of
hipdnn_frontend, the shim's surface area may only grow (new node wrappers, new enum values mapped) — never shrink or change signatures. - The shim's public symbols will additionally expose the upstream-style
version macros (
CUDNN_FRONTEND_MAJOR_VERSION=1,CUDNN_FRONTEND_MINOR_VERSION=24,CUDNN_FRONTEND_PATCH_VERSION=0,CUDNN_FRONTEND_VERSION=12400) declaring which cuDNN FE version this shim claims source compatibility with. Matching upstream is important because PyTorch'sMHA.cppactually gates on these (#if CUDNN_FRONTEND_VERSION <= 11200chooses betweenset_is_inferenceandset_generate_stats). The upstream values are ininclude/cudnn_frontend_version.h. This is independent of hipDNN's own version.
4.9 Explored Alternatives
- hipDNN backend for PyTorch: Pros: Cleanest solution. Cons: Maintenance burden of an additional backend.
- Compile-time
#ifdef'd backend for cuDNN → hipDNN: Pros: Shortest path to success, could have something working very quickly. Cons: The code is deviated from cuDNN enough that maintenance and code quality is challenging. - hipify build-time generation: Pros: integrates with existing systems in some cases. Cons: The conversion path is not smooth, and the debugging experience is bad.
- AI-based conversion tools: Pros: Fits in with our current direction at AMD. Cons: Does not exist. At all.
- Re-implement cuDNN FE on top of hipDNN backend C API directly (skipping
hipdnn_frontend): Pros: avoids the wrapper-of-a-wrapper overhead; could match cuDNN signatures exactly. Cons: duplicates a lot of work already done inhipdnn_frontend; we lose any future improvements tohipdnn_frontendfor free.
Note: once the shim's aliases and wrappers exist, a direct (non-hipify) source-conversion path for the supported API becomes nearly free — the mapping is already encoded in the shim. This does not compete with the hipify path; the two can coexist, and offering both is a reasonable future option for consumers who prefer one workflow over the other.
5. Key Design Decisions
5.1 Header file extension and naming
Decision: Use .h extensions, matching cuDNN frontend
convention. Match cuDNN filenames precisely.
5.2 Forwarding strategy
Decision: Aliasing is the default; composition is the fallback for
types that are not 1:1 (§4.3.1). Composition is used for the shim's Graph,
which contains a hipdnn_frontend::graph::Graph by value rather than
inheriting from or aliasing it.
Rationale: cuDNN FE's Graph has a different surface area than hipDNN's
(plan filtering, notes, knobs, heuristics — different method names, return
types, and validation flow), so it cannot be aliased. Composition gives
complete control over that surface and avoids accidentally exposing hipDNN
methods through the cuDNN-shaped class. For the many other types that are
already 1:1 (most enums, error_t, and the matching *_attributes
classes), no wrapper is written at all — they are aliased, which is cheaper
and removes per-call overhead.
Drawbacks: The composition path is slightly more verbose — each exposed method must be forwarded explicitly — but it applies to only the small set of genuinely non-matching types rather than the whole surface.
5.3 Tensor identity — rely on hipDNN's native UID handling
Decision: No shim-side UID allocator and no shim-side identity map.
hipdnn_frontend already matches cuDNN FE's identity semantics — set UIDs
are honored, unset UIDs are auto-assigned uniquely at build time, duplicates
fail at build, and a shared_ptr-keyed execute() overload already lowers
to the UID-indexed variant pack internally (see §4.4.1 for the verified
Graph.hpp references). The shim simply forwards.
Rationale: hipdnn_frontend::graph::Graph already provides
cuDNN-equivalent identity natively, so a shim-side UID allocator or
shared_ptr<Tensor_attributes> → UID map would be redundant.
Drawbacks: None — there is no per-execute map lookup and no collision semantics for the shim to define.
5.4 Failure mode for unimplemented cuDNN features
Runtime
Favor Error::NOT_SUPPORTED by default, with optional compile-time static_assert
mode if required.
6. Risks
6.1 cuDNN FE surface drift
Risk: cuDNN FE continues to evolve. New nodes, new enum values, and new attribute setters appear in each release. If we pin to v9.0 and PyTorch upgrades to a newer point release, hipified PyTorch may stop compiling.
Mitigation:
- Pinned to cuDNN FE v1.24.0 (see §2). When upstream releases a new
version, the shim does not automatically follow — a manual triage step
evaluates whether to rebase and which new symbols to wrap. New upstream
nodes appear as
error_code_t::GRAPH_NOT_SUPPORTEDat runtime until the wrapper is added. - CI job that, on a schedule, attempts to build a small set of upstream cuDNN FE samples against the shim and reports diffs.
- Establish a quarterly cadence for evaluating upstream changes and triaging them.
6.2 Performance overhead of the wrapper layer
Risk: Even thin wrappers can add measurable overhead in graph-construction-heavy workloads (e.g., PyTorch eager mode building a fresh graph per kernel launch).
Mitigation:
- Prefer aliasing (§4.3.1): aliased types add zero overhead — they are the hipDNN type. This keeps most of the surface out of the perf budget, and the shim carries no per-execute tensor-identity map (§4.4.1, §5.3).
- All forwarding methods on the residual composition wrappers
inlineand header-only. - Benchmark
Graph::build()andGraph::execute()overhead against nativehipdnn_frontendcalls; gate merges on no-regression. - Avoid heap allocations in the hot path of the composition
Graphwrapper.
6.3 Silent behavioral divergence
Risk: Two calls — cudnn_frontend::graph::Graph::build_plans(..., HeurMode_t::A) and the shim's equivalent — return success, but the chosen
plan is materially different (different precision, different numerical
behavior). The consumer's accuracy tests may fail in subtle ways.
Mitigation:
- Log every mapped-but-not-honored heuristic / mode at
WARNlevel on first use per process. - Document the divergences exhaustively in the shim's Doxygen and in a
KNOWN_DIVERGENCES.md. - Add a sample / test that drives
Graph::build_planswith everyHeurMode_tvalue and snapshot-tests the chosen plan's engine ID.
6.4 License / attribution
Risk: To preserve cuDNN FE's exact signatures and Doxygen, we may end up copying substantial chunks of comments / enum value names from NVIDIA's MIT-licensed source. Without correct attribution this is a license violation.
Mitigation:
- File-level header in every shim source file: AMD copyright + "Portions derived from NVIDIA cuDNN frontend, used under the MIT license", with the upstream copyright notice preserved.
- Legal review before initial merge.
6.5 Maintenance burden
Risk: The shim is a long-term ongoing commitment. Each hipDNN node added must also be wrapped; each cuDNN FE release must be evaluated; each PyTorch upgrade tests the shim's adequacy.
Mitigation:
- Aliasing-first (§4.3.1) keeps the hand-written surface small: every 1:1
type is a one-line
usingthat needs no per-release maintenance, so the ongoing burden concentrates on the few composition wrappers and the error-stubs. - Owner: the hipDNN team (collective), with Mitch Ousdahl leading
the initial implementation. The shim is treated as a first-class
component of
hipdnn_frontend, not a side project. - Tooling: a generator/macro that takes a cuDNN FE signature and emits a
Tier-2 fail-stub (returns
GRAPH_NOT_SUPPORTED, points the user at a GitHub issue) for any newly-added upstream symbol we don't yet support, reducing per-node boilerplate. - Out-of-tree consumer integration tests run on PR.
6.6 Encouraging hipDNN-native adoption is undermined
Risk: The shim is so convenient that no downstream project ever moves to the hipDNN-native API, leaving us perpetually playing catch-up with cuDNN FE.
Mitigation: Accept this. The stated goal is adoption, not API purity. A hipified-but-running PyTorch is a much better outcome than a clean hipDNN-native PyTorch that ships in 2027.
7. Execution Plan
7.1 Phase 1 — Foundations (compiles, builds, has CI)
- Add
projects/hipdnn/frontend/include/hipdnn_compatibility/cudnn/to the source tree. - Stand up
cudnn_frontend.humbrella header (empty but installable). - Add CMake target
hipdnn_cudnn_shim(or integrate intohipdnn_frontendas an additional include directory). - Implement
detail/type_mapping.hfor the core enums (cudnnStatus_t,cudnnDataType_t,cudnnTensorFormat_t,cudnnPointwiseMode_t,cudnnReduceTensorOp_t). - Implement
detail/status_translation.handerror_object. - Ship the stub
cudnn.h(per §4.7) covering the C-API types referenced by the v9 graph API (cudnnHandle_t,cudnnStatus_t,cudnnDataType_t,cudnnTensorFormat_t,cudnnConvolutionMode_t,cudnnReduceTensorOp_t,cudnnNormFwdPhase_t,cudnnBackendHeurMode_t,cudnnBackendNumericalNote_t,cudnnBackendBehaviorNote_t,cudnnBackendDescriptorType_t) and the small set of C entry points needed for init / error handling / version checks and for building the mirrored samples:cudnnCreate,cudnnDestroy,cudnnSetStream,cudnnGetStream,cudnnGetErrorString,cudnnGetVersion, and acreate_cudnn_handle()helper (each forwarding to the hipDNN equivalent — see §4.7). The remainder of the cuDNN C library (convolution descriptor APIs, etc.) stays out of scope. - Unit tests for the type mapping and status translation (round-trip every enum value).
Exit criterion: a v9-only translation unit — a hand-written test file
that uses only cudnn_frontend::graph::* and the C-API types listed
above — compiles cleanly against the shim's cudnn_frontend.h umbrella.
Source that mixes v9 and v0.x types is out of scope (§4.7, §9).
7.2 Phase 2 — Tensor and Graph skeleton
- Implement
Tensor_attributeswrapper (all setters, includingset_dim,set_stride,set_data_type,set_uid,set_is_virtual(graph_properties.h:327),set_is_pass_by_value(:348; note theis_prefix),set_output(:333, which is!set_is_virtual),set_ragged_offset(:446),set_name). - Implement
Graphwrapper: constructors,set_io_data_type,set_compute_data_type,set_intermediate_data_type,set_name,validate,build_operation_graph,create_execution_plans,check_support(both overloads),build_plans(both overloads),build_plan_at_index,get_execution_plan_count, the conveniencebuild()one-shot (validate → build_operation_graph → create_execution_plans → check_support → build_plans; most upstream samples use it), all fourexecuteoverloads (shared_ptr-keyed pack; uid-keyed pack; uid-keyed-with-shape-overrides; flat-pointer-array form),get_workspace_size,serialize,deserialize. - Implement the graph-level configuration setters per the §4.4 triage table:
set_override_shape_enabledforwards (hipDNN supports it);set_dynamic_shape_enabledforwards if supported, else no-op + debug log;set_kernel_cachelogs and ignores;set_sm_countandset_device_propertieserror (via the recorded-error mechanism, §4.4.2);set_sm_versionis TBD pending investigation. - Add
Graph::tensor()returningstd::shared_ptr<Tensor_attributes>, including the scalar convenience overloads (tensor(int64_t),tensor(float), …). - No tensor-identity map or UID allocator is needed — hipDNN handles UID assignment natively (§4.4.1).
- Unit tests covering: building an empty graph, an invalid graph, serializing and deserializing the empty graph.
Exit criteria: A cuDNN FE sample that constructs a graph with a single tensor and round-trips it through serialize/deserialize works.
7.3 Phase 3 — Node coverage, priority order
Priority is driven by the verified consumer survey in §4.4.3. PyTorch's
only v9 graph-API consumer is MHA.cpp, which exercises SDPA fwd/bwd and
nothing else. The remaining nodes have no current PyTorch v9 consumer; their
order is driven by future PyTorch conversions of ops that today still use
the v8 backend descriptor API and by any other downstream consumer that
materialises a concrete ask.
Tier 1 — PyTorch SDPA unblocked (Phase 3a, minimum-viable shim):
SDPA_attributes(full setter surface used by PyTorch — see §4.4.3 table). Including the[[deprecated]]set_is_inference()(since PyTorch still emits it onCUDNN_FRONTEND_VERSION <= 11200paths) and the float and shared_ptr overloads ofset_attn_scale.Graph::sdpa(shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, SDPA_attributes) -> std::array<shared_ptr<Tensor_attributes>, 2>(returns{O, Stats}, verified atgraph_interface.h:1857)SDPA_backward_attributes(full setter surface used by PyTorch)Graph::sdpa_backward(shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, shared_ptr<Tensor_attributes>, SDPA_backward_attributes) -> std::array<shared_ptr<Tensor_attributes>, 3>(returns{dQ, dK, dV}, verified at:1923)- Tensor-attribute features required by MHA but not yet noted elsewhere:
set_ragged_offset,set_is_pass_by_value(these areTensor_attributessetters, not new nodes, but they must work for the SDPA path).
Exit criteria for Phase 3a: a hipified copy of PyTorch's MHA.cpp
compiles unchanged against the shim, and the four PyTorch entry points
(run_cudnn_SDP_fprop, run_cudnn_SDP_fprop_nestedtensor,
run_cudnn_SDP_bprop, run_cudnn_SDP_bprop_nestedtensor) execute end-to-end
on hipDNN's SDPA provider.
Tier 2 — Remaining nodes (Phase 3b):
The remaining v9 nodes with a hipDNN equivalent (Convolution
fprop/dgrad/wgrad, BatchNorm forward/backward/inference, LayerNorm,
RMSNorm fwd/bwd, Matmul, Pointwise, Reduction, ResampleFwd,
BlockScaleQuantize/Dequantize, CustomOp) are implemented in
consumer-driven order. Each node lands when a real consumer asks for
it. Until then, the Graph::* method and attribute class are declared
(so source compiles) but return error_code_t::GRAPH_NOT_SUPPORTED per
§4.4.2.
For each node, the deliverable is: attribute wrapper, Graph::* method,
unit tests building/validating/executing the node, and a sample mirroring
the corresponding cuDNN FE sample.
7.4 Phase 4 — Heuristics surface, logging, and polish
- Implement
HeurMode_t,NumericalNote_t,BehaviorNote_tenum mapping. - Wire the plan-filter methods on the shim's
Graph, applying the per-note triage (§4.5; classification table in Supporting Reference §4):select_*/deselect_*notes — map to engine capability where hipDNN can honor it, warn-and-ignore where advisory, error where correctness-critical and unsupported (e.g. deterministic). No blanket no-op.deselect_workspace_greater_than— filter hipDNN plans by workspace size beforebuild_plans()(hipDNN'sget_workspace_size()is sufficient).deselect_shared_mem_greater_than— reject a non-zero value until hipDNN exposes per-plan shared-memory metadata (see Supporting Reference §4).
- Wire the plan/engine introspection surface (Reference §1):
build(),get_engine_count,get_knobs_for_engine,create_execution_plan(engine_id, knobs),get_plan_name/get_plan_name_at_index,get_workspace_size_plan_at_index,execute_plan_at_index, anddeselect_engines— forwarding to hipDNN where supported (much of this arrives with auto-tuning PR #7217), error-wrapper otherwise. autotune/warmup— forward to hipDNN's auto-tuning path (PR #7217).populate_cuda_graph/update_cuda_graph— Tier-2 error-wrappers (no in-scope HIP-graph capture analogue).- Implement environment-variable bridge for cuDNN FE logging vars (forward the cuDNN env vars into hipDNN's Logging API — §4.6).
KNOWN_DIVERGENCES.mddocument.- Performance benchmarks vs. native
hipdnn_frontend.
7.5 Phase 5 — Real-world validation
- Hipify a small known-good cuDNN FE project (suggestion:
cuDNN FE's own
samples/cpp/test programs) and verify they build & run. - Hipify and run a vertical slice of PyTorch's cuDNN-using ops.
The verified PyTorch target slice (per §4.4.3) is the four entry points in
aten/src/ATen/native/cudnn/MHA.cpp:
run_cudnn_SDP_fproprun_cudnn_SDP_fprop_nestedtensorrun_cudnn_SDP_bproprun_cudnn_SDP_bprop_nestedtensor
These cover dense and nested-tensor SDPA, forward and backward, with optional causal masking, padding masking via ragged sequence lengths, dropout, and additive attention bias. Validation:
- Hipify
MHA.cppand rebuild PyTorch's_cudnn_attention_*ops against the shim. - Run
test_transformers.pySDPA paths with the cuDNN backend forced (SDPBackend.CUDNN_ATTENTION) on ROCm. - Compare numerical results against the math-reference SDPA backend within the test's existing tolerances.
PyTorch's cudnn_convolution and quantized cuDNN ops both still consume
the v8 backend descriptor API and are out of scope for this RFC (§4.7,
§9). For the validation build, these files must be excluded from the
PyTorch build (or rewritten to v9 first).
7.6 Documentation
- Doxygen on every public shim symbol, including the link to its cuDNN FE upstream counterpart.
docs/cudnn-shim.mduser guide covering: installation, the hipification workflow, known divergences, known unsupported nodes, troubleshooting.- Inline sample showing the textual-hipify transformation on a small cuDNN FE program.
8. Testing Plan
8.1 Unit tests
Location: projects/hipdnn/frontend/tests/hipdnn_compatibility/cudnn/. Same
gtest framework as the rest of the frontend tests; same naming convention
(TestCudnnShim*.cpp, suites prefixed TestCudnnShim*).
TestCudnnShimTypeMapping— round-trip every mappable enum value in both directions; assert unmappable values produce the documented error.TestCudnnShimStatusTranslation— every cuDNN status code maps to a hipDNN one and back.TestCudnnShimHandle— handle lifecycle, stream binding.TestCudnnShimTensor—Tensor_attributesconstruction, all setters, UID assignment, identity preservation across copies.TestCudnnShimGraph<NodeName>— one suite per supported node, covering: construction, validation (good and bad), serialize/deserialize round-trip, variant-pack execution against the in-treefake_backend(seeprojects/hipdnn/frontend/tests/fake_backend/).TestCudnnShimError— error_object construction, code mapping, message preservation throughhipdnn_frontend::Error.
8.2 Integration tests (AMD-authored)
Location: projects/hipdnn/samples/cudnn_shim/ (or under
projects/hipdnn/samples/ next to the existing convolution, sdpa, etc.
samples).
These are AMD-authored integration tests, distinct from the upstream-sample compatibility check in §8.3 (which compiles NVIDIA's own sources). They should resemble the existing hipDNN frontend integration tests — driving the shim end-to-end against the in-tree test plugins / providers — so they exercise our wrapper/alias layer specifically rather than re-proving upstream's samples.
- Per-node test driving the shim API, built and run against a real provider
(miopen-provider or hipblaslt-provider as applicable) or the in-tree
fake_backend. - "Full pipeline" test: build → validate → check_support → build_plans → execute against real tensor data, asserting numerical correctness against a hipDNN-native reference.
8.3 Compatibility / "build the cuDNN FE samples" test
This is the source-compatibility proof and is deliberately separate
from the AMD-authored integration tests in §8.2: here we compile and run
NVIDIA's unmodified sources (only the includes repointed) to demonstrate
the shim is genuinely drop-in. The strongest evidence of source
compatibility is that NVIDIA's own samples/cpp/ programs from the pinned
cuDNN FE version build and pass when their includes are repointed at the
shim.
- Vendor (as a git submodule or fetched at configure time) a pinned cuDNN FE tag.
- CI job that builds those samples against the shim.
- For samples whose dependencies cannot be satisfied (e.g., samples that rely on cuBLAS), document the skip and the reason.
8.4 Performance / overhead testing
- Microbenchmarks comparing native
hipdnn_frontendGraph construction + execute vs. the shim equivalent for a handful of representative graphs. - Gate (from §2): CI fails when the shim adds more than 1 % to
build()time or more than 1 µs perexecute()call relative to the nativehipdnn_frontendbaseline, measured on the same graphs. - Baselines re-captured on every hipDNN minor-version bump.
8.5 Install / package testing
- The shim headers must be installed by the
DevelopmentCMake component alongsidehipdnn_frontendheaders. - Add a
find_package(hipdnn_frontend)-using downstream CMake project underprojects/hipdnn/tests/install/(or wherever the existing install tests live) that#includes the shim umbrella header and compiles a trivial Graph.
Python bindings are out of scope (§1, §9); a hipDNN benchmarking/bindings project would be the natural host for shim-level perf or Python-side testing if such bindings are ever built.
8.6 PyTorch end-to-end smoke test
A small set of PyTorch tests run on a nightly schedule against a PyTorch
build whose aten/src/ATen/native/cudnn/MHA.cpp has been hipified to use
the shim. Concretely: the test_transformers.py SDPA paths forced to
the cuDNN backend (SDPBackend.CUDNN_ATTENTION).
⚠️ OPEN QUESTION: Hosting and ownership of this CI job. ROCm PyTorch
testing infrastructure already exists; we need to confirm whether (a) the
shim's CI is added there, (b) a hipDNN-side CI host is built that pulls
PyTorch in, or (c) the test is run on-demand by the shim's named owners
(see §6.5) at each shim release. Recommend a focused operational review
(/rfc-review-ops) before this is finalized.
9. Future Considerations
- Python bindings (out of scope, possible future RFC): cuDNN frontend ships Python bindings; this RFC is C++-only (§1). A Python-binding compatibility layer — if a consumer materializes a concrete ask — would be a separate RFC, and a hipDNN benchmarking/bindings project would be its natural home (see §8).
- v0.x compile-only stub layer (deferred): a follow-up RFC covering
compile-only stubs for the upstream v0.x / v8 C++ surface
(
TensorBuilder,OperationBuilder,EngineConfigGenerator,ExecutionPlanBuilder,VariantPackBuilder,EngineHeuristicsBuilder,EngineFallbackListBuilder,EngineConfigBuilder,ConvDescBuilder,MatMulDescBuilder,PointWiseDescBuilder,ResampleDescBuilder,ReductionDescBuilder, plus the*_v8types, the free functionsfilter,hasNumericalNote<>,time_sorted_plan<>,check_errata,load_from_config, andcudnnException). Stubbed methods returnerror_code_t::GRAPH_NOT_SUPPORTEDat runtime. This unblocks consumers that mix v9 and v0.x in the same translation unit (PyTorch'sConv_v8.cpp, the quantized cuDNN ops). Triggered by a concrete consumer ask; scope estimate (PyTorch alone touches 13+ builder classes plus several free functions) to be filled in at that RFC's authoring time. Because v0.x types live in the same upstreamcudnn_frontendnamespace and ship from headers adjacent to the v9 ones, the follow-up is purely additive to this shim — new headers in the existinghipdnn_compatibility/cudnn/directory and new declarations in<shim_ns>and<shim_ns>::detail, with no rename or move of v9 symbols. The path layout in §4.1 was chosen with that in mind. See §4.7 and Supporting Reference §6 for the underlying constraint and the options considered.