Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions projects/hipdnn/backend/src/PlatformUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#pragma once

#include <array>
#include <cstdint>
#include <filesystem>
#include <hipdnn_data_sdk/utilities/PlatformUtils.hpp>
#include <string>
Expand All @@ -24,5 +26,6 @@ PluginLibHandle openLibrary(const std::filesystem::path& libraryPath);
void closeLibrary(PluginLibHandle handle);
void* getSymbol(PluginLibHandle handle, const char* symbolName);
std::string getSystemInfo();
std::array<uint8_t, 16> generateUuidV4();

}
16 changes: 16 additions & 0 deletions projects/hipdnn/backend/src/PlatformUtils.linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "HipdnnException.hpp"
#include <dlfcn.h>
#include <spdlog/fmt/fmt.h>
#include <sys/random.h>
#include <sys/utsname.h>

namespace hipdnn_backend::platform_utilities
Expand Down Expand Up @@ -79,6 +80,21 @@ std::string getSystemInfo()
buffer.machine);
}

std::array<uint8_t, 16> generateUuidV4()
{
// getentropy() fills the whole buffer or fails, for any request up to 256 bytes.
std::array<uint8_t, 16> bytes{};
if(getentropy(bytes.data(), bytes.size()) != 0)
{
throw HipdnnException(HIPDNN_STATUS_INTERNAL_ERROR,
"Failed to generate graph UUID using getentropy.");
}

bytes[6] = static_cast<uint8_t>((bytes[6] & 0x0fU) | 0x40U);
bytes[8] = static_cast<uint8_t>((bytes[8] & 0x3fU) | 0x80U);
return bytes;
}

}

#endif // defined(__linux__)
27 changes: 27 additions & 0 deletions projects/hipdnn/backend/src/PlatformUtils.windows.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT

#ifdef _WIN32
// Must precede every include: <stdlib.h> only declares rand_s when this is already defined, and
// any header that reaches it first silently leaves rand_s undeclared.
#define _CRT_RAND_S
#endif

#include "PlatformUtils.hpp"

#ifdef _WIN32

#include "HipdnnException.hpp"
#include <array>
#include <cstdlib>
#include <cstring>
#include <spdlog/fmt/fmt.h>
#include <winternl.h>

Expand Down Expand Up @@ -142,6 +150,25 @@ std::string getSystemInfo()
architecture);
}

std::array<uint8_t, 16> generateUuidV4()
{
std::array<uint8_t, 16> bytes{};
for(size_t offset = 0; offset < bytes.size(); offset += sizeof(unsigned int))
{
unsigned int randomValue;
if(rand_s(&randomValue) != 0)
{
throw HipdnnException(HIPDNN_STATUS_INTERNAL_ERROR,
"Failed to generate graph UUID using rand_s.");
}
std::memcpy(bytes.data() + offset, &randomValue, sizeof(randomValue));
}

bytes[6] = static_cast<uint8_t>((bytes[6] & 0x0fU) | 0x40U);
bytes[8] = static_cast<uint8_t>((bytes[8] & 0x3fU) | 0x80U);
return bytes;
}

}

#endif // _WIN32
82 changes: 69 additions & 13 deletions projects/hipdnn/backend/src/descriptors/GraphDescriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
#include "HipdnnBackendDescriptorType.h"
#include "HipdnnException.hpp"
#include "NodeFactory.hpp"
#include "PlatformUtils.hpp"

#include <hipdnn_flatbuffers_sdk/utilities/FlatbufferUtils.hpp>
#include <hipdnn_flatbuffers_sdk/utilities/Uuid.hpp>
#include <hipdnn_flatbuffers_sdk/utilities/json/Graph.hpp>
#include <hipdnn_plugin_sdk/PluginVersionConstants.hpp>
#include <logging/GraphLogger.hpp>
#include <logging/Logging.hpp>
#include <nlohmann/json.hpp>
#include <unordered_map>

