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
243 lines
6.5 KiB
ReStructuredText
243 lines
6.5 KiB
ReStructuredText
.. _amdgpu-async-operations:
|
|
|
|
===============================
|
|
AMDGPU Asynchronous Operations
|
|
===============================
|
|
|
|
.. contents::
|
|
:local:
|
|
|
|
Introduction
|
|
============
|
|
|
|
Asynchronous operations are memory transfers (usually between the global memory
|
|
and LDS) that are completed independently at an unspecified scope. A thread that
|
|
requests one or more asynchronous transfers can use *asyncmarks* to track
|
|
their completion. The thread waits for each asyncmark to be *completed*, which
|
|
indicates that requests initiated in *program-order* before this asyncmark have also
|
|
completed.
|
|
|
|
Operations
|
|
==========
|
|
|
|
Memory Accesses
|
|
---------------
|
|
|
|
The following instructions request asynchronous transfer of data between global
|
|
memory and LDS memory.
|
|
|
|
.. note::
|
|
|
|
These listings are *merely representative*. The actual function signatures
|
|
and supported architectures are documented in the :ref:`amdgpu-usage-guide`.
|
|
|
|
**GFX9 Async Instructions (LDS DMA)**
|
|
|
|
.. code-block:: llvm
|
|
|
|
void @llvm.amdgcn.load.async.to.lds(ptr %src, ptr %dst)
|
|
void @llvm.amdgcn.global.load.async.lds(ptr %src, ptr %dst)
|
|
void @llvm.amdgcn.raw.buffer.load.async.lds(ptr %src, ptr %dst)
|
|
void @llvm.amdgcn.raw.ptr.buffer.load.async.lds(ptr %src, ptr %dst)
|
|
void @llvm.amdgcn.struct.buffer.load.async.lds(ptr %src, ptr %dst)
|
|
void @llvm.amdgcn.struct.ptr.buffer.load.async.lds(ptr %src, ptr %dst)
|
|
|
|
**GFX12 Async Instructions**
|
|
|
|
.. code-block:: llvm
|
|
|
|
void @llvm.amdgcn.global.load.async.to.lds.type(ptr %dst, ptr %src)
|
|
void @llvm.amdgcn.global.store.async.from.lds.type(ptr %dst, ptr %src)
|
|
void @llvm.amdgcn.cluster.load.async.to.lds.type(ptr %dst, ptr %src)
|
|
|
|
Asyncmark Operations
|
|
---------------------
|
|
|
|
An *asyncmark* in the abstract machine tracks all the async operations that
|
|
are *program-ordered* before that asyncmark. An asyncmark M is said to be *completed*
|
|
only when all async operations *program-ordered* before M are reported by the
|
|
implementation as having finished, and it is said to be *outstanding* otherwise.
|
|
|
|
Thus we have the following sufficient condition:
|
|
|
|
An async operation X is *completed* at a program point P if there exists an
|
|
asyncmark M such that X is *program-ordered* before M, M is *program-ordered* before
|
|
P, and M is completed. X is said to be *outstanding* at P otherwise.
|
|
|
|
The abstract machine maintains a sequence of asyncmarks during the
|
|
execution of a function body, which excludes any asyncmarks produced by calls to
|
|
other functions encountered in the currently executing function.
|
|
|
|
``@llvm.amdgcn.asyncmark()``
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
When executed, inserts an asyncmark in the sequence associated with the
|
|
currently executing function body.
|
|
|
|
``@llvm.amdgcn.wait.asyncmark(i16 %N)``
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Waits until there are at most N outstanding asyncmarks in the sequence associated
|
|
with the currently executing function body.
|
|
|
|
Memory Consistency Model
|
|
========================
|
|
|
|
Each asynchronous operation consists of a non-atomic read on the source and a
|
|
non-atomic write on the destination. Async "LDS DMA" intrinsics result in async
|
|
accesses that guarantee visibility relative to other memory operations as
|
|
follows:
|
|
|
|
An asynchronous operation `A` program ordered before an overlapping memory
|
|
operation `X` happens-before `X` only if `A` is completed before `X`.
|
|
|
|
A memory operation `X` program ordered before an overlapping asynchronous
|
|
operation `A` happens-before `A`.
|
|
|
|
.. note::
|
|
|
|
The *only if* in the above wording implies that unlike the default LLVM
|
|
memory model, certain program order edges are not automatically included in
|
|
``happens-before``.
|
|
|
|
Examples
|
|
========
|
|
|
|
Uneven blocks of async transfers
|
|
--------------------------------
|
|
|
|
.. code-block:: c++
|
|
|
|
void foo(global int *g, local int *l) {
|
|
// first block
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
asyncmark();
|
|
|
|
// second block; longer
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
asyncmark();
|
|
|
|
// third block; shorter
|
|
async_load_to_lds(l, g);
|
|
async_load_to_lds(l, g);
|
|
asyncmark();
|
|
|
|
// Wait for first block
|
|
wait.asyncmark(2);
|
|
}
|
|
|
|
Software pipeline
|
|
-----------------
|
|
|
|
.. code-block:: c++
|
|
|
|
void foo(global int *g, local int *l) {
|
|
// first block
|
|
asyncmark();
|
|
|
|
// second block
|
|
asyncmark();
|
|
|
|
// third block
|
|
asyncmark();
|
|
|
|
for (;;) {
|
|
wait.asyncmark(2);
|
|
// use data
|
|
|
|
// next block
|
|
asyncmark();
|
|
}
|
|
|
|
// flush one block
|
|
wait.asyncmark(2);
|
|
|
|
// flush one more block
|
|
wait.asyncmark(1);
|
|
|
|
// flush last block
|
|
wait.asyncmark(0);
|
|
}
|
|
|
|
Ordinary function call
|
|
----------------------
|
|
|
|
.. code-block:: c++
|
|
|
|
extern void bar(); // may or may not make async calls
|
|
|
|
void foo(global int *g, local int *l) {
|
|
// first block
|
|
asyncmark();
|
|
|
|
// second block
|
|
asyncmark();
|
|
|
|
// function call
|
|
bar();
|
|
|
|
// third block
|
|
asyncmark();
|
|
|
|
wait.asyncmark(1); // wait for the second block
|
|
wait.asyncmark(0); // will wait for third block, including bar()
|
|
}
|
|
|
|
Implementation notes
|
|
====================
|
|
|
|
[This section is informational.]
|
|
|
|
Optimization
|
|
------------
|
|
|
|
The implementation may eliminate asyncmark/wait intrinsics in the following cases:
|
|
|
|
1. An ``asyncmark`` operation which is not included in the wait count of a later
|
|
wait operation in the current function. In particular, an ``asyncmark`` which
|
|
is not post-dominated by any ``wait.asyncmark``.
|
|
2. A ``wait.asyncmark`` whose wait count is more than the outstanding async
|
|
asyncmarks at that point. In particular, a ``wait.asyncmark`` that is not
|
|
dominated by any ``asyncmark``.
|
|
|
|
In general, at a function call, if the caller uses sufficient waits to track
|
|
its own async operations, the actions performed by the callee cannot affect
|
|
correctness. But inlining such a call may result in redundant waits.
|
|
|
|
.. code-block:: c++
|
|
|
|
void foo() {
|
|
asyncmark(); // A
|
|
}
|
|
|
|
void bar() {
|
|
asyncmark(); // B
|
|
asyncmark(); // C
|
|
foo();
|
|
wait.asyncmark(1);
|
|
}
|
|
|
|
Before inlining, the ``wait.asyncmark`` waits for asyncmark B to be completed.
|
|
|
|
.. code-block:: c++
|
|
|
|
void foo() {
|
|
}
|
|
|
|
void bar() {
|
|
asyncmark(); // B
|
|
asyncmark(); // C
|
|
asyncmark(); // A from call to foo()
|
|
wait.asyncmark(1);
|
|
}
|
|
|
|
After inlining, the ``wait.asyncmark`` now waits for asyncmark C to complete, which is
|
|
longer than necessary. Ideally, the optimizer should have eliminated asyncmark A in
|
|
the body of foo() itself.
|