Skip to content

Add a DeviceHash class for GPU hashing#1583

Merged
publixsubfan merged 8 commits into
developfrom
feature/yang39/device-hash
Jun 10, 2025
Merged

Add a DeviceHash class for GPU hashing#1583
publixsubfan merged 8 commits into
developfrom
feature/yang39/device-hash

Conversation

@publixsubfan

Copy link
Copy Markdown
Contributor

Summary

  • Adds axom::DeviceHash for supporting hashing certain primitive types (integers, floating points, enums) on the GPU
    • Mimics the libc++ behavior for std::hash, which is to return the identity of the hashed value (casted to axom::IndexType)
    • For unsupported types, just wraps a call to std::hash::operator ()

@publixsubfan publixsubfan added Core Issues related to Axom's 'core' component GPU Issues related to GPU development labels May 25, 2025
Comment thread src/axom/core/tests/core_device_hash.hpp Outdated

@kennyweiss kennyweiss left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @publixsubfan

It's be helpful to see how DeviceHash could be used w/ a composite type, e.g. a simple 3D-point type.

Please also update the RELEASE-NOTES

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a test to show how one might extend this to new types, e.g. to a Point struct w/ x-, y- and z fields, or to an axom::NumericalArray ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial thought is that for user-provided types, we could ask users to create a template specialization of axom:::DeviceHash; if we wanted to provide a "default" hash for our internal types, we could specialize the helper function axom::detail::DeviceHashHelper.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks -- I don't think we need to add any for our internal types to this PR, just thought it'd be helpful to add a simple example to show how one might do it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a small test as an example of a user-defined hash specialization. The main caveat is that the DeviceHash specialization must be defined within the axom namespace.

using result_type = axom::IndexType;
AXOM_HOST_DEVICE axom::IndexType operator()(T value) const
{
// Special case: -0.0 and 0.0 compare equal but have different byte representations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

using result_type = axom::IndexType;
AXOM_HOST_DEVICE axom::IndexType operator()(T* ptr) const
{
return reinterpret_cast<axom::IndexType>(ptr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not static_cast here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall there might have been an issue with the pointer-to-int static_cast losing precision, and triggering a warning. I'll take a look again.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a big deal. I was just curious if there was an issue with static_cast in this context.

// Invocations of the hash function should be idempotent.
EXPECT_EQ(computed_hashes[i], device_hasher(things_to_hash[i]));

// Check that we don't have hash collisions with other values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud: is there a good way to check whether there could be collisions with two arbitrary, but different, integral values?


TEST(core_device_hash, hash_int)
{
axom::DeviceHash<int> device_hasher;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason not to unify all these tests to a single routine that takes a generic container templated on its value type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the conversions between different types, as well as enum handling, might be a little ugly. Also, there is a special case for floating points that we have to test (DeviceHash(-0.0) == DeviceHash(0.0))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

understood. I didn't look at it too closely.

@Arlie-Capps Arlie-Capps left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @publixsubfan .

@publixsubfan
publixsubfan force-pushed the feature/yang39/device-hash branch 3 times, most recently from fc206bb to e48fd46 Compare June 3, 2025 23:30
Comment on lines +192 to +219
namespace
{
template <typename T>
struct UserVector
{
T x, y, z;
};
} // namespace

// Test that we can correctly specialize a device hash for a user-defined type.
namespace axom
{
template <typename T>
struct DeviceHash<UserVector<T>>
{
using argument_type = UserVector<T>;
using result_type = axom::IndexType;

AXOM_HOST_DEVICE axom::IndexType operator()(UserVector<T> value) const
{
// Copy byte representation over
constexpr int NWORDS = sizeof(UserVector<T>) / sizeof(int);
alignas(UserVector<T>) int bytes[NWORDS];
*reinterpret_cast<UserVector<T> *>(bytes) = value;

axom::IndexType hash_result {};
for(int i = 0; i < NWORDS; i++)
{
hash_result ^= (bytes[i] + 0x853c49e6);
}
return hash_result;
}
};
} // namespace axom

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @publixsubfan !

@publixsubfan
publixsubfan force-pushed the feature/yang39/device-hash branch from af34933 to 619a876 Compare June 10, 2025 00:35
@publixsubfan
publixsubfan merged commit 2205a6e into develop Jun 10, 2025
15 checks passed
@publixsubfan
publixsubfan deleted the feature/yang39/device-hash branch June 10, 2025 03:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Core Issues related to Axom's 'core' component GPU Issues related to GPU development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants