Files
TheRock/rocm-systems/projects/hip-tests/catch/unit/graph/hipGraphMemsetNodeFlatAlloc.cc
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

451 lines
16 KiB
C++

/*
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
/**
* @addtogroup GraphTest GraphTest
* @{
* Coverage for 2D/3D memset graph nodes whose destination is a "flat"
* allocation (plain hipMalloc, hipHostMalloc, hipMallocManaged) rather than
* a pitched/3D allocation. These cases regressed because internal validation
* consulted per-allocation 2D extent metadata that is only populated by
* hipMallocPitch / hipMalloc3D.
*
* The tests below also pin down hipGraphExecUpdate's contract for memset
* nodes (CUDA: "For 2d memsets, only address and assigned value may be
* updated"), and the equivalent contract reached through
* hipGraphExecNodeSetParams.
*/
#include <hip_test_common.hh>
#include <resource_guards.hh>
#include <utils.hh>
namespace {
// Allocates `pitch * height` bytes via plain hipMalloc, with `pitch` chosen
// independently of any device alignment requirement -- so the 2D extent
// metadata that hipMallocPitch would have recorded is left at zero.
struct FlatPitched {
void* ptr = nullptr;
size_t pitch = 0;
size_t width_bytes = 0;
size_t height = 0;
FlatPitched(size_t width_bytes_, size_t height_, size_t align = 512)
: width_bytes(width_bytes_), height(height_) {
pitch = ((width_bytes + align - 1) / align) * align;
HIP_CHECK(hipMalloc(&ptr, pitch * height));
}
~FlatPitched() { static_cast<void>(hipFree(ptr)); }
FlatPitched(const FlatPitched&) = delete;
FlatPitched(FlatPitched&&) = delete;
};
void Verify2DMemsetResult(void* dst, size_t pitch, size_t width_bytes, size_t height,
unsigned char expected) {
std::vector<unsigned char> host(width_bytes * height);
HIP_CHECK(hipMemcpy2D(host.data(), width_bytes, dst, pitch, width_bytes, height,
hipMemcpyDeviceToHost));
ArrayFindIfNot(host.data(), expected, width_bytes * height);
}
hipMemsetParams Make2DParams(void* dst, size_t pitch, size_t width, size_t height,
unsigned int element_size, unsigned int value) {
hipMemsetParams p = {};
p.dst = dst;
p.pitch = pitch;
p.elementSize = element_size;
p.width = width;
p.height = height;
p.value = value;
return p;
}
} // namespace
/**
* Test Description
* ------------------------
* - hipGraphMemsetNodeSetParams (template-edit, isExec=false path) accepts a
* 2D memset whose destination is plain hipMalloc memory. Previously the
* implementation rejected this with hipErrorInvalidValue because it
* consulted per-allocation extent metadata that hipMalloc never sets.
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphMemsetNodeSetParams_FlatAlloc_2D_Positive) {
constexpr size_t width_bytes = 456;
constexpr size_t height = 123;
FlatPitched alloc(width_bytes, height);
hipGraph_t graph = nullptr;
HIP_CHECK(hipGraphCreate(&graph, 0));
// Seed node with placeholder params (1D so AddMemsetNode trivially accepts).
auto initial = Make2DParams(alloc.ptr, alloc.pitch, 1, 1, 1, 0);
hipGraphNode_t node = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&node, graph, nullptr, 0, &initial));
// Now reshape to a real 2D memset on the same flat allocation.
auto reshaped = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, 0xAB);
HIP_CHECK(hipGraphMemsetNodeSetParams(node, &reshaped));
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiate(&exec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(exec, hipStreamPerThread));
HIP_CHECK(hipStreamSynchronize(hipStreamPerThread));
Verify2DMemsetResult(alloc.ptr, alloc.pitch, width_bytes, height, 0xAB);
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(graph));
}
/**
* Test Description
* ------------------------
* - When the destination IS a hipMallocPitch allocation, the original
* declared-extent enforcement must remain: requesting a 2D memset whose
* width/height exceeds the declared extent is rejected. Regression guard
* against the FlatAlloc fix above accidentally weakening pitched-alloc
* validation.
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphMemsetNodeSetParams_PitchedAlloc_OverExtent_Negative) {
constexpr size_t width = 64;
constexpr size_t height = 16;
LinearAllocGuard2D<unsigned char> alloc(width, height); // hipMallocPitch
hipGraph_t graph = nullptr;
HIP_CHECK(hipGraphCreate(&graph, 0));
auto initial = Make2DParams(alloc.ptr(), alloc.pitch(), width, height, 1, 0);
hipGraphNode_t node = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&node, graph, nullptr, 0, &initial));
auto over = Make2DParams(alloc.ptr(), alloc.pitch(), width * 2, height * 2, 1, 0);
HIP_CHECK_ERROR(hipGraphMemsetNodeSetParams(node, &over), hipErrorInvalidValue);
HIP_CHECK(hipGraphDestroy(graph));
}
/**
* Test Description
* ------------------------
* - Reproduces the original test1.cpp scenario: build two stream-captured
* graphs that both invoke hipMemset2DAsync on the same plain hipMalloc
* pointer with identical parameters, instantiate the first, then update
* the executable graph using the second. Should succeed.
* - Exercises hipGraphExecUpdate's polymorphic SetParams(GraphNode*) path
* on a memset node whose destination is a flat allocation.
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphExecUpdate_FlatAlloc_2DMemset_Idempotent) {
constexpr size_t width_bytes = 456;
constexpr size_t height = 123;
FlatPitched alloc(width_bytes, height);
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
auto capture_graph = [&](unsigned char value) {
hipGraph_t g = nullptr;
HIP_CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeThreadLocal));
HIP_CHECK(hipMemset2DAsync(alloc.ptr, alloc.pitch, value, width_bytes, height, stream));
HIP_CHECK(hipStreamEndCapture(stream, &g));
return g;
};
hipGraph_t gA = capture_graph(0);
hipGraph_t gB = capture_graph(0);
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiateWithFlags(&exec, gA, 0));
hipGraphNode_t err_node = nullptr;
hipGraphExecUpdateResult result = hipGraphExecUpdateError;
HIP_CHECK(hipGraphExecUpdate(exec, gB, &err_node, &result));
REQUIRE(result == hipGraphExecUpdateSuccess);
HIP_CHECK(hipGraphLaunch(exec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
Verify2DMemsetResult(alloc.ptr, alloc.pitch, width_bytes, height, 0);
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(gB));
HIP_CHECK(hipGraphDestroy(gA));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* Test Description
* ------------------------
* - hipGraphExecUpdate must reject any 2D memset shape change. Per CUDA's
* documented contract for cudaGraphExecUpdate: "For 2d memsets, only
* address and assigned value may be updated."
* - Covers width, height, and elementSize changes individually.
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphExecUpdate_2DMemset_ShapeChange_Negative) {
constexpr size_t width_bytes = 128;
constexpr size_t height = 8;
FlatPitched alloc(width_bytes, height);
hipGraph_t gA = nullptr;
HIP_CHECK(hipGraphCreate(&gA, 0));
auto base = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, 0);
hipGraphNode_t nodeA = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&nodeA, gA, nullptr, 0, &base));
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiate(&exec, gA, nullptr, nullptr, 0));
auto try_update = [&](const hipMemsetParams& changed) {
hipGraph_t gB = nullptr;
HIP_CHECK(hipGraphCreate(&gB, 0));
hipGraphNode_t nodeB = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&nodeB, gB, nullptr, 0, &changed));
hipGraphNode_t err_node = nullptr;
hipGraphExecUpdateResult result = hipGraphExecUpdateSuccess;
hipError_t status = hipGraphExecUpdate(exec, gB, &err_node, &result);
REQUIRE(status == hipErrorGraphExecUpdateFailure);
REQUIRE(result == hipGraphExecUpdateErrorParametersChanged);
HIP_CHECK(hipGraphDestroy(gB));
};
SECTION("width changes") {
try_update(Make2DParams(alloc.ptr, alloc.pitch, width_bytes / 2, height, 1, 0));
}
SECTION("height changes") {
try_update(Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height / 2, 1, 0));
}
SECTION("byte-width changes via elementSize") {
// HIP's strict check compares `width * elementSize` as a single byte count, so an
// elementSize change that *preserves* total bytes is allowed (more lenient than CUDA's
// "only address and value may change" contract). Verify rejection only when the byte count
// actually grows.
try_update(Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 2, 0));
}
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(gA));
}
/**
* Test Description
* ------------------------
* - hipGraphExecUpdate must accept changes to value and dst on a 2D memset
* node, even though shape is fixed (per CUDA spec: address and value are
* the two updatable fields).
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphExecUpdate_2DMemset_ValueAndDst_Positive) {
constexpr size_t width_bytes = 128;
constexpr size_t height = 8;
FlatPitched allocA(width_bytes, height);
FlatPitched allocB(width_bytes, height);
hipGraph_t gA = nullptr;
HIP_CHECK(hipGraphCreate(&gA, 0));
auto pA = Make2DParams(allocA.ptr, allocA.pitch, width_bytes, height, 1, 0x11);
hipGraphNode_t nodeA = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&nodeA, gA, nullptr, 0, &pA));
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiate(&exec, gA, nullptr, nullptr, 0));
hipGraph_t gB = nullptr;
HIP_CHECK(hipGraphCreate(&gB, 0));
// dst -> allocB, value -> 0x77, but shape unchanged.
auto pB = Make2DParams(allocB.ptr, allocB.pitch, width_bytes, height, 1, 0x77);
hipGraphNode_t nodeB = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&nodeB, gB, nullptr, 0, &pB));
hipGraphNode_t err_node = nullptr;
hipGraphExecUpdateResult result = hipGraphExecUpdateError;
HIP_CHECK(hipGraphExecUpdate(exec, gB, &err_node, &result));
REQUIRE(result == hipGraphExecUpdateSuccess);
HIP_CHECK(hipGraphLaunch(exec, hipStreamPerThread));
HIP_CHECK(hipStreamSynchronize(hipStreamPerThread));
Verify2DMemsetResult(allocB.ptr, allocB.pitch, width_bytes, height, 0x77);
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(gB));
HIP_CHECK(hipGraphDestroy(gA));
}
/**
* Test Description
* ------------------------
* - hipGraphExecUpdate recurses into child-graph nodes; the child-graph
* branch must enter the same exec-strict path as top-level memset nodes.
* Builds a host graph containing a child graph whose body is a 2D memset
* on plain hipMalloc memory, then updates the exec graph from a second
* identically-shaped graph.
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphExecUpdate_FlatAlloc_2DMemset_ChildGraph) {
constexpr size_t width_bytes = 200;
constexpr size_t height = 10;
FlatPitched alloc(width_bytes, height);
auto build_outer = [&](unsigned char value, hipGraph_t* outer_out) {
hipGraph_t inner = nullptr;
HIP_CHECK(hipGraphCreate(&inner, 0));
auto p = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, value);
hipGraphNode_t inner_node = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&inner_node, inner, nullptr, 0, &p));
hipGraph_t outer = nullptr;
HIP_CHECK(hipGraphCreate(&outer, 0));
hipGraphNode_t child = nullptr;
HIP_CHECK(hipGraphAddChildGraphNode(&child, outer, nullptr, 0, inner));
HIP_CHECK(hipGraphDestroy(inner));
*outer_out = outer;
};
hipGraph_t gA = nullptr;
hipGraph_t gB = nullptr;
build_outer(0x33, &gA);
build_outer(0x33, &gB);
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiate(&exec, gA, nullptr, nullptr, 0));
hipGraphNode_t err_node = nullptr;
hipGraphExecUpdateResult result = hipGraphExecUpdateError;
HIP_CHECK(hipGraphExecUpdate(exec, gB, &err_node, &result));
REQUIRE(result == hipGraphExecUpdateSuccess);
HIP_CHECK(hipGraphLaunch(exec, hipStreamPerThread));
HIP_CHECK(hipStreamSynchronize(hipStreamPerThread));
Verify2DMemsetResult(alloc.ptr, alloc.pitch, width_bytes, height, 0x33);
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(gB));
HIP_CHECK(hipGraphDestroy(gA));
}
/**
* Test Description
* ------------------------
* - hipGraphExecMemsetNodeSetParams on a 2D memset whose destination is a
* plain hipMalloc allocation: changing only `value` (preserving shape)
* must succeed; changing shape (width/height) must fail per the same
* CUDA contract that governs hipGraphExecUpdate.
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphExecMemsetNodeSetParams_FlatAlloc_2D) {
constexpr size_t width_bytes = 128;
constexpr size_t height = 8;
FlatPitched alloc(width_bytes, height);
hipGraph_t graph = nullptr;
HIP_CHECK(hipGraphCreate(&graph, 0));
auto base = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, 0);
hipGraphNode_t node = nullptr;
HIP_CHECK(hipGraphAddMemsetNode(&node, graph, nullptr, 0, &base));
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiate(&exec, graph, nullptr, nullptr, 0));
SECTION("value-only change accepted") {
auto only_value = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, 0x5A);
HIP_CHECK(hipGraphExecMemsetNodeSetParams(exec, node, &only_value));
HIP_CHECK(hipGraphLaunch(exec, hipStreamPerThread));
HIP_CHECK(hipStreamSynchronize(hipStreamPerThread));
Verify2DMemsetResult(alloc.ptr, alloc.pitch, width_bytes, height, 0x5A);
}
SECTION("width change rejected") {
auto width_change = Make2DParams(alloc.ptr, alloc.pitch, width_bytes / 2, height, 1, 0);
HIP_CHECK_ERROR(hipGraphExecMemsetNodeSetParams(exec, node, &width_change),
hipErrorInvalidValue);
}
SECTION("height change rejected") {
auto height_change = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height / 2, 1, 0);
HIP_CHECK_ERROR(hipGraphExecMemsetNodeSetParams(exec, node, &height_change),
hipErrorInvalidValue);
}
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(graph));
}
#if HT_AMD
/**
* Test Description
* ------------------------
* - hipGraphExecNodeSetParams (the generic node-params variant) on a memset
* node must follow the same exec-strict contract as
* hipGraphExecMemsetNodeSetParams. Previously this entry point hard-coded
* isExec=false, allowing shape changes that violate CUDA's contract.
* - AMD-only: hipGraphNodeParams / hipGraphExecNodeSetParams are not
* implemented for the NVIDIA backend in this tree (per CMakeLists).
* Test source
* ------------------------
* - unit/graph/hipGraphMemsetNodeFlatAlloc.cc
*/
HIP_TEST_CASE(Unit_hipGraphExecNodeSetParams_Memset_FlatAlloc_2D) {
constexpr size_t width_bytes = 128;
constexpr size_t height = 8;
FlatPitched alloc(width_bytes, height);
hipGraph_t graph = nullptr;
HIP_CHECK(hipGraphCreate(&graph, 0));
hipGraphNodeParams initial = {};
initial.type = hipGraphNodeTypeMemset;
initial.memset = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, 0);
hipGraphNode_t node = nullptr;
HIP_CHECK(hipGraphAddNode(&node, graph, nullptr, 0, &initial));
hipGraphExec_t exec = nullptr;
HIP_CHECK(hipGraphInstantiate(&exec, graph, nullptr, nullptr, 0));
SECTION("value-only change accepted") {
hipGraphNodeParams only_value = {};
only_value.type = hipGraphNodeTypeMemset;
only_value.memset = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height, 1, 0x9C);
HIP_CHECK(hipGraphExecNodeSetParams(exec, node, &only_value));
HIP_CHECK(hipGraphLaunch(exec, hipStreamPerThread));
HIP_CHECK(hipStreamSynchronize(hipStreamPerThread));
Verify2DMemsetResult(alloc.ptr, alloc.pitch, width_bytes, height, 0x9C);
}
SECTION("shape change rejected") {
hipGraphNodeParams shape_change = {};
shape_change.type = hipGraphNodeTypeMemset;
shape_change.memset = Make2DParams(alloc.ptr, alloc.pitch, width_bytes, height / 2, 1, 0);
HIP_CHECK_ERROR(hipGraphExecNodeSetParams(exec, node, &shape_change), hipErrorInvalidValue);
}
HIP_CHECK(hipGraphExecDestroy(exec));
HIP_CHECK(hipGraphDestroy(graph));
}
#endif // HT_AMD
/**
* End doxygen group GraphTest.
* @}
*/