Files
TheRock/rocm-libraries/projects/hipdnn/samples/example_engine_plugin/sample/ExampleProviderSample.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

857 lines
30 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
// TEMPLATE ADAPTATION: Adapt this sample to exercise your plugin's operations. Scenario 1 (engine
// selection + knob modification) and Scenario 2 (convenience build) demonstrate the two API
// styles. Keep the structure and replace the graph construction and verification logic. Scenario 3
// (plugin loading modes) can be kept as-is with your plugin name updated, customized for your
// specific environment, or discarded if the plugin is installed directly to the ROCm hipDNN plugin
// folder where it will be loaded automatically with all hipDNN plugins.
//
// Sample application demonstrating how to load a hipDNN engine plugin and
// execute graphs using the plugin's GPU engines. This program serves as both
// a demonstration and an end-to-end acceptance test.
//
// Three scenarios are demonstrated:
// 1. ReLU forward with engine selection and knob modification (explicit 6-step
// build, leaky ReLU via negative_slope knob, verification)
// 2. Convolution forward with engine selection (build() convenience, Tensor
// class for GPU memory management, verification with hardcoded values)
// 3. Plugin loading modes (ADDITIVE, ABSOLUTE, presence verification)
//
// When no GPU is detected, only non-GPU portions run (plugin
// loading, presence verification, graph construction).
//
// Plugin path resolution:
// 1. If a command-line argument is provided, that path is used. This can be
// a directory containing the plugin or the full path to the plugin file.
// 2. Otherwise, if HIPDNN_PLUGIN_DIR is set in the environment, that is used.
// 3. Otherwise, the directory containing this executable is used as a last resort.
//
// Prerequisites:
// - hipDNN installed
// - ROCm with HIPRTC and a compatible GPU (for GPU execution portions)
// - The example_provider_plugin shared library (built in the same CMake project)
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include <hip/hip_runtime.h>
#include <hipdnn_data_sdk/utilities/PlatformUtils.hpp>
#include <hipdnn_data_sdk/utilities/Tensor.hpp>
#include <hipdnn_frontend.hpp>
#include <hipdnn_frontend/Utilities.hpp>
using namespace hipdnn_frontend;
// Gracefully skip the sample when no GPU is present: prints a skip message and
// returns 0 from the enclosing function.
#define RETURN_SUCCESS_IF_NO_DEVICE() \
do \
{ \
int deviceCount = 0; \
if(hipGetDeviceCount(&deviceCount) != hipSuccess || deviceCount == 0) \
{ \
std::cout << "SKIPPED: No GPU devices available.\n"; \
return 0; \
} \
} while(0)
// ============================================================================
// Helper Functions
// ============================================================================
// HIP error checking helper
static bool checkHip(hipError_t err, const char* msg)
{
if(err != hipSuccess)
{
std::cerr << " HIP ERROR: " << msg << ": " << hipGetErrorString(err) << "\n";
return false;
}
return true;
}
// Check a hipDNN graph operation result and print an error message on failure.
// Returns true if the result is OK, false otherwise. The caller decides how
// to handle the failure (cleanup, early return, etc.).
static bool checkGraphResult(const hipdnn_frontend::error_t& result, const char* stepName)
{
if(result.code != ErrorCode::OK)
{
std::cerr << " ERROR: " << stepName << " failed: " << result.err_msg << "\n";
return false;
}
return true;
}
// Result of createReluGraph: the graph plus input/output tensor attributes.
struct ReluGraph
{
std::shared_ptr<hipdnn_frontend::graph::Graph> graph;
std::shared_ptr<hipdnn_frontend::graph::TensorAttributes> x;
std::shared_ptr<hipdnn_frontend::graph::TensorAttributes> y;
};
// Build a pointwise ReLU forward graph with the given tensor dimensions.
static ReluGraph createReluGraph(const std::string& name, const std::vector<int64_t>& dims)
{
auto graph = std::make_shared<hipdnn_frontend::graph::Graph>();
graph->set_name(name)
.set_io_data_type(DataType::FLOAT)
.set_intermediate_data_type(DataType::FLOAT)
.set_compute_data_type(DataType::FLOAT);
auto x = std::make_shared<hipdnn_frontend::graph::TensorAttributes>();
x->set_uid(1)
.set_name("X")
.set_dim(dims)
.set_stride({dims[1] * dims[2] * dims[3], dims[2] * dims[3], dims[3], 1})
.set_data_type(DataType::FLOAT);
hipdnn_frontend::graph::PointwiseAttributes attrs;
attrs.set_name("relu_fwd");
attrs.set_mode(PointwiseMode::RELU_FWD);
auto y = graph->pointwise(x, attrs);
y->set_uid(2).set_data_type(DataType::FLOAT).set_output(true);
return {graph, x, y};
}
// Run the full build/execute sequence for a ReLU graph using GPU device memory.
// Returns true on success.
static bool runReluGraph(hipdnnHandle_t handle,
const std::string& graphName,
const std::vector<float>& input,
std::vector<float>& output)
{
auto numElements = static_cast<int64_t>(input.size());
const std::vector<int64_t> dims = {1, 1, 1, numElements};
auto [graph, x, y] = createReluGraph(graphName, dims);
graph->set_preferred_engine_id_ext("EXAMPLE_PROVIDER_RELU_ENGINE");
auto result = graph->build(handle);
if(!checkGraphResult(result, "build"))
{
return false;
}
// Use Tensor class for GPU memory management (host+device allocation,
// automatic H2D/D2H transfers)
hipdnn_data_sdk::utilities::Tensor<float> xTensor(dims);
xTensor.fillWithData(input.data(), input.size() * sizeof(float));
hipdnn_data_sdk::utilities::Tensor<float> yTensor(dims);
yTensor.fillWithValue(0.0f);
std::unordered_map<int64_t, void*> variantPack;
variantPack[x->get_uid()] = xTensor.memory().deviceData();
variantPack[y->get_uid()] = yTensor.memory().deviceData();
result = graph->execute(handle, variantPack, nullptr);
if(!checkGraphResult(result, "execute"))
{
return false;
}
// Transfer output from device to host via Tensor's managed memory
yTensor.memory().markDeviceModified();
auto yHostPtr = yTensor.memory().hostData();
output.assign(yHostPtr, yHostPtr + input.size());
return true;
}
// Print knob value
static std::string knobValueToString(const KnobValueVariant& value)
{
if(std::holds_alternative<int64_t>(value))
{
return std::to_string(std::get<int64_t>(value));
}
if(std::holds_alternative<double>(value))
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << std::get<double>(value);
return oss.str();
}
if(std::holds_alternative<std::string>(value))
{
return "\"" + std::get<std::string>(value) + "\"";
}
return "(unknown)";
}
// Print a float vector.
static void printVector(const std::string& label, const std::vector<float>& v)
{
std::cout << " " << label << ": [";
for(size_t i = 0; i < v.size(); ++i)
{
if(i > 0)
{
std::cout << ", ";
}
std::cout << v[i];
}
std::cout << "]\n";
}
// Print a 2D matrix stored row-major.
static void printMatrix(const std::string& label, const float* m, int rows, int cols)
{
std::cout << " " << label << " (" << rows << "x" << cols << "):\n";
for(int r = 0; r < rows; ++r)
{
std::cout << " [";
for(int c = 0; c < cols; ++c)
{
if(c > 0)
{
std::cout << ", ";
}
std::cout << std::setw(6) << std::fixed << std::setprecision(1) << m[r * cols + c];
}
std::cout << "]\n";
}
std::cout << "\n";
}
// Query and print loaded plugin paths via the frontend API.
static void printLoadedPlugins(hipdnnHandle_t handle)
{
std::vector<std::filesystem::path> paths;
auto err = getLoadedEnginePluginPaths(handle, paths);
if(err.is_bad() || paths.empty())
{
std::cout << " (no plugins loaded)\n";
return;
}
for(size_t i = 0; i < paths.size(); ++i)
{
std::cout << " [" << i << "] " << paths[i] << "\n";
}
}
// Check whether the example_provider_plugin is present among loaded plugins.
// Returns true if found, false otherwise.
static bool verifyPluginPresence(hipdnnHandle_t handle, const std::string& modeLabel)
{
std::vector<std::filesystem::path> paths;
auto err = getLoadedEnginePluginPaths(handle, paths);
if(err.is_bad() || paths.empty())
{
std::cerr << " ERROR: No plugins loaded after " << modeLabel << "\n";
return false;
}
bool found = false;
for(const auto& path : paths)
{
if(path.string().find("example_provider_plugin") != std::string::npos)
{
found = true;
break;
}
}
if(!found)
{
std::cerr << " ERROR: example_provider_plugin not found among loaded plugins after "
<< modeLabel << "\n";
return false;
}
std::cout << " Plugin verified present after " << modeLabel << "\n";
return true;
}
// ============================================================================
// Scenario 1: ReLU Forward with Engine Selection and Knob Modification
// ============================================================================
static bool scenario1ReluForward(const std::vector<std::string>& pluginPaths)
{
std::cout << "\n=== Scenario 1: ReLU Forward with Engine Selection and Knob Modification ===\n";
std::cout
<< "Demonstrates the explicit 6-step graph build sequence, engine selection by name,\n"
<< "and knob modification for leaky ReLU.\n\n";
// Use ABSOLUTE mode so the example plugin is loaded regardless of
// system-installed plugins
setEnginePluginPaths(pluginPaths, PluginLoadingMode::MODE_ABSOLUTE);
auto [handle, handleErr] = hipdnn_frontend::createHipdnnHandle();
if(handleErr.is_bad())
{
std::cerr << " ERROR: createHipdnnHandle failed\n";
return false;
}
std::vector<int64_t> dims = {1, 1, 1, 6};
auto graph = std::make_shared<hipdnn_frontend::graph::Graph>();
graph->set_name("Scenario1_ReLU")
.set_io_data_type(DataType::FLOAT)
.set_intermediate_data_type(DataType::FLOAT)
.set_compute_data_type(DataType::FLOAT);
auto x = std::make_shared<hipdnn_frontend::graph::TensorAttributes>();
x->set_uid(1)
.set_name("X")
.set_dim(dims)
.set_stride({dims[1] * dims[2] * dims[3], dims[2] * dims[3], dims[3], 1})
.set_data_type(DataType::FLOAT);
hipdnn_frontend::graph::PointwiseAttributes attrs;
attrs.set_name("relu_fwd");
attrs.set_mode(PointwiseMode::RELU_FWD);
auto y = graph->pointwise(x, attrs);
y->set_uid(2).set_data_type(DataType::FLOAT).set_output(true);
// Select the example plugin's GPU ReLU engine by name
std::cout << " Selecting engine: EXAMPLE_PROVIDER_RELU_ENGINE\n\n";
graph->set_preferred_engine_id_ext("EXAMPLE_PROVIDER_RELU_ENGINE");
auto result = graph->validate();
if(!checkGraphResult(result, "validate"))
{
return false;
}
result = graph->build_operation_graph(*handle);
if(!checkGraphResult(result, "build_operation_graph"))
{
return false;
}
// Query available engines and their knobs
std::vector<int64_t> rankedEngineIds;
result = graph->get_ranked_engine_ids(rankedEngineIds);
if(!checkGraphResult(result, "get_ranked_engine_ids"))
{
return false;
}
if(rankedEngineIds.empty())
{
std::cerr << " ERROR: No engines available\n";
return false;
}
const int64_t engineId = rankedEngineIds[0];
std::vector<Knob> knobs;
result = graph->get_knobs_for_engine(engineId, knobs);
if(!checkGraphResult(result, "get_knobs_for_engine"))
{
return false;
}
std::cout << " Engine has " << knobs.size() << " knob(s):\n";
for(const auto& knob : knobs)
{
std::cout << " " << knob.knobId()
<< " (default=" << knobValueToString(knob.defaultValue()) << ")\n";
}
// Set negative_slope = 0.1
std::vector<KnobSetting> settings;
settings.emplace_back("example.relu.negative_slope", 0.1);
std::cout << " Setting example.relu.negative_slope = 0.1\n\n";
result = graph->create_execution_plan_ext(engineId, settings);
if(!checkGraphResult(result, "create_execution_plan_ext"))
{
return false;
}
result = graph->check_support();
if(!checkGraphResult(result, "check_support"))
{
return false;
}
result = graph->build_plans();
if(!checkGraphResult(result, "build_plans"))
{
return false;
}
std::cout << " Graph built successfully.\n";
// GPU execution and verification
std::vector<float> input = {-1.0f, -0.5f, 0.0f, 0.5f, 1.0f, 1.5f};
std::vector<float> output(input.size(), -999.0f);
const size_t bufferSize = input.size() * sizeof(float);
float* dInput = nullptr;
float* dOutput = nullptr;
if(!checkHip(hipMalloc(&dInput, bufferSize), "hipMalloc")
|| !checkHip(hipMalloc(&dOutput, bufferSize), "hipMalloc")
|| !checkHip(hipMemcpy(dInput, input.data(), bufferSize, hipMemcpyHostToDevice),
"hipMemcpy H2D"))
{
static_cast<void>(hipFree(dInput));
static_cast<void>(hipFree(dOutput));
return false;
}
std::unordered_map<int64_t, void*> variantPack;
variantPack[1] = dInput;
variantPack[2] = dOutput;
result = graph->execute(*handle, variantPack, nullptr);
if(!checkGraphResult(result, "execute"))
{
static_cast<void>(hipFree(dInput));
static_cast<void>(hipFree(dOutput));
return false;
}
if(!checkHip(hipMemcpy(output.data(), dOutput, bufferSize, hipMemcpyDeviceToHost),
"hipMemcpy D2H"))
{
static_cast<void>(hipFree(dInput));
static_cast<void>(hipFree(dOutput));
return false;
}
printVector("Input ", input);
printVector("Output", output);
// Verify correctness: ReLU(x) = x >= 0 ? x : 0.1 * x
bool correct = true;
for(size_t i = 0; i < input.size(); ++i)
{
const float expected = input[i] >= 0.0f ? input[i] : 0.1f * input[i];
if(std::abs(output[i] - expected) > 1e-6f)
{
std::cerr << " MISMATCH at [" << i << "]: expected " << expected << ", got "
<< output[i] << "\n";
correct = false;
}
}
if(correct)
{
std::cout << " All outputs match expected ReLU (slope=0.1)\n";
}
static_cast<void>(hipFree(dInput));
static_cast<void>(hipFree(dOutput));
if(!correct)
{
return false;
}
std::cout << "\n Scenario 1 completed successfully.\n";
return true;
}
// ============================================================================
// Scenario 2: Convolution Forward with Engine Selection
// ============================================================================
static bool scenario2ConvForward(const std::vector<std::string>& pluginPaths)
{
std::cout << "\n=== Scenario 2: Convolution Forward with Engine Selection ===\n";
std::cout
<< "Demonstrates ConvFwd graph construction with NCHW/KCRS tensor layouts, the build()\n"
<< "convenience method, Tensor class for GPU memory management, and verification\n"
<< "against hardcoded expected values.\n\n";
// Use ABSOLUTE mode so the example plugin is loaded regardless of
// system-installed plugins
setEnginePluginPaths(pluginPaths, PluginLoadingMode::MODE_ABSOLUTE);
auto [handle, handleErr] = hipdnn_frontend::createHipdnnHandle();
if(handleErr.is_bad())
{
std::cerr << " ERROR: createHipdnnHandle failed\n";
return false;
}
// Dimensions: N=1, C=1, H=4, W=4, K=1, R=3, S=3
// No padding, stride=1, dilation=1 => output 1x1x2x2
// NOLINTBEGIN(readability-identifier-naming)
const int64_t N = 1;
const int64_t C = 1;
const int64_t H = 4;
const int64_t W = 4;
const int64_t K = 1;
const int64_t R = 3;
const int64_t S = 3;
// NOLINTEND(readability-identifier-naming)
const int64_t outH = H - R + 1; // 2
const int64_t outW = W - S + 1; // 2
auto graph = std::make_shared<hipdnn_frontend::graph::Graph>();
graph->set_name("Scenario2_ConvFwd")
.set_io_data_type(DataType::FLOAT)
.set_intermediate_data_type(DataType::FLOAT)
.set_compute_data_type(DataType::FLOAT);
// Input tensor X: NCHW layout (using makeTensorAttributes + generateStrides)
auto xAttr = std::make_shared<hipdnn_frontend::graph::TensorAttributes>(
hipdnn_frontend::graph::makeTensorAttributes(
"X",
DataType::FLOAT,
{N, C, H, W},
hipdnn_data_sdk::utilities::generateStrides({N, C, H, W})));
// Weight tensor W: KCRS layout (using makeTensorAttributes + generateStrides)
auto wAttr = std::make_shared<hipdnn_frontend::graph::TensorAttributes>(
hipdnn_frontend::graph::makeTensorAttributes(
"W",
DataType::FLOAT,
{K, C, R, S},
hipdnn_data_sdk::utilities::generateStrides({K, C, R, S})));
hipdnn_frontend::graph::ConvFpropAttributes convAttrs;
convAttrs.set_padding({0, 0}).set_stride({1, 1}).set_dilation({1, 1});
// Output tensor Y (attributes derived from conv_fprop)
auto yOut = graph->conv_fprop(xAttr, wAttr, convAttrs);
yOut->set_data_type(DataType::FLOAT).set_output(true);
// Select the example plugin's ConvFwd engine by name
std::cout << " Selecting engine: EXAMPLE_PROVIDER_CONV_FWD_ENGINE\n\n";
graph->set_preferred_engine_id_ext("EXAMPLE_PROVIDER_CONV_FWD_ENGINE");
// Use the build() convenience method (contrasts with Scenario 1's explicit
// 6-step sequence, showing both API styles)
auto result = graph->build(*handle);
if(!checkGraphResult(result, "build"))
{
return false;
}
std::cout << " Graph built successfully.\n";
// 4x4 input matrix (values 1..16), managed by Tensor class
// clang-format off
std::vector<float> inputData = {
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f,
9.0f, 10.0f, 11.0f, 12.0f,
13.0f, 14.0f, 15.0f, 16.0f
};
// clang-format on
// 3x3 all-ones filter for easy verification
std::vector<float> weightData(static_cast<size_t>(R * S), 1.0f);
// Use Tensor class for GPU memory management (host+device allocation,
// automatic H2D/D2H transfers) instead of raw hipMalloc/hipMemcpy/hipFree
hipdnn_data_sdk::utilities::Tensor<float> xTensor({N, C, H, W});
xTensor.fillWithData(inputData.data(), inputData.size() * sizeof(float));
hipdnn_data_sdk::utilities::Tensor<float> wTensor({K, C, R, S});
wTensor.fillWithData(weightData.data(), weightData.size() * sizeof(float));
hipdnn_data_sdk::utilities::Tensor<float> yTensor({N, K, outH, outW});
yTensor.fillWithValue(0.0f);
std::unordered_map<int64_t, void*> variantPack;
variantPack[xAttr->get_uid()] = xTensor.memory().deviceData();
variantPack[wAttr->get_uid()] = wTensor.memory().deviceData();
variantPack[yOut->get_uid()] = yTensor.memory().deviceData();
result = graph->execute(*handle, variantPack, nullptr);
if(!checkGraphResult(result, "execute"))
{
return false;
}
// Transfer output from device to host via Tensor's managed memory
yTensor.memory().markDeviceModified();
auto yHostPtr = yTensor.memory().hostData();
printMatrix("Input", inputData.data(), static_cast<int>(H), static_cast<int>(W));
printMatrix("Filter (all ones)", weightData.data(), static_cast<int>(R), static_cast<int>(S));
printMatrix("Output", yHostPtr, static_cast<int>(outH), static_cast<int>(outW));
// With a 4x4 input (values 1-16) and a 3x3 all-ones filter (no padding,
// stride 1), each output element is the sum of a 3x3 window:
// output[0,0] = 1+2+3+5+6+7+9+10+11 = 54
// output[0,1] = 2+3+4+6+7+8+10+11+12 = 63
// output[1,0] = 5+6+7+9+10+11+13+14+15 = 90
// output[1,1] = 6+7+8+10+11+12+14+15+16 = 99
std::vector<float> expected = {54.0f, 63.0f, 90.0f, 99.0f};
bool correct = true;
for(size_t i = 0; i < expected.size(); ++i)
{
if(std::abs(yHostPtr[i] - expected[i]) > 1e-5f)
{
std::cerr << " MISMATCH at [" << i << "]: expected " << expected[i] << ", got "
<< yHostPtr[i] << "\n";
correct = false;
}
}
if(correct)
{
std::cout << " All outputs match expected values {54, 63, 90, 99}\n";
}
if(!correct)
{
return false;
}
std::cout << "\n Scenario 2 completed successfully.\n";
return true;
}
// ============================================================================
// Scenario 3: Plugin Loading Modes
// ============================================================================
static bool scenario3PluginLoadingModes(const std::vector<std::string>& pluginPaths)
{
std::cout << "\n=== Scenario 3: Plugin Loading Modes ===\n";
std::cout << "Demonstrates ADDITIVE and ABSOLUTE loading modes with presence verification "
"after each mode.\n\n";
// ---- ADDITIVE mode ----
std::cout << " --- ADDITIVE mode ---\n";
std::cout << " ADDITIVE mode loads the specified plugin directories alongside\n"
<< " any system-installed plugins. This is the default mode.\n\n";
auto err = setEnginePluginPaths(pluginPaths, PluginLoadingMode::MODE_ADDITIVE);
if(err.is_bad())
{
std::cerr << " ERROR: setEnginePluginPaths (ADDITIVE) failed: " << err.err_msg << "\n";
return false;
}
{
auto [handle, handleErr] = hipdnn_frontend::createHipdnnHandle();
if(handleErr.is_bad())
{
std::cerr << " ERROR: createHipdnnHandle failed (ADDITIVE)\n";
return false;
}
std::cout << " Loaded plugins (ADDITIVE):\n";
printLoadedPlugins(*handle);
if(!verifyPluginPresence(*handle, "ADDITIVE loading"))
{
return false;
}
}
// ---- ABSOLUTE mode ----
std::cout << "\n --- ABSOLUTE mode ---\n";
std::cout << " ABSOLUTE mode replaces all plugin search paths with only the\n"
<< " specified directories. System-installed plugins are ignored.\n\n";
err = setEnginePluginPaths(pluginPaths, PluginLoadingMode::MODE_ABSOLUTE);
if(err.is_bad())
{
std::cerr << " ERROR: setEnginePluginPaths (ABSOLUTE) failed: " << err.err_msg << "\n";
return false;
}
{
auto [handle, handleErr] = hipdnn_frontend::createHipdnnHandle();
if(handleErr.is_bad())
{
std::cerr << " ERROR: createHipdnnHandle failed (ABSOLUTE)\n";
return false;
}
std::cout << " Loaded plugins (ABSOLUTE, only our plugin):\n";
printLoadedPlugins(*handle);
if(!verifyPluginPresence(*handle, "ABSOLUTE loading"))
{
return false;
}
// Run a quick ReLU execution to confirm the loaded plugin is functional
std::cout << "\n Running quick ReLU to confirm plugin functionality...\n";
std::vector<float> input = {-2.0f, 0.0f, 3.0f};
std::vector<float> output;
if(!runReluGraph(*handle, "Scenario3_QuickReLU", input, output))
{
return false;
}
// Verify the output
bool correct = true;
for(size_t i = 0; i < input.size(); ++i)
{
const float expected = std::max(0.0f, input[i]);
if(output[i] != expected)
{
std::cerr << " MISMATCH at [" << i << "]: expected " << expected << ", got "
<< output[i] << "\n";
correct = false;
}
}
if(correct)
{
std::cout << " Quick ReLU verification passed.\n";
}
else
{
return false;
}
}
// ---- Optional: HIPDNN_PLUGIN_DIR environment variable ----
const std::string envPluginDir = hipdnn_data_sdk::utilities::getEnv("HIPDNN_PLUGIN_DIR");
if(!envPluginDir.empty())
{
std::cout << "\n --- HIPDNN_PLUGIN_DIR environment variable ---\n";
std::cout << " HIPDNN_PLUGIN_DIR = " << envPluginDir << "\n";
std::cout << " hipDNN uses this environment variable at handle creation time\n"
<< " to discover plugin shared libraries.\n";
auto [handle, handleErr] = hipdnn_frontend::createHipdnnHandle();
if(handleErr.is_bad())
{
std::cerr << " ERROR: createHipdnnHandle failed (env var)\n";
return false;
}
std::cout << " Loaded plugins (via env var):\n";
printLoadedPlugins(*handle);
}
else
{
std::cout << "\n NOTE: HIPDNN_PLUGIN_DIR is not set.\n"
<< " Set it to demonstrate environment-variable-based plugin loading.\n";
}
std::cout << "\n Scenario 3 completed successfully.\n";
return true;
}
// ============================================================================
// Main
// ============================================================================
int main(int argc, char* argv[])
{
try
{
std::cout << "hipDNN Example Plugin Sample Application\n";
std::cout << "=========================================\n";
RETURN_SUCCESS_IF_NO_DEVICE();
hipDeviceProp_t props;
static_cast<void>(hipGetDeviceProperties(&props, 0));
std::cout << "GPU: " << props.name << " (" << props.gcnArchName << ")\n";
// Determine plugin path: CLI argument > HIPDNN_PLUGIN_DIR > executable directory.
#ifdef _WIN32
const std::string pluginFilename = "example_provider_plugin.dll";
#else
const std::string pluginFilename = "libexample_provider_plugin.so";
#endif
std::string pluginPath;
if(argc > 1)
{
pluginPath = argv[1];
std::cout << "Plugin path (from argument): " << pluginPath << "\n";
}
else
{
pluginPath = hipdnn_data_sdk::utilities::getEnv("HIPDNN_PLUGIN_DIR");
if(!pluginPath.empty())
{
std::cout << "Plugin path (from HIPDNN_PLUGIN_DIR): " << pluginPath << "\n";
}
else
{
auto exeDir = hipdnn_data_sdk::utilities::getCurrentExecutableDirectory();
pluginPath = std::filesystem::absolute(exeDir).string();
std::cout << "Plugin path (from executable location): " << pluginPath << "\n";
}
}
// To assist in debugging issues when running this sample, determine whether
// the plugin exists before running the scenarios.
std::vector<std::string> pluginPaths;
auto fsPath = std::filesystem::path(pluginPath);
if(std::filesystem::is_regular_file(fsPath))
{
if(fsPath.filename().string().find("example_provider_plugin") != std::string::npos)
{
pluginPaths.push_back(pluginPath);
}
}
else if(std::filesystem::is_directory(fsPath))
{
if(std::filesystem::exists(fsPath / pluginFilename))
{
pluginPaths.push_back(pluginPath);
}
}
std::cout << "Plugin paths:\n";
for(size_t i = 0; i < pluginPaths.size(); ++i)
{
std::cout << " [" << i << "] " << pluginPaths[i] << "\n";
}
if(pluginPaths.empty())
{
std::cerr << "\nERROR: Example plugin library (" << pluginFilename
<< ") not found.\n\n";
std::cerr << "Path provided: " << pluginPath << "\n\n";
std::cerr << "Usage: " << argv[0] << " [plugin_path]\n\n"
<< "The plugin path can be:\n"
<< " - A directory containing " << pluginFilename << "\n"
<< " - The full path to the plugin file itself\n\n"
<< "Plugin path resolution (when no argument is provided):\n"
<< " 1. If HIPDNN_PLUGIN_DIR is set, that is used.\n"
<< " 2. Otherwise, the directory containing this executable is used.\n";
return 1;
}
bool allPassed = true;
allPassed = scenario1ReluForward(pluginPaths) && allPassed;
allPassed = scenario2ConvForward(pluginPaths) && allPassed;
allPassed = scenario3PluginLoadingModes(pluginPaths) && allPassed;
std::cout << "\n=========================================\n";
if(allPassed)
{
std::cout << "All scenarios completed successfully.\n";
return 0;
}
std::cerr << "Some scenarios failed.\n";
return 1;
}
catch(const std::exception& e)
{
std::fprintf(stderr, "Fatal error: %s\n", e.what());
return 1;
}
catch(...)
{
std::fprintf(stderr, "Fatal error: unknown exception\n");
return 1;
}
}