Expand All @@ -33,11 +36,34 @@ void GraphDescriptor::finalize()
HIPDNN_STATUS_BAD_PARAM,
"GraphDescriptor::finalize: no operations set");

invalidateCache();
buildSerializedGraph();
const bool needsId = !_graphId.has_value();
if(needsId)
{
_graphId = platform_utilities::generateUuidV4();
}

try
{
invalidateCache();
buildSerializedGraph();
}
catch(...)
{
// Uncovered by tests: reaching here needs buildSerializedGraph() to fail on operations
// that already validated, which no input can force through the public API.
if(needsId)
{
_graphId.reset();
}
throw;
}

HipdnnBackendDescriptorImpl<GraphDescriptor>::finalize();

HIPDNN_BACKEND_LOG_INFO("Finalized graph \"{}\" with id {}",
_name,
hipdnn_flatbuffers_sdk::utilities::formatUuid(*_graphId));

if(logging::GraphLogger::isEnabled())
{
auto serialized = getSerializedGraph();
Expand All @@ -58,6 +84,11 @@ std::unique_ptr<hipdnn_flatbuffers_sdk::data_objects::GraphT>
graph->preferred_engine_id = _preferredEngineId;
graph->is_override_shape_enabled = _isOverrideShapeEnabled;
graph->name = _name;
if(_graphId.has_value())
{
graph->id = std::make_unique<hipdnn_flatbuffers_sdk::data_objects::Uuid>(
hipdnn_flatbuffers_sdk::utilities::toFlatbufferUuid(*_graphId));
}

std::unordered_map<int64_t, std::shared_ptr<TensorDescriptor>> seenTensors;

Expand Down Expand Up @@ -378,21 +409,30 @@ void GraphDescriptor::setAttribute(hipdnnBackendAttributeName_t attributeName,
// Non-mutating attributes (e.g., HANDLE) set invalidate = false.
if(invalidate)
{
if(_graphId.has_value())
{
HIPDNN_BACKEND_LOG_DEBUG(
"Discarding id {} for graph \"{}\": its contents changed, so finalizing again "
"generates a new id",
hipdnn_flatbuffers_sdk::utilities::formatUuid(*_graphId),
_name);
}
invalidateCache();
_graphId.reset();
}
}

void GraphDescriptor::deserializeGraph(const uint8_t* serializedGraph, size_t graphByteSize)
{
THROW_IF_TRUE(isFinalized(),
HIPDNN_STATUS_NOT_INITIALIZED,
"GraphDescriptor::deserializeGraph() failed: Already finalized.");
THROW_IF_NULL(serializedGraph,
HIPDNN_STATUS_BAD_PARAM_NULL_POINTER,
"GraphDescriptor::deserializeGraph: serializedGraph is null");
THROW_IF_TRUE(graphByteSize == 0,
HIPDNN_STATUS_BAD_PARAM,
"GraphDescriptor::deserializeGraph: graphByteSize is 0");

invalidateCache();

// Parse FlatBuffer and eagerly unpack into _operations
std::unique_ptr<hipdnn_flatbuffers_sdk::data_objects::GraphT> graph;
flatbuffer_utilities::convertSerializedGraphToGraph(serializedGraph, graphByteSize, graph);
Expand All @@ -405,7 +445,22 @@ void GraphDescriptor::deserializeGraph(const uint8_t* serializedGraph, size_t gr
HIPDNN_STATUS_NOT_SUPPORTED,
"Serialized graph requires a newer engine plugin API version than this build supports.");

// Extract graph-level attributes
std::optional<std::array<uint8_t, 16>> graphId;
if(graph->id)
{
graphId = hipdnn_flatbuffers_sdk::utilities::toUuidBytes(*graph->id);
}

auto tensorMap = NodeFactory::buildTensorMap(graph->tensors);
std::vector<std::shared_ptr<IBackendDescriptor>> unpacked;
unpacked.reserve(graph->nodes.size());
for(const auto& nodeT : graph->nodes)
{
unpacked.push_back(NodeFactory::createOperationFromNode(*nodeT, tensorMap));
}

// Commit only after every validation and conversion succeeds.
invalidateCache();
_computeDataType = graph->compute_data_type;
_intermediateDataType = graph->intermediate_data_type;
_ioDataType = graph->io_data_type;
Expand All @@ -416,14 +471,12 @@ void GraphDescriptor::deserializeGraph(const uint8_t* serializedGraph, size_t gr
_isRuntimePassByValueEnabled = hipdnn_flatbuffers_sdk::utilities::anyTensorIsRuntimePassByValue(
graph->tensors, [](const auto& tensor) { return tensor.get(); });
_name = graph->name;

// Populate _operations from the deserialized graph nodes
auto tensorMap = NodeFactory::buildTensorMap(graph->tensors);
std::vector<std::shared_ptr<IBackendDescriptor>> unpacked;
unpacked.reserve(graph->nodes.size());
for(const auto& nodeT : graph->nodes)
_graphId = graphId;
if(_graphId.has_value())
{
unpacked.push_back(NodeFactory::createOperationFromNode(*nodeT, tensorMap));
HIPDNN_BACKEND_LOG_DEBUG("Deserialized graph \"{}\" with inherited id {}",
_name,
hipdnn_flatbuffers_sdk::utilities::formatUuid(*_graphId));
}
_operations = std::move(unpacked);
}
Expand Down Expand Up @@ -578,6 +631,9 @@ std::string GraphDescriptor::toString() const
std::string str = "GraphDescriptor: {handle=";
str += _handle != nullptr ? fmt::format("{:p}", static_cast<const void*>(_handle)) : "null";
str += ", name=" + (_name.empty() ? std::string("(empty)") : _name);
str += ", id="
+ (_graphId.has_value() ? hipdnn_flatbuffers_sdk::utilities::formatUuid(*_graphId)
: std::string("(none)"));
str += ", serializedGraphSize=" + std::to_string(_graphSerializedBuffer.size());
str += '}';
return str;
Expand Down
6 changes: 6 additions & 0 deletions projects/hipdnn/backend/src/descriptors/GraphDescriptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "BackendDescriptor.hpp"
#include "IGraphOperation.hpp"
#include <array>
#include <flatbuffers/detached_buffer.h>
#include <hipdnn_flatbuffers_sdk/data_objects/graph_generated.h>
#include <hipdnn_plugin_sdk/PluginApiDataTypes.h>
Expand Down Expand Up @@ -37,6 +38,11 @@ class GraphDescriptor : public HipdnnBackendDescriptorImpl<GraphDescriptor>
// Cleared by invalidateCache() when operations are mutated.
flatbuffers::DetachedBuffer _graphSerializedBuffer;

// Stable identity of a logical finalized graph across serialization round trips.
// Deserialized copies preserve it, so it is not a unique live-object token; use it only to
// key state derived from immutable graph content. Newly generated values use UUID v4.
std::optional<std::array<uint8_t, 16>> _graphId;

// Source of truth for the graph's operations. Populated via setOperations() (C-API flow)
// or eagerly from deserializeGraph() (FlatBuffer flow).
// Stored as IBackendDescriptor so getOperations() can pack them without cross-casting.
Expand Down
52 changes: 38 additions & 14 deletions projects/hipdnn/backend/src/logging/GraphLogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
#include <hipdnn_data_sdk/utilities/PlatformUtils.hpp>
#include <hipdnn_data_sdk/utilities/StringUtil.hpp>
#include <hipdnn_flatbuffers_sdk/data_objects/graph_generated.h>
#include <hipdnn_flatbuffers_sdk/utilities/Uuid.hpp>
#include <hipdnn_flatbuffers_sdk/utilities/json/Graph.hpp>
#include <iomanip>
#include <mutex>
#include <nlohmann/json.hpp>
#include <sstream>

namespace hipdnn_backend::logging
{
Expand Down Expand Up @@ -90,38 +89,63 @@ std::filesystem::path GraphLogger::getOutputDirectory()

void GraphLogger::logGraph(const uint8_t* serializedGraph, size_t size)
{
auto hash = hipdnn_data_sdk::utilities::fnv1aHash(serializedGraph, size);
flatbuffers::Verifier verifier(serializedGraph, size);
if(!hipdnn_flatbuffers_sdk::data_objects::VerifyGraphBuffer(verifier))
{
HIPDNN_BACKEND_LOG_WARN("Not logging a graph whose buffer failed verification");
return;
}

const auto* graph
= flatbuffers::GetRoot<hipdnn_flatbuffers_sdk::data_objects::Graph>(serializedGraph);

std::ostringstream oss;
oss << "graph_" << std::hex << std::setfill('0') << std::setw(16) << hash << ".json";
auto fullPath = getOutputDirectory() / oss.str();
// Dumps are named by the graph's ID, so re-finalizing a descriptor or replaying a serialized
// graph reuses one file, while a graph rebuilt from scratch is a distinct graph object and
// gets its own.
if(graph->id() == nullptr)
{
HIPDNN_BACKEND_LOG_WARN("Not logging a graph without an identity; only a finalized graph "
"has one");
return;
}

if(fullPath.empty())
const auto graphId = hipdnn_flatbuffers_sdk::utilities::formatUuid(
hipdnn_flatbuffers_sdk::utilities::toUuidBytes(*graph->id()));
const auto graphName = graph->name() != nullptr ? graph->name()->str() : std::string();

const auto outputDirectory = getOutputDirectory();
if(outputDirectory.empty())
{
HIPDNN_BACKEND_LOG_WARN("Graph logging is enabled but no valid output directory is set");
return;
}

const auto fullPath = outputDirectory / ("graph_" + graphId + ".json");
if(std::filesystem::exists(fullPath))
{
HIPDNN_BACKEND_LOG_INFO("Skipping duplicate graph logged to {}", fullPath.string());
HIPDNN_BACKEND_LOG_INFO("Skipping graph \"{}\" with id {}, already logged to {}",
graphName,
graphId,
fullPath.string());
return;
}

auto* graph
= flatbuffers::GetRoot<hipdnn_flatbuffers_sdk::data_objects::Graph>(serializedGraph);
const nlohmann::json j = *graph;
const nlohmann::json graphJson = *graph;

std::ofstream file(fullPath);
if(file.is_open())
{
file << j.dump(2);
file << graphJson.dump(2);
file.close();
HIPDNN_BACKEND_LOG_INFO("Graph logged to {}", fullPath.string());
HIPDNN_BACKEND_LOG_INFO(
"Writing graph \"{}\" with id {} to {}", graphName, graphId, fullPath.string());
}
else
{
HIPDNN_BACKEND_LOG_WARN("Failed to open graph log file: {}", fullPath.string());
HIPDNN_BACKEND_LOG_WARN("Failed to open graph log file {} for graph \"{}\" with id {}",
fullPath.string(),
graphName,
graphId);
}
}

Expand Down
6 changes: 4 additions & 2 deletions projects/hipdnn/backend/src/logging/GraphLogger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ namespace hipdnn_backend::logging
class GraphLogger
{
public:
/// Logs the graph as JSON. Deduplicates by content hash — if the same
/// flatbuffer binary has already been logged, this is a no-op.
/// Logs the graph as JSON, named by the graph's ID. Only a finalized graph carries an ID;
/// an unidentified or malformed buffer is not logged. Logging a graph whose ID already has
/// a file in the output directory is a no-op, so re-finalized and replayed graphs are
/// written once; a graph rebuilt from scratch has a new ID and is written separately.
/// @param serializedGraph pointer to flatbuffer binary
/// @param size size of the flatbuffer binary
static void logGraph(const uint8_t* serializedGraph, size_t size);
Expand Down
Loading
Loading