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
239 lines
9.0 KiB
C++
239 lines
9.0 KiB
C++
// Copyright (c) 2026 Advanced Micro Devices, Inc.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
/// @file Multi-XCD vector addition stress test with golden reference validation.
|
|
///
|
|
/// Loads a compiled vector_add.hip kernel and dispatches 256 workgroups across
|
|
/// all 8 XCDs (CDNA4 topology), one workgroup per CU. Each wavefront of 64
|
|
/// threads computes C[gid] = A[gid] + B[gid]. Results are compared against a
|
|
/// CPU golden reference.
|
|
|
|
#include "aql_queue.h"
|
|
#include "test_paths.h"
|
|
|
|
#include "embedded_schema.h"
|
|
#include "rocjitsu/code/executable.h"
|
|
#include "rocjitsu/config/config_loader.h"
|
|
#include "rocjitsu/isa/decoder.h"
|
|
#include "rocjitsu/vm/amdgpu/compute_unit.h"
|
|
#include "rocjitsu/vm/amdgpu/gpu_memory.h"
|
|
|
|
#include "rocjitsu/base/rj_compiler.h"
|
|
RJ_DIAGNOSTIC_PUSH
|
|
RJ_DIAGNOSTIC_IGNORE_PEDANTIC
|
|
#include "hsa/AMDHSAKernelDescriptor.h"
|
|
RJ_DIAGNOSTIC_POP
|
|
#include "rocjitsu/vm/amdgpu/wavefront.h"
|
|
#include "rocjitsu/vm/soc.h"
|
|
|
|
#include "simdojo/sim/simulation.h"
|
|
#include "simdojo/sim/topology.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#ifdef HAS_DEVICE_KERNELS
|
|
|
|
namespace {
|
|
|
|
using namespace rocjitsu;
|
|
|
|
const std::string CONFIG_PATH = test::config_path("gfx950_cdna4.json");
|
|
using test::kernel_path;
|
|
|
|
constexpr uint32_t TOTAL_XCDS = 8;
|
|
constexpr uint32_t CUS_PER_XCD = 32; // 4 SEs x 8 CUs
|
|
constexpr uint32_t TOTAL_CUS = TOTAL_XCDS * CUS_PER_XCD;
|
|
constexpr uint32_t WF_SIZE = 64;
|
|
constexpr uint32_t N = TOTAL_CUS * WF_SIZE; // 16384 elements, one WG per CU
|
|
|
|
constexpr uint64_t KD_ADDR = 0x10000;
|
|
constexpr uint64_t A_ADDR = 0x100000;
|
|
constexpr uint64_t B_ADDR = 0x200000;
|
|
constexpr uint64_t C_ADDR = 0x300000;
|
|
constexpr uint64_t KERNARG_ADDR = 0x400000;
|
|
|
|
TEST(VectorAddStressTest, AllCUsGoldenReference) {
|
|
// Load the compiled vector_add kernel.
|
|
Executable exec(kernel_path("vector_add"));
|
|
ASSERT_TRUE(exec.is_valid()) << "Failed to load vector_add.o";
|
|
ASSERT_GT(exec.num_code_objects(ROCJITSU_CODE_TARGET_GFX950), 0u);
|
|
auto *co = exec.code_object(ROCJITSU_CODE_TARGET_GFX950, 0);
|
|
ASSERT_NE(co, nullptr);
|
|
|
|
// Build the simulation engine with CDNA4 topology.
|
|
auto loaded = config::load_config(CONFIG_PATH, rocjitsu::kEmbeddedSchema);
|
|
auto *soc = loaded.soc();
|
|
auto *memory = loaded.memory();
|
|
auto engine = std::make_unique<simdojo::SimulationEngine>(loaded.engine_config);
|
|
engine->topology().set_root(loaded.take_root());
|
|
loaded.wire_links(engine->topology());
|
|
engine->create();
|
|
|
|
// Load code into GPU memory. Place .rodata (kernel descriptor) and .text (code)
|
|
// at their virtual addresses relative to a base. The .kd symbol value matches
|
|
// the rodata vaddr, and kernel_code_entry_byte_offset bridges to .text vaddr.
|
|
uint64_t kd_offset = co->kernel_descriptor_offset("vector_add");
|
|
ASSERT_NE(kd_offset, 0u) << "Kernel descriptor symbol not found";
|
|
for (const auto *sec : co->rodata_sections())
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(sec->data()), sec->size(),
|
|
KD_ADDR + sec->vaddr());
|
|
for (const auto *sec : co->text_sections())
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(sec->data()), sec->size(),
|
|
KD_ADDR + sec->vaddr());
|
|
uint64_t kernel_object = KD_ADDR + kd_offset;
|
|
|
|
// Generate input vectors.
|
|
size_t vec_bytes = N * sizeof(float);
|
|
std::vector<float> A(N), B(N), C_expected(N);
|
|
for (uint32_t i = 0; i < N; ++i) {
|
|
A[i] = static_cast<float>(i % 97) * 0.1f;
|
|
B[i] = static_cast<float>(i % 61) * 0.2f;
|
|
C_expected[i] = A[i] + B[i];
|
|
}
|
|
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(A.data()), vec_bytes, A_ADDR);
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(B.data()), vec_bytes, B_ADDR);
|
|
std::vector<float> zeros(N, 0.0f);
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(zeros.data()), vec_bytes, C_ADDR);
|
|
|
|
// Write kernel arguments: (A*, B*, C*, N).
|
|
struct {
|
|
uint64_t A, B, C;
|
|
uint32_t N;
|
|
} args = {A_ADDR, B_ADDR, C_ADDR, N};
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(&args), sizeof(args), KERNARG_ADDR);
|
|
|
|
// Dispatch across all XCDs via AQL queues.
|
|
uint32_t wgs_per_xcd = TOTAL_CUS / TOTAL_XCDS;
|
|
for (uint32_t xi = 0; xi < TOTAL_XCDS; ++xi) {
|
|
auto *cp = soc->xcd(xi)->command_processor();
|
|
cp->set_workgroup_id_offset(xi * wgs_per_xcd);
|
|
uint64_t ring = 0xF0000000ULL + xi * 0x100000ULL;
|
|
test::AqlQueue queue(memory, cp, ring, 4096, ring + 0x10000, ring + 0x10008, ring + 0x10010);
|
|
queue.dispatch(kernel_object, wgs_per_xcd * WF_SIZE, WF_SIZE, KERNARG_ADDR);
|
|
}
|
|
|
|
// Engine drives all CPs and CUs to completion.
|
|
engine->run();
|
|
soc->flush_all();
|
|
|
|
// Read back results and compare against golden reference.
|
|
unsigned mismatches = 0;
|
|
for (uint32_t i = 0; i < N; ++i) {
|
|
float actual = std::bit_cast<float>(memory->read32(C_ADDR + i * sizeof(float)));
|
|
if (std::abs(actual - C_expected[i]) > 1e-6f) {
|
|
if (mismatches < 10)
|
|
ADD_FAILURE() << "Mismatch at C[" << i << "]: GPU=" << actual << " CPU=" << C_expected[i];
|
|
++mismatches;
|
|
}
|
|
}
|
|
EXPECT_EQ(mismatches, 0u) << mismatches << " elements differ (showing first 10)";
|
|
}
|
|
|
|
// Multi-threaded version: one worker thread per XCD (8 threads).
|
|
// Exercises the barrier-based LBTS protocol with real GPU kernel execution.
|
|
// Multi-threaded: exercises barrier-based LBTS with real GPU kernel execution.
|
|
TEST(VectorAddStressTest, AllCUsGoldenReference_MultiThreaded) {
|
|
Executable exec(kernel_path("vector_add"));
|
|
ASSERT_TRUE(exec.is_valid()) << "Failed to load vector_add.o";
|
|
ASSERT_GT(exec.num_code_objects(ROCJITSU_CODE_TARGET_GFX950), 0u);
|
|
auto *co = exec.code_object(ROCJITSU_CODE_TARGET_GFX950, 0);
|
|
ASSERT_NE(co, nullptr);
|
|
|
|
auto loaded = config::load_config(CONFIG_PATH, rocjitsu::kEmbeddedSchema);
|
|
auto *soc = loaded.soc();
|
|
auto *memory = loaded.memory();
|
|
loaded.engine_config.num_threads = TOTAL_XCDS;
|
|
auto engine = std::make_unique<simdojo::SimulationEngine>(loaded.engine_config);
|
|
engine->topology().set_root(loaded.take_root());
|
|
loaded.wire_links(engine->topology());
|
|
|
|
// Partition by XCD so each XCD's components stay on one thread.
|
|
std::unordered_map<simdojo::Component *, simdojo::PartitionID> xcd_map;
|
|
for (uint32_t i = 0; i < soc->num_xcds(); ++i)
|
|
xcd_map[soc->xcd(i)] = i;
|
|
engine->topology().partition_manual(
|
|
TOTAL_XCDS, [&](simdojo::Component *c) -> simdojo::PartitionID {
|
|
for (auto *p = static_cast<simdojo::Component *>(c); p != nullptr;
|
|
p = static_cast<simdojo::Component *>(p->parent())) {
|
|
auto it = xcd_map.find(p);
|
|
if (it != xcd_map.end())
|
|
return it->second;
|
|
}
|
|
return 0;
|
|
});
|
|
engine->create();
|
|
|
|
co->load_to_memory(memory, KD_ADDR);
|
|
uint64_t kernel_object = KD_ADDR + co->kernel_descriptor_offset("vector_add");
|
|
ASSERT_NE(kernel_object, KD_ADDR);
|
|
|
|
size_t vec_bytes = N * sizeof(float);
|
|
std::vector<float> A(N), B(N), C_expected(N);
|
|
for (uint32_t i = 0; i < N; ++i) {
|
|
A[i] = static_cast<float>(i % 97) * 0.1f;
|
|
B[i] = static_cast<float>(i % 61) * 0.2f;
|
|
C_expected[i] = A[i] + B[i];
|
|
}
|
|
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(A.data()), vec_bytes, A_ADDR);
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(B.data()), vec_bytes, B_ADDR);
|
|
std::vector<float> zeros(N, 0.0f);
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(zeros.data()), vec_bytes, C_ADDR);
|
|
|
|
struct {
|
|
uint64_t A, B, C;
|
|
uint32_t N;
|
|
} args = {A_ADDR, B_ADDR, C_ADDR, N};
|
|
memory->load_image(reinterpret_cast<const uint8_t *>(&args), sizeof(args), KERNARG_ADDR);
|
|
|
|
uint32_t wgs_per_xcd = TOTAL_CUS / TOTAL_XCDS;
|
|
for (uint32_t xi = 0; xi < TOTAL_XCDS; ++xi) {
|
|
auto *cp = soc->xcd(xi)->command_processor();
|
|
cp->set_workgroup_id_offset(xi * wgs_per_xcd);
|
|
uint64_t ring = 0xF0000000ULL + xi * 0x100000ULL;
|
|
test::AqlQueue queue(memory, cp, ring, 4096, ring + 0x10000, ring + 0x10008, ring + 0x10010);
|
|
queue.dispatch(kernel_object, wgs_per_xcd * WF_SIZE, WF_SIZE, KERNARG_ADDR);
|
|
}
|
|
|
|
engine->run();
|
|
soc->flush_all();
|
|
|
|
unsigned mismatches = 0;
|
|
for (uint32_t i = 0; i < N; ++i) {
|
|
float actual = std::bit_cast<float>(memory->read32(C_ADDR + i * sizeof(float)));
|
|
if (std::abs(actual - C_expected[i]) > 1e-6f) {
|
|
if (mismatches < 10)
|
|
ADD_FAILURE() << "Mismatch at C[" << i << "]: GPU=" << actual << " CPU=" << C_expected[i];
|
|
++mismatches;
|
|
}
|
|
}
|
|
EXPECT_EQ(mismatches, 0u) << mismatches << " elements differ (showing first 10)";
|
|
}
|
|
|
|
TEST(VectorAddCodeObjectTest, LoadsAndDecodes) {
|
|
Executable exec(kernel_path("vector_add"));
|
|
ASSERT_TRUE(exec.is_valid()) << "Failed to load vector_add.o";
|
|
auto *co = exec.code_object(ROCJITSU_CODE_TARGET_GFX950, 0);
|
|
ASSERT_NE(co, nullptr);
|
|
ASSERT_FALSE(co->text_sections().empty());
|
|
|
|
auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_CDNA4);
|
|
const auto *text = co->text_sections()[0];
|
|
const auto *data = reinterpret_cast<const uint32_t *>(text->data());
|
|
std::unique_ptr<Instruction> inst(decoder->decode(data));
|
|
EXPECT_NE(inst, nullptr) << "Failed to decode first instruction";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#endif // HAS_DEVICE_KERNELS
|