Files
TheRock/rocm-libraries/projects/hipdnn/backend/tests/descriptors/TestGraphDescriptorLifting.cpp
T
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

827 lines
37 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include "DescriptorTestUtils.hpp"
#include "GraphTestUtils.hpp"
#include "HipdnnException.hpp"
#include "TensorDescriptorTestUtils.hpp"
#include "TestMacros.hpp"
#include "descriptors/ConvolutionFwdOperationDescriptor.hpp"
#include "descriptors/DataTypeConversion.hpp"
#include "descriptors/GraphDescriptor.hpp"
#include "descriptors/TensorDescriptor.hpp"
#include "hipdnn_backend.h"
#include "mocks/MockHandle.hpp"
#include <flatbuffers/flatbuffers.h>
#include <gtest/gtest.h>
#include <hipdnn_flatbuffers_sdk/data_objects/convolution_fwd_attributes_generated.h>
#include <hipdnn_flatbuffers_sdk/data_objects/graph_generated.h>
#include <hipdnn_flatbuffers_sdk/data_objects/matmul_attributes_generated.h>
#include <hipdnn_flatbuffers_sdk/data_objects/tensor_attributes_generated.h>
#include <hipdnn_test_sdk/constants/ConvFpropConstants.hpp>
#include <hipdnn_test_sdk/utilities/ToVec.hpp>
#include <array>
#include <cstring>
#include <memory>
#include <set>
#include <vector>
using namespace hipdnn_backend;
using namespace hipdnn_backend::test_utilities;
using namespace hipdnn_flatbuffers_sdk::data_objects;
using namespace hipdnn_tests::constants;
using hipdnn_tests::toVec;
class TestGraphDescriptorLifting : public ::testing::Test
{
public:
// Build a graph via operations, finalize, and return serialized bytes
std::vector<uint8_t> buildAndSerializeGraph(const std::vector<HipdnnBackendDescriptor*>& ops,
hipdnnDataType_t computeDt = HIPDNN_DATA_FLOAT,
hipdnnDataType_t intermediateDt = HIPDNN_DATA_FLOAT,
hipdnnDataType_t ioDt = HIPDNN_DATA_FLOAT,
std::optional<int64_t> preferredEngineId
= std::nullopt,
const std::string& name = "")
{
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
hipdnnHandle_t handle = &_mockHandle;
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle));
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
static_cast<int64_t>(ops.size()),
static_cast<const void*>(ops.data()));
graphDesc->setAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_COMPUTE_DATA_TYPE_EXT, HIPDNN_TYPE_DATA_TYPE, 1, &computeDt);
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_INTERMEDIATE_DATA_TYPE_EXT,
HIPDNN_TYPE_DATA_TYPE,
1,
&intermediateDt);
graphDesc->setAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_IO_DATA_TYPE_EXT, HIPDNN_TYPE_DATA_TYPE, 1, &ioDt);
if(preferredEngineId.has_value())
{
int64_t engineId = preferredEngineId.value();
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_PREFERRED_ENGINE_ID_EXT,
HIPDNN_TYPE_INT64,
1,
&engineId);
}
if(!name.empty())
{
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_NAME_EXT,
HIPDNN_TYPE_CHAR,
static_cast<int64_t>(name.size()),
name.c_str());
}
graphDesc->finalize();
auto serialized = graphDesc->getSerializedGraph();
return {static_cast<const uint8_t*>(serialized.ptr),
static_cast<const uint8_t*>(serialized.ptr) + serialized.size};
}
// Create a new GraphDescriptor from serialized bytes and finalize
std::shared_ptr<GraphDescriptor> deserializeAndFinalize(const std::vector<uint8_t>& bytes)
{
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
graphDesc->deserializeGraph(bytes.data(), bytes.size());
hipdnnHandle_t handle = &_mockHandle;
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle));
graphDesc->finalize();
return graphDesc;
}
protected:
mutable MockHandle _mockHandle;
};
// =============================================================================
// Deserialization/Lifting Round-Trip Tests
// =============================================================================
TEST_F(TestGraphDescriptorLifting, DeserializePreservesConvFpropNode)
{
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
// Deserialize and finalize
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Verify via the serialized buffer that the node is preserved
auto serialized = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->nodes.size(), 1);
ASSERT_EQ(graphT->nodes[0]->attributes.type, NodeAttributes::ConvolutionFwdAttributes);
ASSERT_EQ(graphT->tensors.size(), 3);
const auto* convAttrs = graphT->nodes[0]->attributes.AsConvolutionFwdAttributes();
ASSERT_NE(convAttrs, nullptr);
EXPECT_EQ(convAttrs->x_tensor_uid, K_FPROP_TENSOR_X_UID);
EXPECT_EQ(convAttrs->w_tensor_uid, K_FPROP_TENSOR_W_UID);
EXPECT_EQ(convAttrs->y_tensor_uid, K_FPROP_TENSOR_Y_UID);
EXPECT_EQ(convAttrs->pre_padding, toVec(K_FPROP_CONV_PADDING));
EXPECT_EQ(convAttrs->post_padding, toVec(K_FPROP_CONV_PADDING));
EXPECT_EQ(convAttrs->stride, toVec(K_FPROP_CONV_STRIDE));
EXPECT_EQ(convAttrs->dilation, toVec(K_FPROP_CONV_DILATION));
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesMultipleNodes)
{
auto conv1 = createDefaultConvOp();
auto xDesc2 = createFinalizedTensor(
K_FPROP_TENSOR_X2_UID, toVec(K_FPROP_TENSOR_X2_DIMS), toVec(K_FPROP_TENSOR_X2_STRIDES));
auto wDesc2 = createFinalizedTensor(
K_FPROP_TENSOR_W2_UID, toVec(K_FPROP_TENSOR_W2_DIMS), toVec(K_FPROP_TENSOR_W2_STRIDES));
auto yDesc2 = createFinalizedTensor(
K_FPROP_TENSOR_Y2_UID, toVec(K_FPROP_TENSOR_Y2_DIMS), toVec(K_FPROP_TENSOR_Y2_STRIDES));
auto convOp2 = createFinalizedConvOp(xDesc2.get(), wDesc2.get(), yDesc2.get());
const std::vector<HipdnnBackendDescriptor*> ops = {conv1.convOp.get(), convOp2.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Verify via the serialized buffer
auto serialized = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->nodes.size(), 2);
ASSERT_EQ(graphT->tensors.size(), 6);
ASSERT_EQ(graphT->nodes[0]->attributes.type, NodeAttributes::ConvolutionFwdAttributes);
ASSERT_EQ(graphT->nodes[1]->attributes.type, NodeAttributes::ConvolutionFwdAttributes);
const auto* conv1Attrs = graphT->nodes[0]->attributes.AsConvolutionFwdAttributes();
ASSERT_NE(conv1Attrs, nullptr);
EXPECT_EQ(conv1Attrs->x_tensor_uid, K_FPROP_TENSOR_X_UID);
EXPECT_EQ(conv1Attrs->w_tensor_uid, K_FPROP_TENSOR_W_UID);
EXPECT_EQ(conv1Attrs->y_tensor_uid, K_FPROP_TENSOR_Y_UID);
const auto* conv2Attrs = graphT->nodes[1]->attributes.AsConvolutionFwdAttributes();
ASSERT_NE(conv2Attrs, nullptr);
EXPECT_EQ(conv2Attrs->x_tensor_uid, K_FPROP_TENSOR_X2_UID);
EXPECT_EQ(conv2Attrs->w_tensor_uid, K_FPROP_TENSOR_W2_UID);
EXPECT_EQ(conv2Attrs->y_tensor_uid, K_FPROP_TENSOR_Y2_UID);
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesTensorData)
{
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Verify tensor data via the serialized buffer
auto serialized = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->tensors.size(), 3);
// Find the X tensor by UID and verify its attributes
auto* xTensor = findTensorByUid(*graphT, K_FPROP_TENSOR_X_UID);
ASSERT_NE(xTensor, nullptr);
EXPECT_EQ(xTensor->data_type, DataType::FLOAT);
EXPECT_EQ(xTensor->dims, toVec(K_FPROP_TENSOR_X_DIMS));
EXPECT_EQ(xTensor->strides, toVec(K_FPROP_TENSOR_X_STRIDES));
// Find the W tensor by UID and verify its attributes
auto* wTensor = findTensorByUid(*graphT, K_FPROP_TENSOR_W_UID);
ASSERT_NE(wTensor, nullptr);
EXPECT_EQ(wTensor->data_type, DataType::FLOAT);
EXPECT_EQ(wTensor->dims, toVec(K_FPROP_TENSOR_W_DIMS));
EXPECT_EQ(wTensor->strides, toVec(K_FPROP_TENSOR_W_STRIDES));
// Find the Y tensor by UID and verify its attributes
auto* yTensor = findTensorByUid(*graphT, K_FPROP_TENSOR_Y_UID);
ASSERT_NE(yTensor, nullptr);
EXPECT_EQ(yTensor->data_type, DataType::FLOAT);
EXPECT_EQ(yTensor->dims, toVec(K_FPROP_TENSOR_Y_DIMS));
EXPECT_EQ(yTensor->strides, toVec(K_FPROP_TENSOR_Y_STRIDES));
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesGraphLevelAttributes)
{
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(
ops, HIPDNN_DATA_HALF, HIPDNN_DATA_BFLOAT16, HIPDNN_DATA_FLOAT, 42);
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Query compute data type
int64_t computeCount = 0;
hipdnnDataType_t computeDt = HIPDNN_DATA_FLOAT;
ASSERT_NO_THROW(liftedGraph->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_COMPUTE_DATA_TYPE_EXT,
HIPDNN_TYPE_DATA_TYPE,
1,
&computeCount,
&computeDt));
EXPECT_EQ(computeDt, HIPDNN_DATA_HALF);
// Query intermediate data type
int64_t intermediateCount = 0;
hipdnnDataType_t intermediateDt = HIPDNN_DATA_FLOAT;
ASSERT_NO_THROW(liftedGraph->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_INTERMEDIATE_DATA_TYPE_EXT,
HIPDNN_TYPE_DATA_TYPE,
1,
&intermediateCount,
&intermediateDt));
EXPECT_EQ(intermediateDt, HIPDNN_DATA_BFLOAT16);
// Query IO data type
int64_t ioCount = 0;
hipdnnDataType_t ioDt = HIPDNN_DATA_HALF;
ASSERT_NO_THROW(liftedGraph->getAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_IO_DATA_TYPE_EXT, HIPDNN_TYPE_DATA_TYPE, 1, &ioCount, &ioDt));
EXPECT_EQ(ioDt, HIPDNN_DATA_FLOAT);
// Query preferred engine ID
int64_t engineIdCount = 0;
int64_t engineId = 0;
ASSERT_NO_THROW(liftedGraph->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_PREFERRED_ENGINE_ID_EXT,
HIPDNN_TYPE_INT64,
1,
&engineIdCount,
&engineId));
EXPECT_EQ(engineIdCount, 1);
EXPECT_EQ(engineId, 42);
}
TEST_F(TestGraphDescriptorLifting, LiftedGraphSerializesToSameBinary)
{
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto originalBytes = buildAndSerializeGraph(
ops, HIPDNN_DATA_HALF, HIPDNN_DATA_BFLOAT16, HIPDNN_DATA_FLOAT, 99);
// Deserialize and finalize (first lift)
auto liftedGraph = deserializeAndFinalize(originalBytes);
// Re-serialize the lifted graph
auto reSerializedData = liftedGraph->getSerializedGraph();
const std::vector<uint8_t> reSerializedBytes(static_cast<const uint8_t*>(reSerializedData.ptr),
static_cast<const uint8_t*>(reSerializedData.ptr)
+ reSerializedData.size);
// Compare binary output
ASSERT_EQ(originalBytes.size(), reSerializedBytes.size());
EXPECT_EQ(originalBytes, reSerializedBytes);
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesNodeTensorUids)
{
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Verify node and tensor data via the serialized graph buffer
auto serialized = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->nodes.size(), 1);
ASSERT_EQ(graphT->tensors.size(), 3);
// Verify the node has ConvolutionFwdAttributes with correct tensor UIDs
const auto* convAttrs = graphT->nodes[0]->attributes.AsConvolutionFwdAttributes();
ASSERT_NE(convAttrs, nullptr);
EXPECT_EQ(convAttrs->x_tensor_uid, K_FPROP_TENSOR_X_UID);
EXPECT_EQ(convAttrs->w_tensor_uid, K_FPROP_TENSOR_W_UID);
EXPECT_EQ(convAttrs->y_tensor_uid, K_FPROP_TENSOR_Y_UID);
// Verify the tensor UIDs in the tensor list
std::set<int64_t> tensorUids;
for(const auto& tensor : graphT->tensors)
{
tensorUids.insert(tensor->uid);
}
EXPECT_EQ(tensorUids.count(K_FPROP_TENSOR_X_UID), 1u);
EXPECT_EQ(tensorUids.count(K_FPROP_TENSOR_W_UID), 1u);
EXPECT_EQ(tensorUids.count(K_FPROP_TENSOR_Y_UID), 1u);
EXPECT_EQ(tensorUids.size(), 3u);
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesConvolutionParameters)
{
// Create a conv op with non-default parameters
auto xDesc = createFinalizedTensor(K_FPROP_TENSOR_X_UID);
auto wDesc = createFinalizedTensor(
K_FPROP_TENSOR_W_UID, toVec(K_FPROP_TENSOR_W_DIMS), toVec(K_FPROP_TENSOR_W_STRIDES));
auto yDesc = createFinalizedTensor(
K_FPROP_TENSOR_Y_UID, toVec(K_FPROP_TENSOR_Y_DIMS), toVec(K_FPROP_TENSOR_Y_STRIDES));
auto convWrapper = createDescriptor<ConvolutionFwdOperationDescriptor>();
auto convDesc = convWrapper->asDescriptor<ConvolutionFwdOperationDescriptor>();
HipdnnBackendDescriptor* x = xDesc.get();
HipdnnBackendDescriptor* w = wDesc.get();
HipdnnBackendDescriptor* y = yDesc.get();
convDesc->setAttribute(HIPDNN_ATTR_OPERATION_CONVOLUTION_FORWARD_X,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&x));
convDesc->setAttribute(HIPDNN_ATTR_OPERATION_CONVOLUTION_FORWARD_W,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&w));
convDesc->setAttribute(HIPDNN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&y));
const std::vector<int64_t> kCustomPrePadding = {2, 3};
const std::vector<int64_t> kCustomPostPadding = {4, 5};
const std::vector<int64_t> kCustomStride = {2, 2};
const std::vector<int64_t> kCustomDilation = {2, 2};
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_PRE_PADDINGS, HIPDNN_TYPE_INT64, 2, kCustomPrePadding.data());
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_POST_PADDINGS, HIPDNN_TYPE_INT64, 2, kCustomPostPadding.data());
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_FILTER_STRIDES, HIPDNN_TYPE_INT64, 2, kCustomStride.data());
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_DILATIONS, HIPDNN_TYPE_INT64, 2, kCustomDilation.data());
auto computeType = HIPDNN_DATA_HALF;
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_COMP_TYPE, HIPDNN_TYPE_DATA_TYPE, 1, &computeType);
hipdnnConvolutionMode_t convMode = HIPDNN_CROSS_CORRELATION;
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_CONV_MODE, HIPDNN_TYPE_CONVOLUTION_MODE, 1, &convMode);
convDesc->finalize();
const std::vector<HipdnnBackendDescriptor*> ops = {convWrapper.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Verify convolution parameters via the serialized graph buffer
auto serialized = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->nodes.size(), 1);
const auto* convAttrs = graphT->nodes[0]->attributes.AsConvolutionFwdAttributes();
ASSERT_NE(convAttrs, nullptr);
EXPECT_EQ(convAttrs->pre_padding, kCustomPrePadding);
EXPECT_EQ(convAttrs->post_padding, kCustomPostPadding);
EXPECT_EQ(convAttrs->stride, kCustomStride);
EXPECT_EQ(convAttrs->dilation, kCustomDilation);
EXPECT_EQ(convAttrs->conv_mode, ConvMode::CROSS_CORRELATION);
EXPECT_EQ(graphT->nodes[0]->compute_data_type, DataType::HALF);
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesConvolutionModeConvolution)
{
// Verify HIPDNN_CONVOLUTION round-trips correctly
// (contrasting with CROSS_CORRELATION tested in DeserializePreservesConvolutionParameters)
auto xDesc = createFinalizedTensor(K_FPROP_TENSOR_X_UID);
auto wDesc = createFinalizedTensor(
K_FPROP_TENSOR_W_UID, toVec(K_FPROP_TENSOR_W_DIMS), toVec(K_FPROP_TENSOR_W_STRIDES));
auto yDesc = createFinalizedTensor(
K_FPROP_TENSOR_Y_UID, toVec(K_FPROP_TENSOR_Y_DIMS), toVec(K_FPROP_TENSOR_Y_STRIDES));
auto convWrapper = createDescriptor<ConvolutionFwdOperationDescriptor>();
auto convDesc = convWrapper->asDescriptor<ConvolutionFwdOperationDescriptor>();
HipdnnBackendDescriptor* x = xDesc.get();
HipdnnBackendDescriptor* w = wDesc.get();
HipdnnBackendDescriptor* y = yDesc.get();
convDesc->setAttribute(HIPDNN_ATTR_OPERATION_CONVOLUTION_FORWARD_X,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&x));
convDesc->setAttribute(HIPDNN_ATTR_OPERATION_CONVOLUTION_FORWARD_W,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&w));
convDesc->setAttribute(HIPDNN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&y));
const std::vector<int64_t> kPadding = {1, 1};
const std::vector<int64_t> kStride = {1, 1};
const std::vector<int64_t> kDilation = {1, 1};
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_PRE_PADDINGS, HIPDNN_TYPE_INT64, 2, kPadding.data());
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_POST_PADDINGS, HIPDNN_TYPE_INT64, 2, kPadding.data());
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_FILTER_STRIDES, HIPDNN_TYPE_INT64, 2, kStride.data());
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_DILATIONS, HIPDNN_TYPE_INT64, 2, kDilation.data());
auto computeType = HIPDNN_DATA_FLOAT;
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_COMP_TYPE, HIPDNN_TYPE_DATA_TYPE, 1, &computeType);
// Use CONVOLUTION mode (not CROSS_CORRELATION)
hipdnnConvolutionMode_t convMode = HIPDNN_CONVOLUTION;
convDesc->setAttribute(
HIPDNN_ATTR_CONVOLUTION_CONV_MODE, HIPDNN_TYPE_CONVOLUTION_MODE, 1, &convMode);
convDesc->finalize();
const std::vector<HipdnnBackendDescriptor*> ops = {convWrapper.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Verify convolution mode via the serialized graph buffer
auto serialized = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->nodes.size(), 1);
const auto* convAttrs = graphT->nodes[0]->attributes.AsConvolutionFwdAttributes();
ASSERT_NE(convAttrs, nullptr);
EXPECT_EQ(convAttrs->conv_mode, ConvMode::CONVOLUTION);
}
TEST_F(TestGraphDescriptorLifting, DoubleRoundTrip)
{
// Build -> Serialize -> Deserialize -> Serialize -> Deserialize -> Verify
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto originalBytes = buildAndSerializeGraph(
ops, HIPDNN_DATA_HALF, HIPDNN_DATA_BFLOAT16, HIPDNN_DATA_FLOAT, 77);
// First round-trip
auto liftedGraph1 = deserializeAndFinalize(originalBytes);
auto serialized1 = liftedGraph1->getSerializedGraph();
const std::vector<uint8_t> bytes1(static_cast<const uint8_t*>(serialized1.ptr),
static_cast<const uint8_t*>(serialized1.ptr)
+ serialized1.size);
// Second round-trip
auto liftedGraph2 = deserializeAndFinalize(bytes1);
auto serialized2 = liftedGraph2->getSerializedGraph();
const std::vector<uint8_t> bytes2(static_cast<const uint8_t*>(serialized2.ptr),
static_cast<const uint8_t*>(serialized2.ptr)
+ serialized2.size);
// All three should be identical
EXPECT_EQ(originalBytes, bytes1);
EXPECT_EQ(bytes1, bytes2);
// Verify graph-level attributes on final lift
int64_t engineIdCount = 0;
int64_t engineId = 0;
liftedGraph2->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_PREFERRED_ENGINE_ID_EXT,
HIPDNN_TYPE_INT64,
1,
&engineIdCount,
&engineId);
EXPECT_EQ(engineId, 77);
// Verify the serialized graph still has 1 node
auto graphT = UnPackGraph(serialized2.ptr);
EXPECT_EQ(graphT->nodes.size(), 1);
ASSERT_EQ(graphT->tensors.size(), 3);
ASSERT_EQ(graphT->nodes[0]->attributes.type, NodeAttributes::ConvolutionFwdAttributes);
// Verify graph-level data types from the serialized buffer
EXPECT_EQ(graphT->compute_data_type, DataType::HALF);
EXPECT_EQ(graphT->intermediate_data_type, DataType::BFLOAT16);
EXPECT_EQ(graphT->io_data_type, DataType::FLOAT);
}
// =============================================================================
// Cross-flow Tests (FlatBuffer + C-API interoperability)
// =============================================================================
TEST_F(TestGraphDescriptorLifting, SetOperationsAfterDeserializeSucceeds)
{
// Build and serialize a single conv op graph
auto conv1 = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv1.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
// Create a new GraphDescriptor and deserialize the bytes
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
graphDesc->deserializeGraph(serializedBytes.data(), serializedBytes.size());
hipdnnHandle_t handle = &_mockHandle;
ASSERT_NO_THROW(graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle)));
// Adding operations after deserialization appends to the existing operations
auto conv2 = createDefaultConvOp(HIPDNN_DATA_HALF);
HipdnnBackendDescriptor* op2Ptr = conv2.convOp.get();
ASSERT_NO_THROW(graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&op2Ptr)));
// Verify the operations count includes both deserialized and appended ops
int64_t elementCount = 0;
ASSERT_NO_THROW(graphDesc->getAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_OPS, HIPDNN_TYPE_BACKEND_DESCRIPTOR, 0, &elementCount, nullptr));
EXPECT_EQ(elementCount, 2);
}
TEST_F(TestGraphDescriptorLifting, GetAttributeOpsAfterDeserializeSucceeds)
{
// Build and serialize a single conv op graph
auto conv1 = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv1.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
// Deserialize and finalize (FlatBuffer flow)
auto liftedGraph = deserializeAndFinalize(serializedBytes);
// Lazy unpack: getOperations() unpacks the FlatBuffer nodes into _operations
int64_t elementCount = -1;
ASSERT_NO_THROW(liftedGraph->getAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_OPS, HIPDNN_TYPE_BACKEND_DESCRIPTOR, 0, &elementCount, nullptr));
EXPECT_EQ(elementCount, 1);
}
TEST_F(TestGraphDescriptorLifting, DeserializeOnlyFinalize)
{
// Build and serialize a graph with specific graph-level attributes
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto originalBytes = buildAndSerializeGraph(
ops, HIPDNN_DATA_HALF, HIPDNN_DATA_BFLOAT16, HIPDNN_DATA_FLOAT, 42);
// Create new GraphDescriptor, deserialize, set handle, finalize (no operations added)
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
graphDesc->deserializeGraph(originalBytes.data(), originalBytes.size());
hipdnnHandle_t handle = &_mockHandle;
ASSERT_NO_THROW(graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle)));
ASSERT_NO_THROW(graphDesc->finalize());
// Get the re-serialized bytes
auto serialized = graphDesc->getSerializedGraph();
const std::vector<uint8_t> reSerializedBytes(static_cast<const uint8_t*>(serialized.ptr),
static_cast<const uint8_t*>(serialized.ptr)
+ serialized.size);
// Re-serializing from unpacked operations should produce identical bytes
ASSERT_EQ(originalBytes.size(), reSerializedBytes.size());
EXPECT_EQ(originalBytes, reSerializedBytes);
}
TEST_F(TestGraphDescriptorLifting, FlatBufferFlowFinalizePreservesSerialization)
{
// Build and serialize a single conv op graph
auto conv1 = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv1.convOp.get()};
auto originalBytes = buildAndSerializeGraph(ops);
// Deserialize, set handle, finalize (FlatBuffer flow)
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
graphDesc->deserializeGraph(originalBytes.data(), originalBytes.size());
hipdnnHandle_t handle = &_mockHandle;
ASSERT_NO_THROW(graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle)));
ASSERT_NO_THROW(graphDesc->finalize());
// Get serialized bytes — they should be identical to the original
auto serialized = graphDesc->getSerializedGraph();
const std::vector<uint8_t> newBytes(static_cast<const uint8_t*>(serialized.ptr),
static_cast<const uint8_t*>(serialized.ptr)
+ serialized.size);
EXPECT_EQ(originalBytes, newBytes);
// Verify the serialized graph content
auto graphT = UnPackGraph(serialized.ptr);
ASSERT_EQ(graphT->nodes.size(), 1);
ASSERT_EQ(graphT->tensors.size(), 3);
ASSERT_EQ(graphT->nodes[0]->attributes.type, NodeAttributes::ConvolutionFwdAttributes);
// Spot-check tensor dims
auto* xTensor = findTensorByUid(*graphT, K_FPROP_TENSOR_X_UID);
ASSERT_NE(xTensor, nullptr);
EXPECT_EQ(xTensor->dims, toVec(K_FPROP_TENSOR_X_DIMS));
}
TEST_F(TestGraphDescriptorLifting, GetAttributeWrongTypeForOpsOnCApiFlow)
{
// Build a graph via the C-API flow
auto conv = createDefaultConvOp();
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
hipdnnHandle_t handle = &_mockHandle;
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle));
HipdnnBackendDescriptor* opPtr = conv.convOp.get();
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&opPtr));
graphDesc->finalize();
// Call getAttribute with HIPDNN_ATTR_OPERATIONGRAPH_OPS but pass HIPDNN_TYPE_INT64
// instead of HIPDNN_TYPE_BACKEND_DESCRIPTOR
int64_t elementCount = 0;
std::array<int64_t, 1> wrongTypeBuffer = {0};
ASSERT_THROW_HIPDNN_STATUS(graphDesc->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_INT64,
1,
&elementCount,
wrongTypeBuffer.data()),
HIPDNN_STATUS_BAD_PARAM);
}
TEST_F(TestGraphDescriptorLifting, GetAttributeRequestedCountTooSmallOnCApiFlow)
{
// Build a graph with 2 conv ops via the C-API flow
auto conv1 = createDefaultConvOp();
auto xDesc2 = createFinalizedTensor(
K_FPROP_TENSOR_X2_UID, toVec(K_FPROP_TENSOR_X2_DIMS), toVec(K_FPROP_TENSOR_X2_STRIDES));
auto wDesc2 = createFinalizedTensor(
K_FPROP_TENSOR_W2_UID, toVec(K_FPROP_TENSOR_W2_DIMS), toVec(K_FPROP_TENSOR_W2_STRIDES));
auto yDesc2 = createFinalizedTensor(
K_FPROP_TENSOR_Y2_UID, toVec(K_FPROP_TENSOR_Y2_DIMS), toVec(K_FPROP_TENSOR_Y2_STRIDES));
auto convOp2 = createFinalizedConvOp(xDesc2.get(), wDesc2.get(), yDesc2.get());
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
hipdnnHandle_t handle = &_mockHandle;
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_HANDLE,
HIPDNN_TYPE_HANDLE,
1,
static_cast<const void*>(&handle));
HipdnnBackendDescriptor* op1Ptr = conv1.convOp.get();
HipdnnBackendDescriptor* op2Ptr = convOp2.get();
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&op1Ptr));
graphDesc->setAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
static_cast<const void*>(&op2Ptr));
graphDesc->finalize();
// Allocate a buffer for 2 ops, but pass requestedElementCount=1
// with a non-null arrayOfElements
int64_t elementCount = 0;
std::array<HipdnnBackendDescriptor*, 2> returnedOps = {nullptr, nullptr};
ASSERT_THROW_HIPDNN_STATUS(graphDesc->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_OPS,
HIPDNN_TYPE_BACKEND_DESCRIPTOR,
1,
&elementCount,
returnedOps.data()),
HIPDNN_STATUS_BAD_PARAM);
}
TEST_F(TestGraphDescriptorLifting, DeserializeCorruptedBufferThrows)
{
// Pass garbage bytes to deserializeGraph(). The FlatBuffer verifier
// should reject the buffer and throw HIPDNN_STATUS_BAD_PARAM.
std::vector<uint8_t> garbage = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01, 0x02, 0x03};
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
ASSERT_THROW_HIPDNN_STATUS(graphDesc->deserializeGraph(garbage.data(), garbage.size()),
HIPDNN_STATUS_BAD_PARAM);
}
// =============================================================================
// Tests for getAttribute Without Finalization
// =============================================================================
TEST_F(TestGraphDescriptorLifting, GetAttributeOpsOnFlatBufferFlowWorksWithoutFinalization)
{
// Build and serialize a conv graph
auto conv = createDefaultConvOp();
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto serializedBytes = buildAndSerializeGraph(ops);
// Create a new GraphDescriptor and deserialize (no handle, no finalize)
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
graphDesc->deserializeGraph(serializedBytes.data(), serializedBytes.size());
// Lazy unpack works without finalization: unpacks _graph nodes into _operations
int64_t elementCount = -1;
ASSERT_NO_THROW(graphDesc->getAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_OPS, HIPDNN_TYPE_BACKEND_DESCRIPTOR, 0, &elementCount, nullptr));
EXPECT_EQ(elementCount, 1);
}
TEST_F(TestGraphDescriptorLifting, GetAttributeDataTypesWithoutFinalization)
{
// Build and serialize a conv graph with specific data types
auto conv = createDefaultConvOp(HIPDNN_DATA_HALF);
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto serializedBytes
= buildAndSerializeGraph(ops, HIPDNN_DATA_HALF, HIPDNN_DATA_BFLOAT16, HIPDNN_DATA_FLOAT);
// Create a new GraphDescriptor and deserialize (no handle, no finalize)
auto graphWrapper = createDescriptor<GraphDescriptor>();
auto graphDesc = graphWrapper->asDescriptor<GraphDescriptor>();
graphDesc->deserializeGraph(serializedBytes.data(), serializedBytes.size());
// Query compute data type without finalization - should succeed
int64_t computeCount = 0;
hipdnnDataType_t computeDt = HIPDNN_DATA_FLOAT;
ASSERT_NO_THROW(graphDesc->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_COMPUTE_DATA_TYPE_EXT,
HIPDNN_TYPE_DATA_TYPE,
1,
&computeCount,
&computeDt));
EXPECT_EQ(computeDt, HIPDNN_DATA_HALF);
// Query intermediate data type without finalization - should succeed
int64_t intermediateCount = 0;
hipdnnDataType_t intermediateDt = HIPDNN_DATA_FLOAT;
ASSERT_NO_THROW(graphDesc->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_INTERMEDIATE_DATA_TYPE_EXT,
HIPDNN_TYPE_DATA_TYPE,
1,
&intermediateCount,
&intermediateDt));
EXPECT_EQ(intermediateDt, HIPDNN_DATA_BFLOAT16);
// Query IO data type without finalization - should succeed
int64_t ioCount = 0;
hipdnnDataType_t ioDt = HIPDNN_DATA_HALF;
ASSERT_NO_THROW(graphDesc->getAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_IO_DATA_TYPE_EXT, HIPDNN_TYPE_DATA_TYPE, 1, &ioCount, &ioDt));
EXPECT_EQ(ioDt, HIPDNN_DATA_FLOAT);
}
TEST_F(TestGraphDescriptorLifting, DeserializePreservesGraphName)
{
auto conv = createDefaultConvOp();
const std::string graphName = "TestGraphName";
const std::vector<HipdnnBackendDescriptor*> ops = {conv.convOp.get()};
auto bytes = buildAndSerializeGraph(
ops, HIPDNN_DATA_FLOAT, HIPDNN_DATA_FLOAT, HIPDNN_DATA_FLOAT, std::nullopt, graphName);
auto liftedGraph = deserializeAndFinalize(bytes);
// Verify via getAttribute (getString returns size+1 for null terminator)
int64_t nameCount = 0;
ASSERT_NO_THROW(liftedGraph->getAttribute(
HIPDNN_ATTR_OPERATIONGRAPH_NAME_EXT, HIPDNN_TYPE_CHAR, 0, &nameCount, nullptr));
ASSERT_EQ(nameCount, static_cast<int64_t>(graphName.size() + 1));
std::vector<char> nameBuffer(static_cast<size_t>(nameCount));
int64_t actualCount = 0;
ASSERT_NO_THROW(liftedGraph->getAttribute(HIPDNN_ATTR_OPERATIONGRAPH_NAME_EXT,
HIPDNN_TYPE_CHAR,
nameCount,
&actualCount,
nameBuffer.data()));
EXPECT_STREQ(nameBuffer.data(), graphName.c_str());
// Also verify via the serialized FlatBuffer
auto reSerializedData = liftedGraph->getSerializedGraph();
auto graphT = UnPackGraph(reSerializedData.ptr);
EXPECT_EQ(graphT->name, graphName);
}