Files
TheRock/rocm-libraries/projects/rocthrust/test/bitwise_repro/bwr_utils.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

531 lines
20 KiB
C++

// Copyright (C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef BWR_UTILS_HPP
#define BWR_UTILS_HPP
#include <thrust/host_vector.h>
#include <thrust/rocthrust_version.hpp>
#include <hip/hip_runtime_api.h>
#include <string>
// These macros are be used to get a stringified version of a type name (see get_typename_str below) .
// If data_type matches key_type, then we return a stringified version of key_type.
#define STRING(s) #s
#define IF_TYPE_TEST(data_type, key_type) \
if (std::is_same<data_type, key_type>::value) \
return STRING(key_type);
#define ELSE_IF_TYPE_TEST(data_type, key_type) else IF_TYPE_TEST(data_type, key_type)
namespace bwr_utils
{
// The separator character used in the ROCm and rocThrust versions.
static const std::string ver_sep = std::string(".");
// These members shouldn't need to be called directly from outside this file.
namespace crc
{
/*! \brief Builds a table that's used to cache values for the CRC algorithm.
* \note: We're assuming the system is little-endian here.
* \param table Pointer to the buffer to use to store the table (must be of size 256).
*/
void make_table(uint32_t* table)
{
table[0] = 0;
uint32_t crc = 1;
uint8_t i = 128;
do
{
// Note: The constant below is the polynomial for the
// CRC-32/ISO-HDLC version of the 32-bit algorithm.
if (crc & 1)
{
crc = (crc >> 1) ^ 0xedb88320;
}
else
{
crc >>= 1;
}
for (uint32_t j = 0; j < 256; j += 2 * i)
{
table[i + j] = crc ^ table[j];
}
i >>= 1;
} while (i > 0);
}
/*! \brief Performs a 32-bit cyclic redundancy check on the data it's passed.
* \param data Pointer to the data to compute the check for, as a byte-array.
* \param len Number of bytes in the data buffer.
*/
uint32_t crc(const uint8_t* data, size_t len)
{
// Precompute values we know we'll use frequently and store them in a
// lookup table.
static uint32_t table[256];
static bool table_exists = false;
if (!table_exists)
{
make_table(table);
table_exists = true;
}
// Start with a negated version of (unsigned) 0 to handle the
// case where data contains zeros.
uint32_t crc = 0xffffffff;
for (size_t i = 0; i < len; i++)
{
uint32_t index = (crc ^ data[i]) & 0xff;
crc = (crc >> 8) ^ table[index];
}
// Under the negation we performed at the start.
crc = crc ^ 0xffffffff;
return crc;
}
} // namespace crc
/*! \brief Uses a cyclic redundancy check (CRC) to hash the contents of the buffer passed in.
* \note The result does not include type or length information, which is necessary to uniquely
* identify a buffer (eg. two vectors of zeros that are of different lengths will both
* produce the same CRC hash; two vectors of zeros that are the same length but different integral
* types may also produce the same CRC hash.)
*
* \tparam T Buffer element type
* \param buffer Pointer to the buffer to hash
* \param size Number of elements in the range to hash
* \return String hash value as indicated above
*/
template <typename T>
std::string hash_buffer_crc(T* buffer, const size_t size)
{
const uint8_t* bytes = reinterpret_cast<uint8_t*>(buffer);
const size_t num_bytes = size * sizeof(T);
const uint32_t hash_val = crc::crc(bytes, num_bytes);
std::stringstream sstream;
sstream << hash_val;
return sstream.str();
}
/*! \brief Uses a cyclic redundancy check (CRC) to hash the contents of the buffer passed in.
* \note Includes length, but not type information. The length information is important because
* two zero-containing vectors of different lengths will both hash to zero.
*
* \param begin Iterator pointing to the begining of the range to hash
* \param end Iterator pointing to one-past-the-end of the range to hash
* \return String of the form "hash,length"
*/
template <typename T>
std::string hash_vector(const T begin, const T end)
{
const size_t size = end - begin;
return hash_buffer_crc(thrust::raw_pointer_cast(&(*begin)), size) + "," + std::to_string(size);
}
/*! \brief This function returns a string version of the template parameter typename it's passed.
* \note Because C++ has no reflection/introspection, we must manually define strings for each
* type we want to be able to do this for.
*
* Currently, only types used in tests in test/test_reproducibility.cpp are supported here.
* Input types passed passed to TokenHelper (below) also need to be defined here.
*
* \tparam The type to find a string name for.
* \return String version of the type, or empty string if not found.
*/
template <typename T>
std::string get_typename_str()
{
IF_TYPE_TEST(T, thrust::host_vector<short>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<int>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<long long>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<unsigned short>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<unsigned int>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<unsigned long long>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<float>)
ELSE_IF_TYPE_TEST(T, thrust::host_vector<double>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<short>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<int>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<long long>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<unsigned short>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<unsigned int>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<unsigned long long>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<float>)
ELSE_IF_TYPE_TEST(T, thrust::device_vector<double>)
ELSE_IF_TYPE_TEST(T, short)
ELSE_IF_TYPE_TEST(T, int)
ELSE_IF_TYPE_TEST(T, long long)
ELSE_IF_TYPE_TEST(T, unsigned short)
ELSE_IF_TYPE_TEST(T, unsigned int)
ELSE_IF_TYPE_TEST(T, unsigned long long)
ELSE_IF_TYPE_TEST(T, float)
ELSE_IF_TYPE_TEST(T, double)
else throw std::runtime_error("Unable to lookup type name in get_typename_str()");
return "";
}
/*! \brief Builds and returns a string containing the rocThrust version number.
*
* \return String of the form "<major version>.<minor version>.<patch version>".
*/
std::string get_rocthrust_version()
{
static const std::string rocthrust_ver =
std::to_string(ROCTHRUST_VERSION_MAJOR) + ver_sep + std::to_string(ROCTHRUST_VERSION_MINOR) + ver_sep
+ std::to_string(ROCTHRUST_VERSION_PATCH);
return rocthrust_ver;
}
/*! \brief Builds and returns a string containing the GPU architecture.
*
* \return String of the form "gfx<arch number>".
*/
std::string get_gpu_arch()
{
hipDeviceProp_t device_prop;
if (hipGetDeviceProperties(&device_prop, 0) != hipSuccess)
{
throw std::runtime_error("hipGetDeviceProperties failure");
}
static const std::string gpu_arch(device_prop.gcnArchName);
return gpu_arch;
}
/*! \brief Builds and returns a string containing the ROCm version number.
* \note We intentionally omit the "HIP_PATCH_VERSION" here, since we don't
* anticipate results to change at that granularity, and storing results at
* that granularity would require frequent database updates.
*
* \return String of the form "<major version>.<minor version>".
*/
std::string get_rocm_version()
{
static const std::string runtime_ver =
std::to_string(HIP_VERSION_MAJOR) + ver_sep + std::to_string(HIP_VERSION_MINOR);
return runtime_ver;
}
/*! \brief Returns a "token" string the uniquely identifies a scalar value.
*
* \tparam T Type of the scalar
* \param val Scalar value to hash
* \return String of the form "scalar<typename>(value)"
*/
template <typename T>
std::string get_scalar_token(const T& val)
{
return "scalar<" + get_typename_str<T>() + ">(" + std::to_string(val) + ")";
}
/*! \brief Returns a "token" string the uniquely identifies a vector.
* \note This version accepts an existing hash string and type string
* (obtained from hash_vector and get_typename_str, respectively). This
* allows callers to make multiple calls to this function without invoking
* hash_vector multiple times, since it may be expensive if the vector is
* large.
*
* \param vec_hash Hash string for the vector, obtained from hash_vector.
* \param data_type Data type name string, obtained from get_typename_str.
* \return String of the form "vector<typename>(hash)".
*/
std::string get_vector_token(const std::string& vec_hash, const std::string& data_type)
{
return "vector<" + data_type + ">(" + vec_hash + ")";
}
/*! \brief Returns a "token" string the uniquely identifies a vector iterator.
* \note This version accepts an existing hash string and type string
* (obtained from hash_vector and get_typename_str, respectively). This
* allows callers to make multiple calls to this function without invoking
* hash_vector multiple times, since it may be expensive if the vector is
* large.
*
* \param vec_hash Hash string for the vector, obtained from hash_vector.
* \param data_type Data type name string, obtained from get_typename_str.
* \param offset The iterator's current offset from the beginning of the vector.
* \return String of the form "iter(vector<typename>(vec_hash),offset)".
*/
std::string get_iterator_token(const std::string& vec_hash, const std::string& data_type, const size_t offset)
{
return "iter(" + get_vector_token(vec_hash, data_type) + "," + std::to_string(offset) + ")";
}
/*! \brief Returns a "token" string that uniquely identifies a functor (callable object).
*
* \tparam T The data type that the functor operates on.
* \param functor_type String representing the functor type, without the <T> datatype (eg. "thrust::plus" for functor
* thrust::plus<T>). \return Strin gof the form "function<typename>(function_type)"
*/
template <typename T>
std::string get_functor_token(const std::string& functor_type)
{
return "functor<" + get_typename_str<T>() + ">(" + functor_type + ")";
}
/*! \brief Builds a compound string hash value from existing string hashes.
*
* \param begin Iterator pointing to the beginning of the vector of string hashes to combine
* \param end Iterator pointing to the end of the vector of string hashes to combine
* \return String hash value of the form "(<hash 0>,<hash1>)"
*/
std::string build_compound_token(const std::vector<std::string>::const_iterator begin,
const std::vector<std::string>::const_iterator end)
{
std::string token = "(";
if (begin != end)
{
for (auto it = begin; it != end; it++)
{
token += *it;
if (it + 1 != end)
{
token += ",";
}
}
}
token += ")";
return token;
}
/*! \brief Builds a string "token" from a function call's inputs. This token is unique to a particular function call.
*
* \param list Vector of strings where the first element is the function name, and the remainder are hashes representing
* inputs to the function call. \return String hash value of the form "fcn_name(<hash 0>,<hash1>,...)"
*/
std::string build_input_token(const std::vector<std::string>& list)
{
return *(list.begin()) + build_compound_token(list.begin() + 1, list.end());
}
/*! \brief Builds a string "token" from a function call's outputs. This token is unique to a particular return value.
*
* \param list Vector of string hashes representing outputs to the function call.
* \return String hash value of the form "(<hash 0>,<hash1>,...)"
*/
std::string build_output_token(const std::vector<std::string>& list)
{
return build_compound_token(list.begin(), list.end());
}
/*! \brief This class helps create input and output tokens representing a function call.
* You can call build_input_token and build_output_token, passing them string and iterators,
* and the class will combine them together into a single token.
* Eg.
* \code
* // Suppose we want to record this call:
* std::vector<int> input = {...};
* thrust::inclusive_scan(policy, d_input.begin(), d_input.end(), d_output.begin(), thrust::plus<int>);
*
* TokenHelper helper;
* token_helper.build_input_token(
* "thrust::inclusive_scan",
* d_input.begin(),
* d_input.end(),
* {bwr_utils::get_functor_token<T>("thrust::plus")}
* );
*
* token_helper.build_output_token(d_output.begin(), d_output.size());
*
* // Can access the input/output tokens using:
* std::string input_token = helper.get_input_token();
* std::string output_Token = helper.get_output_token();
* \endcode
*/
class TokenHelper
{
public:
TokenHelper() = default;
~TokenHelper() = default;
/*! \brief Returns a "token" string that uniquely identifies a function call's inputs.
* \note It's assumed that you'll pass in all the function call's inputs in the order they appear in the call from
* left to right.
*
* \tparam Iter The input vector iterator type
* \param fcn_name The name of the function being called
* \param input_begin Iterator to the beginning of the input data vector
* \param input_end Iterator to the end of the input data vector
* \param extra_inputs A vector of (ordered) extra input tokens obtained from the bwr_utils::get_*_token functions.
*/
template <typename Iter>
void build_input_token(const std::string& fcn_name,
const Iter input_begin,
const Iter input_end,
std::vector<std::string>&& extra_inputs = {})
{
save_input_token(fcn_name, input_begin, input_end, {}, std::forward<std::vector<std::string>&&>(extra_inputs));
}
/*! \brief Returns a "token" string that uniquely identifies a function call's inputs. This version accepts both
* values and keys. \note It's assumed that you'll pass in all the function call's inputs in the order they appear in
* the call from left to right.
*
* \tparam KeyIter The key input vector iterator type
* \tparam ValueIter The value input vector iterator type
* \param fcn_name The name of the function being called
* \param key_input_begin Iterator to the beginning of the key input data vector
* \param key_input_end Iterator to the end of the key input data vector
* \param key_input_begin Iterator to the beginning of the value input data vector
* \param extra_inputs A vector of (ordered) extra input tokens obtained from the bwr_utils::get_*_token functions.
*/
template <typename KeyIter, typename ValueIter>
void build_input_token(
const std::string& fcn_name,
const KeyIter key_input_begin,
const KeyIter key_input_end,
const ValueIter value_input_begin,
std::vector<std::string>&& extra_inputs = {})
{
save_input_token(
fcn_name,
key_input_begin,
key_input_end,
std::vector<ValueIter>({value_input_begin}),
std::forward<std::vector<std::string>&&>(extra_inputs));
}
/*! \brief Returns a "token" string that uniquely identifies a function call's output.
*
* \tparam Iter The output vector iterator type
* \param output_begin Iterator to the beginning of the output data vector
* \param size Number of elements in the output vector.
*/
template <typename Iter>
void build_output_token(const Iter output_begin, const size_t size)
{
save_output_token(output_begin, {}, size);
}
/*! \brief Returns a "token" string that uniquely identifies a function call's outputs. This version accepts both
* values and keys.
*
* \tparam KeyIter The key output vector iterator type
* \tparam ValueIter The value output vector iterator type
* \param key_output_begin Iterator to the beginning of the key output data vector
* \param value_output_begin Iterator to the beginning of the value output data vector
*/
template <typename KeyIter, typename ValueIter>
void build_output_token(const KeyIter key_output_begin, const ValueIter value_output_begin, const size_t size)
{
save_output_token(key_output_begin, std::vector<ValueIter>({value_output_begin}), size);
}
/*! \brief Retrieves the input token that was generated by the last call to build_input_token().
*
* \return The input token as described above, or the empty string if build_input_token hasn't been called yet.
*/
std::string get_input_token() const
{
return m_input_token;
}
/*! \brief Retrieves the output token that was generated by the last call to build_output_token().
*
* \return The output token as described above, or the empty string if build_output_token hasn't been called yet.
*/
std::string get_output_token() const
{
return m_output_token;
}
private:
template <typename KeyIter, typename ValueIter = KeyIter>
void save_input_token(
std::string fcn_name,
KeyIter key_input_begin,
KeyIter key_input_end,
std::vector<ValueIter> value_begins,
std::vector<std::string>&& extra_inputs = {})
{
using KeyDataType = typename std::iterator_traits<KeyIter>::value_type;
using ValueDataType = typename std::iterator_traits<ValueIter>::value_type;
const std::string key_data_type = get_typename_str<KeyDataType>();
const size_t size = key_input_end - key_input_begin;
const std::string key_input_data_hash = bwr_utils::hash_vector(key_input_begin, key_input_end);
// Build a vector of tokens to pass to bwr_utils::build_input_token.
// We will always have the key iterators:
std::vector<std::string> subtokens = {
fcn_name,
bwr_utils::get_iterator_token(key_input_data_hash, key_data_type, 0),
bwr_utils::get_iterator_token(key_input_data_hash, key_data_type, size)};
// But we may or may not have a value iterator:
for (auto value_input_begin : value_begins)
{
const std::string value_data_type = get_typename_str<ValueDataType>();
const std::string value_input_data_hash = bwr_utils::hash_vector(value_input_begin, value_input_begin + size);
subtokens.push_back(bwr_utils::get_iterator_token(value_input_data_hash, value_data_type, 0));
}
subtokens.insert(
subtokens.end(), std::make_move_iterator(extra_inputs.begin()), std::make_move_iterator(extra_inputs.end()));
// Save the resulting compound token so that it can be retrieved later
m_input_token = bwr_utils::build_input_token(subtokens);
}
template <typename KeyIter, typename ValueIter = KeyIter>
void save_output_token(const KeyIter key_output_begin, std::vector<ValueIter> value_begins, const size_t size)
{
using KeyDataType = typename std::iterator_traits<KeyIter>::value_type;
using ValueDataType = typename std::iterator_traits<ValueIter>::value_type;
const std::string key_output_data_hash = bwr_utils::hash_vector(key_output_begin, key_output_begin + size);
const std::string key_data_type = bwr_utils::get_typename_str<KeyDataType>();
// Build a vector of tokens to pass to bwr_utils::build_output_token.
// We will always have the key iterators:
std::vector<std::string> subtokens = {
bwr_utils::get_vector_token(key_output_data_hash, key_data_type),
};
// But we may or may not have a value iterator:
for (auto value_output_begin : value_begins)
{
const std::string value_data_type = bwr_utils::get_typename_str<ValueDataType>();
const std::string value_output_data_hash = bwr_utils::hash_vector(value_output_begin, value_output_begin + size);
subtokens.push_back(bwr_utils::get_vector_token(value_output_data_hash, value_data_type));
}
// Save the resulting compound token so that it can be retrieved later
m_output_token = bwr_utils::build_output_token(subtokens);
}
std::string m_input_token;
std::string m_output_token;
};
} // end namespace bwr_utils
#endif // BRW_UTILS_HPP