Files
TheRock/rocm-libraries/projects/hipdnn/frontend/include/hipdnn_frontend/detail/KnobUnpacker.hpp
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

353 lines
14 KiB
C++

// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#pragma once
#include <hipdnn_frontend/Error.hpp>
#include <hipdnn_frontend/Logging.hpp>
#include <hipdnn_frontend/detail/BackendWrapper.hpp>
#include <hipdnn_frontend/detail/DescriptorUnpackHelpers.hpp>
#include <hipdnn_frontend/detail/ScopedHipdnnBackendDescriptor.hpp>
#include <hipdnn_frontend/knob/Knob.hpp>
#include <hipdnn_frontend/knob/KnobConstraint.hpp>
#include <hipdnn_frontend/knob/KnobSetting.hpp>
#include <optional>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>
namespace hipdnn_frontend::detail
{
/// Unpacks int64 default value and constraint fields from a knob descriptor.
[[nodiscard]] inline std::pair<Error, std::pair<KnobValueVariant, std::shared_ptr<IConstraint>>>
unpackIntKnobFields(hipdnnBackendDescriptor_t knobDesc)
{
int64_t intVal = 0;
HIPDNN_FE_TRY(getDescriptorAttrScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_DEFAULT_VALUE,
HIPDNN_TYPE_INT64,
intVal,
"knob info default value (int64)"));
std::optional<int64_t> minVal;
HIPDNN_FE_TRY(getDescriptorAttrOptionalScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_MINIMUM_VALUE,
HIPDNN_TYPE_INT64,
minVal,
"knob info min value (int64)"));
std::optional<int64_t> maxVal;
HIPDNN_FE_TRY(getDescriptorAttrOptionalScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_MAXIMUM_VALUE,
HIPDNN_TYPE_INT64,
maxVal,
"knob info max value (int64)"));
std::optional<int64_t> stride;
HIPDNN_FE_TRY(getDescriptorAttrOptionalScalar(
knobDesc, HIPDNN_ATTR_KNOB_INFO_STRIDE, HIPDNN_TYPE_INT64, stride, "knob info stride"));
std::vector<int64_t> validValuesVec;
HIPDNN_FE_TRY(getDescriptorAttrVec(knobDesc,
HIPDNN_ATTR_KNOB_INFO_VALID_VALUES_INT,
validValuesVec,
"knob info valid values (int64)"));
std::shared_ptr<IConstraint> constraint = std::make_shared<EmptyConstraint>();
if(minVal.has_value() || maxVal.has_value() || stride.has_value() || !validValuesVec.empty())
{
std::unordered_set<int64_t> validValues(validValuesVec.begin(), validValuesVec.end());
constraint = std::make_shared<IntConstraint>(
minVal.value_or(0), maxVal.value_or(0), stride.value_or(1), std::move(validValues));
}
return {{ErrorCode::OK, ""}, {KnobValueVariant{intVal}, std::move(constraint)}};
}
/// Unpacks double default value and constraint fields from a knob descriptor.
[[nodiscard]] inline std::pair<Error, std::pair<KnobValueVariant, std::shared_ptr<IConstraint>>>
unpackFloatKnobFields(hipdnnBackendDescriptor_t knobDesc)
{
double doubleVal = 0.0;
HIPDNN_FE_TRY(getDescriptorAttrScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_DEFAULT_VALUE,
HIPDNN_TYPE_DOUBLE,
doubleVal,
"knob info default value (double)"));
std::optional<double> minVal;
HIPDNN_FE_TRY(getDescriptorAttrOptionalScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_MINIMUM_VALUE,
HIPDNN_TYPE_DOUBLE,
minVal,
"knob info min value (double)"));
std::optional<double> maxVal;
HIPDNN_FE_TRY(getDescriptorAttrOptionalScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_MAXIMUM_VALUE,
HIPDNN_TYPE_DOUBLE,
maxVal,
"knob info max value (double)"));
std::shared_ptr<IConstraint> constraint = std::make_shared<EmptyConstraint>();
if(minVal.has_value() || maxVal.has_value())
{
constraint = std::make_shared<FloatConstraint>(minVal.value_or(0.0), maxVal.value_or(0.0));
}
return {{ErrorCode::OK, ""}, {KnobValueVariant{doubleVal}, std::move(constraint)}};
}
/// Unpacks string default value and constraint fields from a knob descriptor.
[[nodiscard]] inline std::pair<Error, std::pair<KnobValueVariant, std::shared_ptr<IConstraint>>>
unpackStringKnobFields(hipdnnBackendDescriptor_t knobDesc, const std::string& knobId)
{
std::string strVal;
HIPDNN_FE_TRY(getDescriptorAttrString(
knobDesc, HIPDNN_ATTR_KNOB_INFO_DEFAULT_VALUE, strVal, "knob info default value (string)"));
std::optional<int32_t> stringMaxLength;
HIPDNN_FE_TRY(getDescriptorAttrOptionalScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_STRING_MAX_LENGTH,
HIPDNN_TYPE_INT32,
stringMaxLength,
"knob info string max length"));
// Read valid values string - uses the raw C-API since the format
// is a null-separated buffer
std::vector<std::string> validValuesList;
{
int64_t count = 0;
auto countStatus
= hipdnnBackend()->backendGetAttribute(knobDesc,
HIPDNN_ATTR_KNOB_INFO_VALID_VALUES_STRING,
HIPDNN_TYPE_CHAR,
0,
&count,
nullptr);
if(countStatus != HIPDNN_STATUS_SUCCESS && countStatus != HIPDNN_STATUS_NOT_SUPPORTED)
{
return {{ErrorCode::HIPDNN_BACKEND_ERROR,
"Knob '" + knobId + "': failed to query valid string values count"},
{}};
}
if(countStatus == HIPDNN_STATUS_SUCCESS && count > 0)
{
std::vector<char> buffer(static_cast<size_t>(count));
int64_t actualCount = 0;
auto getStatus
= hipdnnBackend()->backendGetAttribute(knobDesc,
HIPDNN_ATTR_KNOB_INFO_VALID_VALUES_STRING,
HIPDNN_TYPE_CHAR,
count,
&actualCount,
buffer.data());
if(getStatus != HIPDNN_STATUS_SUCCESS)
{
return {{ErrorCode::HIPDNN_BACKEND_ERROR,
"Knob '" + knobId + "': failed to read valid string values"},
{}};
}
if(actualCount > 0 && static_cast<size_t>(actualCount) <= buffer.size())
{
// Parse null-separated string buffer
const char* data = buffer.data();
const char* end = data + actualCount;
while(data < end)
{
std::string val(data);
if(!val.empty())
{
validValuesList.push_back(std::move(val));
}
data += std::strlen(data) + 1;
}
}
}
}
std::shared_ptr<IConstraint> constraint = std::make_shared<EmptyConstraint>();
if(stringMaxLength.has_value() || !validValuesList.empty())
{
std::unordered_set<std::string> validValues(validValuesList.begin(), validValuesList.end());
constraint = std::make_shared<StringConstraint>(stringMaxLength.value_or(0),
std::move(validValues));
}
return {{ErrorCode::OK, ""}, {KnobValueVariant{std::move(strVal)}, std::move(constraint)}};
}
/// Unpacks a finalized HIPDNN_BACKEND_KNOB_INFO_DESCRIPTOR into a frontend Knob.
///
/// This lifts knob metadata from a backend KnobDescriptor by reading each
/// attribute via the C-API getAttribute pattern. It reconstructs the frontend
/// Knob with its ID, description, default value, deprecation flag, and
/// constraints directly from descriptor attributes.
///
/// @param knobDesc A finalized knob info backend descriptor
/// @return Pair of error and unpacked knob. On failure, the optional is empty.
[[nodiscard]] inline std::pair<Error, std::optional<Knob>>
unpackKnobDescriptor(hipdnnBackendDescriptor_t knobDesc)
{
// Read knob ID — cannot use HIPDNN_FE_TRY because we need knobId
// for the empty-check immediately after.
std::string knobId;
auto err
= getDescriptorAttrString(knobDesc, HIPDNN_ATTR_KNOB_INFO_TYPE, knobId, "knob info ID");
if(err.is_bad())
{
return {err, std::nullopt};
}
if(knobId.empty())
{
return {{ErrorCode::INVALID_VALUE, "Knob info descriptor has empty knob ID"}, std::nullopt};
}
// Read description
std::string description;
HIPDNN_FE_TRY(getDescriptorAttrString(
knobDesc, HIPDNN_ATTR_KNOB_INFO_DESCRIPTION, description, "knob info description"));
// Read deprecated flag
bool deprecated = false;
HIPDNN_FE_TRY(getDescriptorAttrScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_DEPRECATED,
HIPDNN_TYPE_BOOLEAN,
deprecated,
"knob info deprecated flag"));
// Read the default value type first
int64_t defaultValueTypeRaw = 0;
HIPDNN_FE_TRY(getDescriptorAttrScalar(knobDesc,
HIPDNN_ATTR_KNOB_INFO_DEFAULT_VALUE_TYPE,
HIPDNN_TYPE_INT64,
defaultValueTypeRaw,
"knob info default value type"));
const auto defaultValueType = static_cast<hipdnnBackendAttributeType_t>(defaultValueTypeRaw);
KnobValueVariant defaultValue;
std::shared_ptr<IConstraint> constraint;
// Read the default value and matching constraint fields based on type.
switch(defaultValueType)
{
case HIPDNN_TYPE_INT64:
{
auto [fieldErr, fields] = unpackIntKnobFields(knobDesc);
if(fieldErr.is_bad())
{
return {fieldErr, std::nullopt};
}
defaultValue = std::move(fields.first);
constraint = std::move(fields.second);
break;
}
case HIPDNN_TYPE_DOUBLE:
{
auto [fieldErr, fields] = unpackFloatKnobFields(knobDesc);
if(fieldErr.is_bad())
{
return {fieldErr, std::nullopt};
}
defaultValue = std::move(fields.first);
constraint = std::move(fields.second);
break;
}
case HIPDNN_TYPE_CHAR:
{
auto [fieldErr, fields] = unpackStringKnobFields(knobDesc, knobId);
if(fieldErr.is_bad())
{
return {fieldErr, std::nullopt};
}
defaultValue = std::move(fields.first);
constraint = std::move(fields.second);
break;
}
default:
return {{ErrorCode::INVALID_VALUE,
"Knob '" + knobId
+ "' has unknown default value type: " + std::to_string(defaultValueTypeRaw)},
std::nullopt};
}
auto [knobErr, knob] = Knob::tryCreate(
knobId, description, std::move(defaultValue), deprecated, std::move(constraint));
if(knobErr.is_bad())
{
return {knobErr, std::nullopt};
}
return {{}, std::move(knob)};
}
/// Unpacks knob descriptors from a backend engine descriptor into Knob objects.
///
/// @param engineDesc A finalized engine backend descriptor
/// @param outKnobs Output vector of Knob objects
/// @return Error on failure, empty Error on success
[[nodiscard]] inline Error unpackKnobsFromDescriptors(hipdnnBackendDescriptor_t engineDesc,
std::vector<Knob>& outKnobs)
{
auto [knobDescs, err] = getDescriptorAttrDescArray(
engineDesc, HIPDNN_ATTR_ENGINE_KNOB_INFO, "knob info descriptors");
if(err.is_bad())
{
outKnobs.clear();
return err;
}
std::vector<Knob> unpackedKnobs;
unpackedKnobs.reserve(knobDescs.size());
std::unordered_set<std::string> usedKnobIds;
size_t skippedCount = 0;
for(const auto& knobDesc : knobDescs)
{
auto [knobErr, knob] = unpackKnobDescriptor(knobDesc.get());
if(knobErr.is_bad())
{
HIPDNN_FE_LOG_WARN("Skipping knob: unpack failed: " << knobErr.get_message());
++skippedCount;
continue;
}
if(!knob.has_value())
{
HIPDNN_FE_LOG_WARN("Skipping knob: unpack returned empty result");
++skippedCount;
continue;
}
if(!usedKnobIds.insert(knob->knobId()).second)
{
HIPDNN_FE_LOG_WARN("Skipping knob with duplicate ID: " << knob->knobId());
++skippedCount;
continue;
}
unpackedKnobs.push_back(std::move(knob.value()));
}
outKnobs = std::move(unpackedKnobs);
if(skippedCount > 0)
{
std::string summary = "Loaded " + std::to_string(outKnobs.size()) + " knobs, skipped "
+ std::to_string(skippedCount) + " invalid/duplicate knobs";
HIPDNN_FE_LOG_WARN(summary);
return {ErrorCode::OK, std::move(summary)};
}
return {ErrorCode::OK, ""};
}
} // namespace hipdnn_frontend::detail