CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
457 lines
15 KiB
C++
457 lines
15 KiB
C++
// Copyright © Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
/**
|
|
* @file Attributes.hpp
|
|
* @brief Base class for operation attribute classes using CRTP
|
|
*
|
|
* This file defines the Attributes template base class that provides
|
|
* common functionality for all operation attribute classes. It uses
|
|
* the Curiously Recurring Template Pattern (CRTP) to enable method
|
|
* chaining in derived classes.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "GraphAttributes.hpp"
|
|
#include "TensorAttributes.hpp"
|
|
#include <hipdnn_frontend/Error.hpp>
|
|
#include <hipdnn_frontend/Types.hpp>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace hipdnn_frontend::graph
|
|
{
|
|
|
|
/**
|
|
* @class Attributes
|
|
* @brief CRTP base class for operation attribute classes
|
|
* @tparam DerivedT The derived attribute class type
|
|
*
|
|
* This template class provides common functionality shared by all
|
|
* operation attribute classes:
|
|
* - Name management for debugging and logging
|
|
* - Compute data type configuration
|
|
* - Automatic tensor property propagation from graph context
|
|
*
|
|
* Derived classes must define `inputs` and `outputs` maps with
|
|
* TensorAttributes values. These maps are used by fill_from_context()
|
|
* to propagate graph-level settings to tensors.
|
|
*
|
|
* @code{.cpp}
|
|
* class MyOperationAttributes : public Attributes<MyOperationAttributes>
|
|
* {
|
|
* public:
|
|
* std::unordered_map<InputName, std::shared_ptr<TensorAttributes>> inputs;
|
|
* std::unordered_map<OutputName, std::shared_ptr<TensorAttributes>> outputs;
|
|
* };
|
|
* @endcode
|
|
*/
|
|
template <typename DerivedT>
|
|
class Attributes
|
|
{
|
|
friend DerivedT;
|
|
|
|
private:
|
|
/**
|
|
* @brief Get mutable reference to derived class
|
|
* @return Reference to *this cast to DerivedT&
|
|
*/
|
|
DerivedT& self()
|
|
{
|
|
return static_cast<DerivedT&>(*this);
|
|
}
|
|
|
|
/**
|
|
* @brief Get const reference to derived class
|
|
* @return Reference to *this cast to const DerivedT&
|
|
*/
|
|
const DerivedT& self() const
|
|
{
|
|
return static_cast<const DerivedT&>(*this);
|
|
}
|
|
|
|
public:
|
|
std::string name; ///< Operation name for debugging
|
|
DataType compute_data_type = DataType::NOT_SET; ///< Compute/accumulation data type (NOLINT)
|
|
|
|
// First-wins latch for source-compatibility setters that have no hipDNN
|
|
// equivalent; set via recordUnsupported(), read via hasUnsupportedUsage() /
|
|
// getUnsupportedReason(), and surfaced by the owning node at validate().
|
|
// Public (not private) only because the attribute types are C++17 aggregates
|
|
// brace-initialized in non-friend contexts, which a non-public base member
|
|
// would break; prefer the accessors over touching it directly.
|
|
std::optional<std::string> unsupported_reason; // NOLINT(readability-identifier-naming)
|
|
|
|
/**
|
|
* @brief Set the operation name
|
|
* @param nameValue The name to assign
|
|
* @return Reference to derived class for method chaining
|
|
*/
|
|
DerivedT& set_name(const std::string& nameValue) // NOLINT(readability-identifier-naming)
|
|
{
|
|
name = nameValue;
|
|
return self();
|
|
}
|
|
|
|
/**
|
|
* @brief Get the operation name
|
|
* @return The current operation name
|
|
*/
|
|
const std::string& get_name() const // NOLINT(readability-identifier-naming)
|
|
{
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the compute data type for this operation
|
|
* @param value The data type to use for computation/accumulation
|
|
* @return Reference to derived class for method chaining
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
DerivedT& set_compute_data_type(DataType value)
|
|
{
|
|
compute_data_type = value;
|
|
return self();
|
|
}
|
|
|
|
/**
|
|
* @brief Get the compute data type
|
|
* @return The current compute data type
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
DataType get_compute_data_type() const
|
|
{
|
|
return compute_data_type;
|
|
}
|
|
|
|
/**
|
|
* @brief Whether a source-compatibility setter recorded an unsupported request
|
|
* @return true if a setter with no hipDNN equivalent was invoked
|
|
*
|
|
* Setters that accept a foreign (e.g. cuDNN) spelling with no hipDNN
|
|
* behavior record the request instead of failing at the call site; the
|
|
* owning node surfaces it as an error at validate().
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
bool hasUnsupportedUsage() const
|
|
{
|
|
return unsupported_reason.has_value();
|
|
}
|
|
|
|
/**
|
|
* @brief The reason recorded by the first unsupported setter, if any
|
|
* @return The recorded message, or an empty string when none was recorded
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
const std::string& getUnsupportedReason() const
|
|
{
|
|
static const std::string s_empty;
|
|
return unsupported_reason ? *unsupported_reason : s_empty;
|
|
}
|
|
|
|
/**
|
|
* @brief Fill unset properties from graph context
|
|
* @param graphAttributes The graph attributes to copy from
|
|
* @return Error indicating success or failure
|
|
*
|
|
* Propagates graph-level settings to all input and output tensors
|
|
* that don't have explicit values set. Also sets the compute data
|
|
* type if not already specified.
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
Error fill_from_context(const GraphAttributes& graphAttributes)
|
|
{
|
|
for(auto& [_, tensor] : self().inputs)
|
|
{
|
|
if(tensor)
|
|
{
|
|
tensor->fill_from_context(graphAttributes);
|
|
}
|
|
}
|
|
|
|
for(auto& [_, tensor] : self().outputs)
|
|
{
|
|
if(tensor)
|
|
{
|
|
tensor->fill_from_context(graphAttributes);
|
|
}
|
|
}
|
|
|
|
if(get_compute_data_type() == DataType::NOT_SET)
|
|
{
|
|
set_compute_data_type(graphAttributes.get_compute_data_type());
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* @brief High-level structural comparison of operations using CRTP.
|
|
* Compares core attributes common to all nodes, then delegates specific
|
|
* property evaluation to the derived node subclass.
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
bool logicallyEquals(const Attributes<DerivedT>& other) const
|
|
{
|
|
// Core mathematical metadata configuration must match across all nodes
|
|
if(this->compute_data_type != other.compute_data_type)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Core Map logical validation handled here natively
|
|
if(!compareMapsLogical(self().inputs, other.self().inputs)
|
|
|| !compareMapsLogical(self().outputs, other.self().outputs))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Delegate ONLY custom extended properties to the derived hook
|
|
return self().logicallyEqualsImpl(other.self());
|
|
}
|
|
|
|
/**
|
|
* @brief Global strict equality operator inside Attributes.hpp base class.
|
|
* Handles layout validation for ALL derived classes dynamically without redundancy.
|
|
*/
|
|
friend bool operator==(const Attributes<DerivedT>& lhs, const Attributes<DerivedT>& rhs)
|
|
{
|
|
// Cast down to the actual concrete type
|
|
const auto& derivedLhs = static_cast<const DerivedT&>(lhs);
|
|
const auto& derivedRhs = static_cast<const DerivedT&>(rhs);
|
|
|
|
// Check basic non-tensor base properties
|
|
if(lhs.compute_data_type != rhs.compute_data_type || lhs.name != rhs.name)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const Attributes<DerivedT>& baseLhsView = derivedLhs;
|
|
const Attributes<DerivedT>& baseRhsView = derivedRhs;
|
|
|
|
if(!compareMapsStrict(baseLhsView.self().inputs, baseRhsView.self().inputs)
|
|
|| !compareMapsStrict(baseLhsView.self().outputs, baseRhsView.self().outputs))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Delegate strict check of extended fields to derived hook
|
|
return derivedLhs.strictEqualsImpl(derivedRhs);
|
|
}
|
|
|
|
friend bool operator!=(const Attributes<DerivedT>& lhs, const Attributes<DerivedT>& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
private:
|
|
Attributes() = default;
|
|
|
|
protected:
|
|
// Default fallback hooks for derived classes that do NOT have extra fields
|
|
bool logicallyEqualsImpl([[maybe_unused]] const DerivedT& other) const
|
|
{
|
|
return false;
|
|
}
|
|
bool strictEqualsImpl([[maybe_unused]] const DerivedT& other) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Get an input tensor by name
|
|
* @tparam InputNameT The input name enum or type
|
|
* @param inputName The input identifier
|
|
* @return Shared pointer to the tensor, or nullptr if not found
|
|
*/
|
|
template <typename InputNameT>
|
|
std::shared_ptr<TensorAttributes> getInput(InputNameT inputName) const
|
|
{
|
|
auto it = self().inputs.find(inputName);
|
|
if(it != self().inputs.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Get an output tensor by name
|
|
* @tparam OutputNameT The output name enum or type
|
|
* @param outputName The output identifier
|
|
* @return Shared pointer to the tensor, or nullptr if not found
|
|
*/
|
|
template <typename OutputNameT>
|
|
std::shared_ptr<TensorAttributes> getOutput(OutputNameT outputName) const
|
|
{
|
|
auto it = self().outputs.find(outputName);
|
|
if(it != self().outputs.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Set an input tensor by name (copy)
|
|
* @tparam InputNameT The input name enum or type
|
|
* @param inputName The input identifier
|
|
* @param value The tensor attributes to set
|
|
* @return Reference to derived class for method chaining
|
|
*/
|
|
template <typename InputNameT>
|
|
DerivedT& setInput(InputNameT inputName, const std::shared_ptr<TensorAttributes>& value)
|
|
{
|
|
self().inputs[inputName] = value;
|
|
return self();
|
|
}
|
|
|
|
/**
|
|
* @brief Set an input tensor by name (move)
|
|
* @tparam InputNameT The input name enum or type
|
|
* @param inputName The input identifier
|
|
* @param value The tensor attributes to move
|
|
* @return Reference to derived class for method chaining
|
|
*/
|
|
template <typename InputNameT>
|
|
DerivedT& setInput(InputNameT inputName, std::shared_ptr<TensorAttributes>&& value)
|
|
{
|
|
self().inputs[inputName] = std::move(value);
|
|
return self();
|
|
}
|
|
|
|
/**
|
|
* @brief Set an output tensor by name (copy)
|
|
* @tparam OutputNameT The output name enum or type
|
|
* @param outputName The output identifier
|
|
* @param value The tensor attributes to set
|
|
* @return Reference to derived class for method chaining
|
|
*/
|
|
template <typename OutputNameT>
|
|
DerivedT& setOutput(OutputNameT outputName, const std::shared_ptr<TensorAttributes>& value)
|
|
{
|
|
self().outputs[outputName] = value;
|
|
return self();
|
|
}
|
|
|
|
/**
|
|
* @brief Set an output tensor by name (move)
|
|
* @tparam OutputNameT The output name enum or type
|
|
* @param outputName The output identifier
|
|
* @param value The tensor attributes to move
|
|
* @return Reference to derived class for method chaining
|
|
*/
|
|
template <typename OutputNameT>
|
|
DerivedT& setOutput(OutputNameT outputName, std::shared_ptr<TensorAttributes>&& value)
|
|
{
|
|
self().outputs[outputName] = std::move(value);
|
|
return self();
|
|
}
|
|
|
|
/**
|
|
* @brief Record the first unsupported source-compatibility request (first wins)
|
|
* @param reason Human-readable description of what is unsupported
|
|
* @return Reference to derived class for method chaining
|
|
*
|
|
* Latches the first reason only; later calls are no-ops so the earliest
|
|
* offending setter is the one surfaced at validate().
|
|
*/
|
|
DerivedT& recordUnsupported(const char* reason)
|
|
{
|
|
if(!unsupported_reason.has_value())
|
|
{
|
|
unsupported_reason = reason;
|
|
}
|
|
return self();
|
|
}
|
|
|
|
private:
|
|
/**
|
|
* @brief Performs a logical/semantic equality check between two maps of attribute pointers.
|
|
* * Iterates over keys and evaluates whether their underlying values are functionally
|
|
* equivalent by routing the evaluation down to their custom `logicallyEquals` implementation.
|
|
* This bypasses rigid layout parameters (like memory strides) in favor of graph state matching.
|
|
* * @tparam MapT The map collection type (e.g., std::unordered_map or std::map).
|
|
* @param m1 The primary map instance to compare.
|
|
* @param m2 The secondary map instance to compare against.
|
|
* @return true If both maps represent the same functional mathematical state.
|
|
* @return false If structural layouts or logical evaluations mismatch.
|
|
*/
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
template <typename MapT>
|
|
static bool compareMapsLogical(const MapT& m1, const MapT& m2)
|
|
{
|
|
if(m1.size() != m2.size())
|
|
{
|
|
return false;
|
|
}
|
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
|
for(const auto& [key, t1] : m1)
|
|
{
|
|
auto it = m2.find(key);
|
|
if(it == m2.end())
|
|
{
|
|
return false;
|
|
}
|
|
// Both are unassigned or null; equivalent semantic state
|
|
if(!t1 && !it->second)
|
|
{
|
|
continue;
|
|
}
|
|
// Mismatched pointer presence, or structural logical validation fails
|
|
if(!t1 || !it->second || !t1->logicallyEquals(*it->second))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Performs a strict equality check between two maps of descriptor pointers.
|
|
* * Compares sizes, keys, and dereferenced values. Two maps are strictly equal if they
|
|
* contain the identical set of keys and their corresponding non-null pointers point
|
|
* to objects that satisfy the binary `operator==` check.
|
|
* * @tparam MapT The map collection type (e.g., std::unordered_map or std::map).
|
|
* @param m1 The primary map instance to compare.
|
|
* @param m2 The secondary map instance to compare against.
|
|
* @return true If both maps have identical structures and strict value matching.
|
|
* @return false If sizes, keys, nullability states, or raw evaluations mismatch.
|
|
*/
|
|
template <typename MapT>
|
|
static bool compareMapsStrict(const MapT& m1, const MapT& m2)
|
|
{
|
|
if(m1.size() != m2.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for(const auto& [key, t1] : m1)
|
|
{
|
|
auto it = m2.find(key);
|
|
if(it == m2.end())
|
|
{
|
|
return false;
|
|
}
|
|
// Both are null pointers; they match logically and strictly
|
|
if(!t1 && !it->second)
|
|
{
|
|
continue;
|
|
}
|
|
// One is null while the other isn't, or underlying values differ strictly
|
|
if(!t1 || !it->second || !(*t1 == *it->second))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} // namespace hipdnn_frontend::graph
|