Files
TheRock/rocm-systems/projects/hip-tests/catch/unit/event/hipEventCoalescing.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

353 lines
13 KiB
C++

/*
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
#include <hip_test_common.hh>
#include <hip_test_checkers.hh>
#include <hip_test_kernels.hh>
#include <algorithm>
#include <vector>
/**
* @addtogroup hipEventRecord hipEventRecord
* @{
* @ingroup EventTest
*/
/**
* Test Description
* ------------------------
* - Validates cross-stream synchronization via hipStreamWaitEvent works correctly
* when coalescing is active. This is the most safety-critical scenario for
* coalescing — if a needed barrier is wrongly skipped, the waiting stream
* could start before the producing stream's work completes, causing data races.
* - Scenario per iteration:
* 1. Stream1 launches vectorADD producing C_d
* 2. Stream1 records event multiple times (consecutive should coalesce)
* 3. Stream2 waits on event via hipStreamWaitEvent
* 4. Stream2 launches vectorADDReverse that reads C_d and writes D_d
* 5. Validate D_d matches expected (proves stream2's kernel saw stream1's writes)
* - If coalescing wrongly skipped the barrier, D_d would contain stale/garbage data.
* Test source
* ------------------------
* - unit/event/hipEventCoalescing.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 5.2
*/
HIP_TEST_CASE(Unit_hipEventCoalescing_CrossStreamWait) {
constexpr size_t N = 1024 * 1024;
constexpr size_t Nbytes = N * sizeof(float);
constexpr int kNumIterations = 10;
constexpr int kThreadsPerBlock = 256;
const int kBlocks = (N + kThreadsPerBlock - 1) / kThreadsPerBlock;
float *A_h, *B_h, *C_h, *D_h;
float *A_d, *B_d, *C_d, *D_d;
HipTest::initArrays(&A_d, &B_d, &C_d, &A_h, &B_h, &C_h, N);
HIP_CHECK(hipMalloc(&D_d, Nbytes));
D_h = reinterpret_cast<float*>(malloc(Nbytes));
REQUIRE(D_h != nullptr);
hipStream_t stream1, stream2;
hipEvent_t event;
HIP_CHECK(hipStreamCreate(&stream1));
HIP_CHECK(hipStreamCreate(&stream2));
HIP_CHECK(hipEventCreateWithFlags(&event, hipEventDisableTiming));
HIP_CHECK(hipMemcpy(A_d, A_h, Nbytes, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(B_d, B_h, Nbytes, hipMemcpyHostToDevice));
for (int iter = 0; iter < kNumIterations; iter++) {
// Reset destination on stream1 to known garbage so stale reads would be visible
HIP_CHECK(hipMemsetAsync(C_d, 0xFF, Nbytes, stream1));
// Producer: stream1 computes C_d = A_d + B_d
HipTest::launchKernel<float>(HipTest::vectorADD<float>, kBlocks, kThreadsPerBlock, 0, stream1,
static_cast<const float*>(A_d),
static_cast<const float*>(B_d),
C_d, N);
HIP_CHECK(hipGetLastError());
// Record event multiple times on stream1 (should coalesce — but the barrier
// must survive coalescing so stream2's wait sees stream1's kernel completion)
HIP_CHECK(hipEventRecord(event, stream1));
HIP_CHECK(hipEventRecord(event, stream1)); // Should coalesce
HIP_CHECK(hipEventRecord(event, stream1)); // Should coalesce
// Consumer: stream2 waits on event, then reads C_d and writes D_d = C_d + B_d
HIP_CHECK(hipStreamWaitEvent(stream2, event, 0));
HipTest::launchKernel<float>(HipTest::vectorADDReverse<float>, kBlocks, kThreadsPerBlock, 0,
stream2, static_cast<const float*>(C_d),
static_cast<const float*>(B_d), D_d, N);
HIP_CHECK(hipGetLastError());
HIP_CHECK(hipStreamSynchronize(stream2));
// D_d should equal (A_d + B_d) + B_d
HIP_CHECK(hipMemcpy(D_h, D_d, Nbytes, hipMemcpyDeviceToHost));
HipTest::checkVectors<float>(A_h, B_h, D_h, N, [](float a, float b) { return (a + b) + b; });
}
HIP_CHECK(hipEventDestroy(event));
HIP_CHECK(hipStreamDestroy(stream1));
HIP_CHECK(hipStreamDestroy(stream2));
HIP_CHECK(hipFree(D_d));
free(D_h);
HipTest::freeArrays(A_d, B_d, C_d, A_h, B_h, C_h, false);
}
/**
* Test Description
* ------------------------
* - Validates that async memcpy and memset between hipEventRecord calls
* correctly break coalescing. After such operations, the next record must
* submit a fresh barrier (otherwise the event sync wouldn't wait for the
* intervening copy/set to complete).
* - Two SECTIONs: one for memcpy break, one for memset break.
* - Pattern per section:
* 1. record event
* 2. async memcpy/memset on same stream (must break coalescing)
* 3. record event (must NOT coalesce — submits new barrier covering step 2)
* 4. sync event, validate the intervening operation completed
* Test source
* ------------------------
* - unit/event/hipEventCoalescing.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 5.2
*/
HIP_TEST_CASE(Unit_hipEventCoalescing_AsyncOpsBreakCoalescing) {
constexpr size_t N = 1024 * 1024;
constexpr size_t Nbytes = N * sizeof(int);
constexpr int kNumIterations = 10;
hipStream_t stream;
hipEvent_t event;
HIP_CHECK(hipStreamCreate(&stream));
HIP_CHECK(hipEventCreateWithFlags(&event, hipEventDisableTiming));
int* d_buf;
HIP_CHECK(hipMalloc(&d_buf, Nbytes));
std::vector<int> h_buf(N);
SECTION("Memcpy breaks coalescing") {
for (int iter = 0; iter < kNumIterations; iter++) {
// Fill host source buffer with iter-specific value
const int sentinel = 0x12340000 + iter;
std::fill(h_buf.begin(), h_buf.end(), sentinel);
// First record - submits barrier
HIP_CHECK(hipEventRecord(event, stream));
// Async memcpy - must break coalescing
HIP_CHECK(hipMemcpyAsync(d_buf, h_buf.data(), Nbytes, hipMemcpyHostToDevice, stream));
// Second record - must submit new barrier (NOT coalesce) so sync waits for memcpy
HIP_CHECK(hipEventRecord(event, stream));
// Sync event - if coalescing wrongly skipped the second barrier, the memcpy
// might not have completed when we read back
HIP_CHECK(hipEventSynchronize(event));
// Read back without further sync — relies on event sync covering the memcpy
std::vector<int> h_verify(N, 0);
HIP_CHECK(hipMemcpy(h_verify.data(), d_buf, Nbytes, hipMemcpyDeviceToHost));
for (size_t i = 0; i < N; i++) {
REQUIRE(h_verify[i] == sentinel);
}
}
}
SECTION("Memset breaks coalescing") {
for (int iter = 0; iter < kNumIterations; iter++) {
const unsigned char pattern = static_cast<unsigned char>(0x40 + iter);
const int expected = (pattern << 24) | (pattern << 16) | (pattern << 8) | pattern;
HIP_CHECK(hipEventRecord(event, stream));
// Async memset - must break coalescing
HIP_CHECK(hipMemsetAsync(d_buf, pattern, Nbytes, stream));
// Second record - must submit new barrier
HIP_CHECK(hipEventRecord(event, stream));
HIP_CHECK(hipEventSynchronize(event));
std::vector<int> h_verify(N, 0);
HIP_CHECK(hipMemcpy(h_verify.data(), d_buf, Nbytes, hipMemcpyDeviceToHost));
for (size_t i = 0; i < N; i++) {
REQUIRE(h_verify[i] == expected);
}
}
}
HIP_CHECK(hipFree(d_buf));
HIP_CHECK(hipEventDestroy(event));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* Test Description
* ------------------------
* - Validates that interleaving records of different events on the same stream
* does not cause incorrect coalescing.
* - Coalescing only applies when last_packet was BARRIER for the SAME event.
* - Scenario:
* 1. kernel_A on stream
* 2. record event1 (barrier for event1)
* 3. record event2 (different event - must NOT coalesce, new barrier)
* 4. record event1 (last packet was event2's barrier - must NOT coalesce)
* 5. kernel_B on stream
* 6. record event2 (different event - must NOT coalesce)
* - Both events must independently capture the correct point in the stream.
* - event1's final record point is between kernel_A and kernel_B (after step 4)
* - event2's final record point is after kernel_B (after step 6)
* Test source
* ------------------------
* - unit/event/hipEventCoalescing.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 5.2
*/
HIP_TEST_CASE(Unit_hipEventCoalescing_InterleavedEvents) {
constexpr size_t N = 1024 * 1024;
constexpr size_t Nbytes = N * sizeof(float);
constexpr int kNumIterations = 10;
constexpr int kThreadsPerBlock = 256;
const int kBlocks = (N + kThreadsPerBlock - 1) / kThreadsPerBlock;
float *A_h, *B_h, *C_h;
float *A_d, *B_d, *C_d, *D_d;
HipTest::initArrays(&A_d, &B_d, &C_d, &A_h, &B_h, &C_h, N);
HIP_CHECK(hipMalloc(&D_d, Nbytes));
hipStream_t stream1, stream2;
hipEvent_t event1, event2;
HIP_CHECK(hipStreamCreate(&stream1));
HIP_CHECK(hipStreamCreate(&stream2));
HIP_CHECK(hipEventCreateWithFlags(&event1, hipEventDisableTiming));
HIP_CHECK(hipEventCreateWithFlags(&event2, hipEventDisableTiming));
HIP_CHECK(hipMemcpy(A_d, A_h, Nbytes, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(B_d, B_h, Nbytes, hipMemcpyHostToDevice));
for (int iter = 0; iter < kNumIterations; iter++) {
// kernel_A: C_d = A_d + B_d
HipTest::launchKernel<float>(HipTest::vectorADD<float>, kBlocks, kThreadsPerBlock, 0, stream1,
static_cast<const float*>(A_d),
static_cast<const float*>(B_d),
C_d, N);
HIP_CHECK(hipGetLastError());
// Interleaved records - none should coalesce because event identity changes
HIP_CHECK(hipEventRecord(event1, stream1)); // barrier(event1)
HIP_CHECK(hipEventRecord(event2, stream1)); // different event — NOT coalesce
HIP_CHECK(hipEventRecord(event1, stream1)); // last was event2 — NOT coalesce
// kernel_B: D_d = A_d - B_d (after event1's final record point)
HipTest::launchKernel<float>(HipTest::vectorSUB<float>, kBlocks, kThreadsPerBlock, 0, stream1,
static_cast<const float*>(A_d),
static_cast<const float*>(B_d),
D_d, N);
HIP_CHECK(hipGetLastError());
HIP_CHECK(hipEventRecord(event2, stream1)); // different event — NOT coalesce
// event2 captures point AFTER kernel_B. If stream2 waits on event2 and reads D_d,
// it should see kernel_B's output. If event2 wrongly coalesced with event1,
// stream2 would not wait long enough.
HIP_CHECK(hipStreamWaitEvent(stream2, event2, 0));
// Validate kernel_B's output is readable after waiting on event2
std::vector<float> D_h(N);
HIP_CHECK(hipMemcpyAsync(D_h.data(), D_d, Nbytes, hipMemcpyDeviceToHost, stream2));
HIP_CHECK(hipStreamSynchronize(stream2));
HipTest::checkVectorSUB(A_h, B_h, D_h.data(), N);
// Validate kernel_A's output via event1 wait on a fresh stream wait
HIP_CHECK(hipEventSynchronize(event1));
HIP_CHECK(hipMemcpy(C_h, C_d, Nbytes, hipMemcpyDeviceToHost));
HipTest::checkVectorADD(A_h, B_h, C_h, N);
}
HIP_CHECK(hipEventDestroy(event1));
HIP_CHECK(hipEventDestroy(event2));
HIP_CHECK(hipStreamDestroy(stream1));
HIP_CHECK(hipStreamDestroy(stream2));
HIP_CHECK(hipFree(D_d));
HipTest::freeArrays(A_d, B_d, C_d, A_h, B_h, C_h, false);
}
/**
* Test Description
* ------------------------
* - Validates that hipEventQuery returns correct status after coalesced records.
* - If the last record was coalesced (no fresh barrier submitted), the event
* should still report the status of the actual last submitted barrier.
* - Scenario:
* 1. Launch long-running kernel (busy work)
* 2. Record event multiple times (later records coalesce)
* 3. hipEventQuery should report NotReady while kernel runs
* 4. After hipDeviceSynchronize, hipEventQuery should report Success
* Test source
* ------------------------
* - unit/event/hipEventCoalescing.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 5.2
*/
HIP_TEST_CASE(Unit_hipEventCoalescing_EventQuery) {
constexpr size_t N = 1024 * 64;
constexpr size_t Nbytes = N * sizeof(int);
constexpr int kNumIterations = 5;
constexpr int kThreadsPerBlock = 256;
const int kBlocks = (N + kThreadsPerBlock - 1) / kThreadsPerBlock;
// High count makes addCount slow enough to query before completion
constexpr int kSlowCount = 10000;
hipStream_t stream;
hipEvent_t event;
HIP_CHECK(hipStreamCreate(&stream));
HIP_CHECK(hipEventCreateWithFlags(&event, hipEventDisableTiming));
int *A_d, *C_d;
HIP_CHECK(hipMalloc(&A_d, Nbytes));
HIP_CHECK(hipMalloc(&C_d, Nbytes));
HIP_CHECK(hipMemset(A_d, 0, Nbytes));
for (int iter = 0; iter < kNumIterations; iter++) {
// Launch slow kernel
HipTest::launchKernel<int>(HipTest::addCount<int>, kBlocks, kThreadsPerBlock, 0, stream,
static_cast<const int*>(A_d), C_d, N, kSlowCount);
HIP_CHECK(hipGetLastError());
// Record event multiple times - later records should coalesce
HIP_CHECK(hipEventRecord(event, stream));
HIP_CHECK(hipEventRecord(event, stream)); // Should coalesce
HIP_CHECK(hipEventRecord(event, stream)); // Should coalesce
// Query event - should be NotReady because slow kernel is still running.
// If coalescing wrongly returned success without a barrier, this would fail.
hipError_t query_status = hipEventQuery(event);
REQUIRE((query_status == hipErrorNotReady || query_status == hipSuccess));
// After full sync, query must report success
HIP_CHECK(hipStreamSynchronize(stream));
HIP_CHECK(hipEventQuery(event));
}
HIP_CHECK(hipFree(A_d));
HIP_CHECK(hipFree(C_d));
HIP_CHECK(hipEventDestroy(event));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* @}
*/