Files
TheRock/rocm-systems/projects/rocshmem/examples/tile_halo_exchange.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

362 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
*
* SPDX-License-Identifier: MIT
*****************************************************************************/
/**
* 2D Halo Exchange using rocSHMEM Tile API
*
* This example demonstrates a 2D halo exchange pattern common in stencil
* computations. PEs are arranged in a 2D grid topology, and each PE exchanges
* boundary data with its neighbors using tile_put_wg.
*
* Requirements:
* - Number of PEs must be a perfect square (4, 9, 16, 25, ...)
* - Each PE has a local domain with a 1-element halo region on each side
* - Communication uses workgroup-collective tile_put_wg operations
*/
#include <rocshmem/rocshmem.hpp>
#ifdef __HIP_DEVICE_COMPILE__
#include <rocshmem/rocshmem_device.hpp>
#endif
#include <hip/hip_runtime.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <iomanip>
#include "util.h"
using namespace rocshmem;
/******************************************************************************
* COORDINATE TYPES FOR TILE API
*****************************************************************************/
struct Coord2D {
int row;
int col;
__host__ __device__ Coord2D(int r, int c) : row(r), col(c) {}
__host__ __device__ int get(int dim) const { return (dim == 0) ? row : col; }
};
/******************************************************************************
* TENSOR TYPE
*****************************************************************************/
template <typename T>
struct Tensor2D {
using element_type = T;
static constexpr int ndim = 2;
T* data;
int rows;
int cols;
int row_stride;
int col_stride;
__host__ __device__ Tensor2D(T* data_, int rows_, int cols_,
int row_stride_ = -1, int col_stride_ = 1)
: data(data_), rows(rows_), cols(cols_), col_stride(col_stride_) {
row_stride = (row_stride_ == -1) ? cols : row_stride_;
}
__host__ __device__ T* data_handle() const { return data; }
__host__ __device__ int stride(int dim) const {
return (dim == 0) ? row_stride : col_stride;
}
};
/******************************************************************************
* HALO EXCHANGE KERNEL
*****************************************************************************/
/**
* @brief Workgroup-collective kernel to perform halo exchange
*
* Each workgroup sends one boundary to a neighbor using tile_put_wg
*
* @param local_tensor Local domain tensor (includes halo regions)
* @param inner_size Size of inner domain (excluding halo)
* @param north_pe PE ID of north neighbor (-1 if none)
* @param south_pe PE ID of south neighbor (-1 if none)
* @param east_pe PE ID of east neighbor (-1 if none)
* @param west_pe PE ID of west neighbor (-1 if none)
*/
__global__ void halo_exchange_kernel(Tensor2D<float> local_tensor,
int inner_size,
int north_pe, int south_pe,
int east_pe, int west_pe) {
__shared__ rocshmem_ctx_t ctx;
rocshmem_wg_ctx_create(ROCSHMEM_CTX_WG_PRIVATE, &ctx);
int wg_id = blockIdx.x;
int halo_width = 1;
int total_size = inner_size + 2 * halo_width;
// Each workgroup handles one direction
// WG 0: North, WG 1: South, WG 2: East, WG 3: West
if (wg_id == 0 && north_pe >= 0) {
// Send north boundary (row 1, cols 1-8) to north neighbor's south halo (last row, cols 1-8)
// Source: row 1, cols 1-8 of local tensor
// Dest: row 9 (last), cols 1-8 of north neighbor's tensor
float* src_ptr = local_tensor.data + halo_width * total_size + halo_width;
float* dst_ptr = local_tensor.data + (total_size - halo_width) * total_size + halo_width;
Tensor2D<float> src_sub(src_ptr, 1, inner_size, total_size);
Tensor2D<float> dst_sub(dst_ptr, 1, inner_size, total_size);
Coord2D start(0, 0);
Coord2D boundary(1, inner_size);
rocshmem_ctx_tile_put_wg(ctx, dst_sub, src_sub, start, boundary, north_pe, 0);
}
else if (wg_id == 1 && south_pe >= 0) {
// Send south boundary (row 8, cols 1-8) to south neighbor's north halo (row 0, cols 1-8)
float* src_ptr = local_tensor.data + (total_size - halo_width - 1) * total_size + halo_width;
float* dst_ptr = local_tensor.data + 0 * total_size + halo_width;
Tensor2D<float> src_sub(src_ptr, 1, inner_size, total_size);
Tensor2D<float> dst_sub(dst_ptr, 1, inner_size, total_size);
Coord2D start(0, 0);
Coord2D boundary(1, inner_size);
rocshmem_ctx_tile_put_wg(ctx, dst_sub, src_sub, start, boundary, south_pe, 0);
}
else if (wg_id == 2 && east_pe >= 0) {
// Send east boundary (rows 1-8, col 8) to east neighbor's west halo (rows 1-8, col 0)
float* src_ptr = local_tensor.data + halo_width * total_size + (total_size - halo_width - 1);
float* dst_ptr = local_tensor.data + halo_width * total_size + 0;
Tensor2D<float> src_sub(src_ptr, inner_size, 1, total_size);
Tensor2D<float> dst_sub(dst_ptr, inner_size, 1, total_size);
Coord2D start(0, 0);
Coord2D boundary(inner_size, 1);
rocshmem_ctx_tile_put_wg(ctx, dst_sub, src_sub, start, boundary, east_pe, 0);
}
else if (wg_id == 3 && west_pe >= 0) {
// Send west boundary (rows 1-8, col 1) to west neighbor's east halo (rows 1-8, col 9/last)
float* src_ptr = local_tensor.data + halo_width * total_size + halo_width;
float* dst_ptr = local_tensor.data + halo_width * total_size + (total_size - halo_width);
Tensor2D<float> src_sub(src_ptr, inner_size, 1, total_size);
Tensor2D<float> dst_sub(dst_ptr, inner_size, 1, total_size);
Coord2D start(0, 0);
Coord2D boundary(inner_size, 1);
rocshmem_ctx_tile_put_wg(ctx, dst_sub, src_sub, start, boundary, west_pe, 0);
}
rocshmem_wg_ctx_destroy(&ctx);
}
/******************************************************************************
* HELPER FUNCTIONS
*****************************************************************************/
void print_domain(const std::vector<float>& data, int size, int my_pe,
int grid_row, int grid_col) {
std::cout << "\nPE " << my_pe << " (grid pos [" << grid_row << "," << grid_col
<< "]) domain (" << size << "×" << size << "):" << std::endl;
for (int i = 0; i < size; i++) {
std::cout << " ";
for (int j = 0; j < size; j++) {
float val = data[i * size + j];
if (val < 0) {
std::cout << " -- "; // Uninitialized halo
} else {
std::cout << std::setw(3) << std::setfill(' ') << (int)val << " ";
}
}
std::cout << std::endl;
}
}
/******************************************************************************
* MAIN
*****************************************************************************/
int main(int argc, char** argv) {
rocshmem_init();
int my_pe = rocshmem_my_pe();
int n_pes = rocshmem_n_pes();
// Check that we have a perfect square number of PEs
int grid_size = (int)std::sqrt(n_pes);
if (grid_size * grid_size != n_pes) {
if (my_pe == 0) {
std::cerr << "Error: Number of PEs (" << n_pes
<< ") must be a perfect square (4, 9, 16, 25, ...)" << std::endl;
}
rocshmem_finalize();
return 1;
}
// Determine my position in the 2D grid
int my_grid_row = my_pe / grid_size;
int my_grid_col = my_pe % grid_size;
// Determine neighbor PEs (-1 if at boundary)
int north_pe = (my_grid_row > 0) ? (my_grid_row - 1) * grid_size + my_grid_col : -1;
int south_pe = (my_grid_row < grid_size - 1) ? (my_grid_row + 1) * grid_size + my_grid_col : -1;
int west_pe = (my_grid_col > 0) ? my_grid_row * grid_size + (my_grid_col - 1) : -1;
int east_pe = (my_grid_col < grid_size - 1) ? my_grid_row * grid_size + (my_grid_col + 1) : -1;
if (my_pe == 0) {
std::cout << "=== 2D Halo Exchange using Tile API ===" << std::endl;
std::cout << "Grid topology: " << grid_size << "×" << grid_size
<< " (" << n_pes << " PEs)" << std::endl;
}
// Configuration
const int INNER_SIZE = 8; // Inner domain size (excluding halo)
const int HALO_WIDTH = 1;
const int TOTAL_SIZE = INNER_SIZE + 2 * HALO_WIDTH; // 10×10 including halo
// Allocate symmetric memory for the domain (including halo)
float* d_domain = (float*)rocshmem_malloc(TOTAL_SIZE * TOTAL_SIZE * sizeof(float));
// Initialize domain on host
std::vector<float> h_domain(TOTAL_SIZE * TOTAL_SIZE, -1.0f); // -1 for halo regions
// Initialize inner domain with unique values per PE: PE_ID * 100 + row * 10 + col
for (int i = HALO_WIDTH; i < INNER_SIZE + HALO_WIDTH; i++) {
for (int j = HALO_WIDTH; j < INNER_SIZE + HALO_WIDTH; j++) {
int inner_row = i - HALO_WIDTH;
int inner_col = j - HALO_WIDTH;
h_domain[i * TOTAL_SIZE + j] = my_pe * 100.0f + inner_row * 10.0f + inner_col;
}
}
// Copy to device
CHECK_HIP(hipMemcpy(d_domain, h_domain.data(),
TOTAL_SIZE * TOTAL_SIZE * sizeof(float),
hipMemcpyHostToDevice));
if (my_pe == 0) {
std::cout << "\n--- Before Halo Exchange ---" << std::endl;
}
rocshmem_barrier_all();
// Print initial state
CHECK_HIP(hipMemcpy(h_domain.data(), d_domain,
TOTAL_SIZE * TOTAL_SIZE * sizeof(float),
hipMemcpyDeviceToHost));
print_domain(h_domain, TOTAL_SIZE, my_pe, my_grid_row, my_grid_col);
rocshmem_barrier_all();
// Perform halo exchange
// Launch 4 workgroups (one per direction), each with 256 threads
Tensor2D<float> domain_tensor(d_domain, TOTAL_SIZE, TOTAL_SIZE);
hipLaunchKernelGGL(halo_exchange_kernel, 4, 256, 0, 0,
domain_tensor, INNER_SIZE,
north_pe, south_pe, east_pe, west_pe);
CHECK_HIP(hipStreamSynchronize(0));
rocshmem_barrier_all();
if (my_pe == 0) {
std::cout << "\n--- After Halo Exchange ---" << std::endl;
}
rocshmem_barrier_all();
// Print final state
CHECK_HIP(hipMemcpy(h_domain.data(), d_domain,
TOTAL_SIZE * TOTAL_SIZE * sizeof(float),
hipMemcpyDeviceToHost));
print_domain(h_domain, TOTAL_SIZE, my_pe, my_grid_row, my_grid_col);
// Verify halo values
bool success = true;
// Check north halo (row 0)
if (north_pe >= 0) {
int north_grid_row = north_pe / grid_size;
int north_grid_col = north_pe % grid_size;
for (int j = HALO_WIDTH; j < INNER_SIZE + HALO_WIDTH; j++) {
int expected_inner_row = INNER_SIZE - 1; // Last row of north neighbor's inner domain
int expected_inner_col = j - HALO_WIDTH;
float expected = north_pe * 100.0f + expected_inner_row * 10.0f + expected_inner_col;
float actual = h_domain[0 * TOTAL_SIZE + j];
if (std::abs(actual - expected) > 0.001f) {
std::cout << "PE " << my_pe << " north halo error at [0," << j
<< "]: expected " << expected << ", got " << actual << std::endl;
success = false;
}
}
}
// Check south halo (last row)
if (south_pe >= 0) {
for (int j = HALO_WIDTH; j < INNER_SIZE + HALO_WIDTH; j++) {
int expected_inner_row = 0; // First row of south neighbor's inner domain
int expected_inner_col = j - HALO_WIDTH;
float expected = south_pe * 100.0f + expected_inner_row * 10.0f + expected_inner_col;
float actual = h_domain[(TOTAL_SIZE - 1) * TOTAL_SIZE + j];
if (std::abs(actual - expected) > 0.001f) {
std::cout << "PE " << my_pe << " south halo error at [" << (TOTAL_SIZE - 1)
<< "," << j << "]: expected " << expected << ", got " << actual << std::endl;
success = false;
}
}
}
// Check west halo (column 0)
if (west_pe >= 0) {
for (int i = HALO_WIDTH; i < INNER_SIZE + HALO_WIDTH; i++) {
int expected_inner_row = i - HALO_WIDTH;
int expected_inner_col = INNER_SIZE - 1; // Last column of west neighbor's inner domain
float expected = west_pe * 100.0f + expected_inner_row * 10.0f + expected_inner_col;
float actual = h_domain[i * TOTAL_SIZE + 0];
if (std::abs(actual - expected) > 0.001f) {
std::cout << "PE " << my_pe << " west halo error at [" << i
<< ",0]: expected " << expected << ", got " << actual << std::endl;
success = false;
}
}
}
// Check east halo (last column)
if (east_pe >= 0) {
for (int i = HALO_WIDTH; i < INNER_SIZE + HALO_WIDTH; i++) {
int expected_inner_row = i - HALO_WIDTH;
int expected_inner_col = 0; // First column of east neighbor's inner domain
float expected = east_pe * 100.0f + expected_inner_row * 10.0f + expected_inner_col;
float actual = h_domain[i * TOTAL_SIZE + (TOTAL_SIZE - 1)];
if (std::abs(actual - expected) > 0.001f) {
std::cout << "PE " << my_pe << " east halo error at [" << i
<< "," << (TOTAL_SIZE - 1) << "]: expected " << expected
<< ", got " << actual << std::endl;
success = false;
}
}
}
rocshmem_barrier_all();
if (my_pe == 0) {
std::cout << "\n";
if (success) {
std::cout << "✓ Halo exchange verification PASSED" << std::endl;
} else {
std::cout << "✗ Halo exchange verification FAILED" << std::endl;
}
}
// Cleanup
rocshmem_free(d_domain);
rocshmem_finalize();
return success ? 0 : 1;
}