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
282 lines
10 KiB
C++
282 lines
10 KiB
C++
/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
/* roundtrip-async-multi-stream - Submit NUM_STREAMS read+write pairs
|
|
* concurrently, each on its own hipStreamNonBlocking stream, covering
|
|
* non-overlapping SLICE_SIZE slices of a SLICE_SIZE * NUM_STREAMS file.
|
|
* Each stream owns its own GPU buffer; ordering is stream-local
|
|
* (read[i] -> write[i] on stream[i]) so the streams can run in parallel.
|
|
*
|
|
* Usage: ./roundtrip-async-multi-stream READ_FILE WRITE_FILE [GPUID]
|
|
*
|
|
* READ_FILE Path to the input file. Created/truncated and seeded with
|
|
* TOTAL_SIZE bytes of generated test pattern via a plain POSIX
|
|
* write so the async path has known content to read.
|
|
* WRITE_FILE Path to the output file. Opened O_WRONLY|O_CREAT|O_DIRECT
|
|
* (no O_TRUNC). Each stream writes its assigned slice;
|
|
* SLICE_SIZE is already block-aligned so no ftruncate is
|
|
* needed afterwards.
|
|
* GPUID GPU device index (optional, default 0).
|
|
*
|
|
* Steps:
|
|
* 1. Select GPU
|
|
* 2. Seed READ_FILE with pattern (plain POSIX write, no O_DIRECT)
|
|
* 3. For each of NUM_STREAMS entries:
|
|
* - hipMalloc + hipFileBufRegister
|
|
* - Assign file offset i * SLICE_SIZE
|
|
* - hipStreamCreateWithFlags(hipStreamNonBlocking) + hipMemsetAsync(0)
|
|
* 4. Open READ_FILE (O_RDONLY|O_DIRECT) and WRITE_FILE (O_WRONLY|O_CREAT|O_DIRECT)
|
|
* 5. Loop: submit hipFileReadAsync on each stream
|
|
* 6. Loop: submit hipFileWriteAsync on each stream
|
|
* 7. Loop: hipStreamSynchronize per stream + assert per-entry byte counts
|
|
* 8. Hash the full TOTAL_SIZE of both files and compare
|
|
* 9. Cleanup unwinds each entry: hipFileBufDeregister -> hipFree -> hipStreamDestroy
|
|
*/
|
|
|
|
#include "examples_common.h"
|
|
|
|
#include <hipfile.h>
|
|
#include <hip/hip_runtime_api.h>
|
|
|
|
#include <cerrno>
|
|
#include <fcntl.h>
|
|
#include <cinttypes>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
/// @brief Number of concurrent streams / slices.
|
|
#ifndef NUM_STREAMS
|
|
#define NUM_STREAMS 4
|
|
#endif
|
|
|
|
/// @brief Per-stream slice size in bytes. Must be a multiple of BLOCK_ALIGN.
|
|
#ifndef SLICE_SIZE
|
|
#define SLICE_SIZE (1UL * 1024UL * 1024UL)
|
|
#endif
|
|
|
|
#define TOTAL_SIZE (static_cast<size_t>(NUM_STREAMS) * SLICE_SIZE)
|
|
|
|
static_assert((SLICE_SIZE % BLOCK_ALIGN) == 0, "SLICE_SIZE must be a multiple of BLOCK_ALIGN");
|
|
|
|
/* Per-stream state. Sizes/offsets live in the struct because the async API
|
|
* takes them by pointer and dereferences them at completion time, so they
|
|
* must outlive submission. */
|
|
struct slice_state {
|
|
void *devbuf;
|
|
hipStream_t stream;
|
|
size_t io_size;
|
|
hoff_t file_offset;
|
|
hoff_t buf_offset;
|
|
ssize_t bytes_read;
|
|
ssize_t bytes_written;
|
|
bool buf_registered;
|
|
bool stream_created;
|
|
};
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
if (argc < 3 || argc > 4) {
|
|
fprintf(stderr, "Usage: %s READ_FILE WRITE_FILE [GPUID]\n", argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
const char *read_path = argv[1];
|
|
const char *write_path = argv[2];
|
|
const int gpu_id = (argc == 4) ? atoi(argv[3]) : 0;
|
|
|
|
int rfd = -1, wfd = -1;
|
|
hipFileHandle_t rhandle, whandle;
|
|
bool rhandle_open = false;
|
|
bool whandle_open = false;
|
|
slice_state slices[NUM_STREAMS] = {};
|
|
int exit_status = EXIT_FAILURE;
|
|
hipError_t hip_err;
|
|
hipFileError_t hipfile_err;
|
|
|
|
/* 1. Select GPU */
|
|
hip_err = hipSetDevice(gpu_id);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "Could not select GPU %d (%d)\n", gpu_id, hip_err);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* 2. Seed READ_FILE with a deterministic pattern */
|
|
if (seed_read_file(read_path, TOTAL_SIZE))
|
|
return EXIT_FAILURE;
|
|
|
|
/* 3. Per-entry setup: allocate, register buffer, build stream, zero on it.
|
|
* On any failure, jump to cleanup_slices which walks slices[] and unwinds
|
|
* whatever each entry has populated so far (per-entry bool flags). */
|
|
for (int i = 0; i < NUM_STREAMS; ++i) {
|
|
slice_state &s = slices[i];
|
|
s.io_size = SLICE_SIZE;
|
|
s.file_offset = static_cast<hoff_t>(i) * static_cast<hoff_t>(SLICE_SIZE);
|
|
s.buf_offset = 0;
|
|
|
|
hip_err = hipMalloc(&s.devbuf, SLICE_SIZE);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "Could not allocate %zu bytes on GPU %d for slice %d (%d)\n", SLICE_SIZE, gpu_id,
|
|
i, hip_err);
|
|
goto cleanup_slices;
|
|
}
|
|
|
|
hipfile_err = hipFileBufRegister(s.devbuf, SLICE_SIZE, 0);
|
|
if (hipFileSuccess != hipfile_err.err) {
|
|
fprintf(stderr, "Buffer register failed for slice %d (%s)\n", i,
|
|
hipFileGetOpErrorString(hipfile_err.err));
|
|
goto cleanup_slices;
|
|
}
|
|
s.buf_registered = true;
|
|
|
|
hip_err = hipStreamCreateWithFlags(&s.stream, hipStreamNonBlocking);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "hipStreamCreateWithFlags failed for slice %d (%d)\n", i, hip_err);
|
|
goto cleanup_slices;
|
|
}
|
|
s.stream_created = true;
|
|
|
|
hip_err = hipMemsetAsync(s.devbuf, 0, SLICE_SIZE, s.stream);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "hipMemsetAsync failed for slice %d (%d)\n", i, hip_err);
|
|
goto cleanup_slices;
|
|
}
|
|
}
|
|
|
|
/* 4. Open the input and output files with O_DIRECT and register both.
|
|
* The same pair of fds is shared across all NUM_STREAMS streams. */
|
|
if (open_file(read_path, O_RDONLY | O_DIRECT, 0, &rfd, &rhandle))
|
|
goto cleanup_slices;
|
|
rhandle_open = true;
|
|
|
|
if (open_file(write_path, O_WRONLY | O_CREAT | O_DIRECT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &wfd,
|
|
&whandle))
|
|
goto close_rfile;
|
|
whandle_open = true;
|
|
|
|
/* 5. Submit all reads. Stream-local ordering means the matching write on
|
|
* the same stream will see the read result without an extra fence. */
|
|
for (int i = 0; i < NUM_STREAMS; ++i) {
|
|
slice_state &s = slices[i];
|
|
hipfile_err = hipFileReadAsync(rhandle, s.devbuf, &s.io_size, &s.file_offset, &s.buf_offset,
|
|
&s.bytes_read, s.stream);
|
|
if (hipFileSuccess != hipfile_err.err) {
|
|
fprintf(stderr, "hipFileReadAsync submit failed for slice %d (%s)\n", i,
|
|
hipFileGetOpErrorString(hipfile_err.err));
|
|
goto close_wfile;
|
|
}
|
|
}
|
|
|
|
/* 6. Submit all writes. Each write is on the same stream as its
|
|
* corresponding read, so it observes the read's completion. Across
|
|
* different streams the operations may execute in parallel. */
|
|
for (int i = 0; i < NUM_STREAMS; ++i) {
|
|
slice_state &s = slices[i];
|
|
hipfile_err = hipFileWriteAsync(whandle, s.devbuf, &s.io_size, &s.file_offset, &s.buf_offset,
|
|
&s.bytes_written, s.stream);
|
|
if (hipFileSuccess != hipfile_err.err) {
|
|
fprintf(stderr, "hipFileWriteAsync submit failed for slice %d (%s)\n", i,
|
|
hipFileGetOpErrorString(hipfile_err.err));
|
|
goto close_wfile;
|
|
}
|
|
}
|
|
|
|
/* 7. Drain each stream and check completion counts. */
|
|
for (int i = 0; i < NUM_STREAMS; ++i) {
|
|
slice_state &s = slices[i];
|
|
hip_err = hipStreamSynchronize(s.stream);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "hipStreamSynchronize failed for slice %d (%d)\n", i, hip_err);
|
|
goto close_wfile;
|
|
}
|
|
|
|
if (s.bytes_read != static_cast<ssize_t>(SLICE_SIZE)) {
|
|
fprintf(stderr, "Async read short on slice %d: %zd of %zu\n", i, s.bytes_read, SLICE_SIZE);
|
|
goto close_wfile;
|
|
}
|
|
if (s.bytes_written != SLICE_SIZE) {
|
|
fprintf(stderr, "Async write short on slice %d: %zd of %zu\n", i, s.bytes_written, SLICE_SIZE);
|
|
goto close_wfile;
|
|
}
|
|
}
|
|
|
|
/* 8. Close the O_DIRECT fds before the verify path reopens the files with
|
|
* buffered I/O. Mixing O_DIRECT writes and page-cache reads on the same
|
|
* file has undefined coherence per open(2); closing first sidesteps that
|
|
* and also flushes file-size metadata for the fresh open() in the hasher.
|
|
* Then hash the full TOTAL_SIZE and compare. */
|
|
if (close_file(write_path, wfd, whandle))
|
|
goto close_rfile;
|
|
wfd = -1;
|
|
whandle_open = false;
|
|
|
|
if (close_file(read_path, rfd, rhandle))
|
|
goto cleanup_slices;
|
|
rfd = -1;
|
|
rhandle_open = false;
|
|
|
|
{
|
|
uint64_t hash;
|
|
if (verify_files_match(read_path, write_path, TOTAL_SIZE, &hash))
|
|
goto cleanup_slices;
|
|
|
|
printf("OK %s == %s (%zu bytes across %d streams, hash 0x%016" PRIx64 ")\n", read_path, write_path,
|
|
TOTAL_SIZE, NUM_STREAMS, hash);
|
|
}
|
|
|
|
exit_status = EXIT_SUCCESS;
|
|
|
|
close_wfile:
|
|
if (whandle_open) {
|
|
if (close_file(write_path, wfd, whandle))
|
|
exit_status = EXIT_FAILURE;
|
|
}
|
|
|
|
close_rfile:
|
|
if (rhandle_open) {
|
|
if (close_file(read_path, rfd, rhandle))
|
|
exit_status = EXIT_FAILURE;
|
|
}
|
|
|
|
cleanup_slices:
|
|
/* Teardown order (LIFO of setup): hipStreamDestroy -> hipFileBufDeregister -> hipFree.
|
|
* Per-entry bool flags handle partial setup from a failed init loop. */
|
|
for (int i = 0; i < NUM_STREAMS; ++i) {
|
|
slice_state &s = slices[i];
|
|
|
|
if (s.stream_created) {
|
|
hip_err = hipStreamDestroy(s.stream);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "hipStreamDestroy failed for slice %d (%d)\n", i, hip_err);
|
|
exit_status = EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (s.buf_registered) {
|
|
hipfile_err = hipFileBufDeregister(s.devbuf);
|
|
if (hipFileSuccess != hipfile_err.err) {
|
|
fprintf(stderr, "Buffer deregister failed for slice %d (%s)\n", i,
|
|
hipFileGetOpErrorString(hipfile_err.err));
|
|
exit_status = EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (s.devbuf) {
|
|
hip_err = hipFree(s.devbuf);
|
|
if (hipSuccess != hip_err) {
|
|
fprintf(stderr, "Could not free device buffer for slice %d (%d)\n", i, hip_err);
|
|
exit_status = EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return exit_status;
|
|
}
|