Skip to content

fix(clr): release prior stream writes before memop publication - #13877

Open
zihaomu wants to merge 4 commits into
ROCm:developfrom
zihaomu:users/zihaomu/fix-memop-publication-ordering
Open

zihaomu wants to merge 4 commits into
ROCm:developfrom
zihaomu:users/zihaomu/fix-memop-publication-ordering

Conversation

@zihaomu

@zihaomu zihaomu commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Review status (2026-10-27)

The default pre-notification publication contract remains under review. Head 65467398df below still implements the barrier-packet approach; a separate one-file CLR release candidate has now passed local strict-publication and model correctness checks, without demonstrating a stable model throughput advantage. Those candidate results do not qualify the submitted head. See the updated evidence and proposed revision.

The proposed revision would remove the publication-packet-related Graph runtime changes while retaining dependent update tests. Stream-wide ordering sufficiency, public capability discovery and cross-ASIC cost remain open; no firmware/driver violation is claimed.

Motivation

Restore publication before stream-WRITE notifications. CLR historically issued a SYSTEM-release barrier before its relaxed WRITE blit. #2304 replaced that barrier with dispatch scopes while intending to preserve ordering. On the tested R9700/gfx1201, a minimal D2H → WRITE(default) → CPU acquire/read shows that these sequences are not equivalent: the notification can be observed before the copied data.

The ownership trace and controlled restoration identify the caller-side change. Batch/capture has a separate history: its submission path lacked the ordinary WRITE's pre-release, and #12692 added dispatch scopes only. This PR addresses both paths; it does not attribute every graph/batch failure to #2304 or allege a firmware/HSA violation.

Technical Details

The device helper explicitly uses a system-scope relaxed store. The notification dispatch's SYSTEM release occurs after kernel completion, which is too late for a CPU observing a store inside that kernel.

  • Add a release-only SYSTEM BARRIER_AND before ordinary WRITE/update operations and write-containing batches; preserve existing dispatch scopes and dependencies. WAIT-only batches add no publication barrier.
  • Capture the release packet itself for every replay and keep metadata slots aligned.
  • Rebuild graph packets and the synchronization plan when parameter updates change packet count, including WAIT-only ↔ WAIT+WRITE. Retain incremental updates for unchanged layouts.

The goal is to restore the existing pre-WRITE ordering and provide consistent batch publication. Formal default/batch contract wording, implementation cost, and safe fence reuse remain review points.

Issue Tracking

Eager ordering follows up #2304; batch dispatch scope follows up #12692, already present in the baseline. Targets develop; release backports require separate validation.

Test Plan

The automated minimal regression tests one D2H and one default WRITE, 2000 iterations per width. The destination is poisoned; CPU validation precedes stream synchronization. No graph, GPU WAIT, reset WRITE, result H2D, or application kernel is needed. Timeouts fail the test.

Broader tests validate CPU inputs and GPU outputs across eager, batch, native capture, captured batch, and explicit graphs. Update regressions cover both WAIT↔WRITE directions with dependent segments. Backend selection preserves supported PAL eager checks and Windows ROCr coverage while excluding unsupported batch modes.

timeout 90s ./hipStreamMemOpPublication --reporter compact
timeout 90s ./hipGraphExecBatchMemOpNodeSetParams \
  'Unit_hipGraphExecBatchMemOpNodeSetParams_LayoutChange*' --reporter compact

Test Result

Previously recorded validation of candidate 65467398df on gfx1201: minimal publication 0/4000 errors; publication suite 58 assertions/6 cases with segmented and classic executors; layout updates 1276 assertions in each recorded configuration. Actual Windows/PAL and wider platform qualification remain pending; CI is not claimed fully passing.

Separate ownership diagnostic, not a new Graph validation of this candidate: on fixed baseline 02d6cbc71114, restoring only the pre-#2304 eager WRITE release changes stale snapshots from 3149/4000 to 0/4000. Post-sync data is correct in both. The same test binary and identical non-HIP loaded-library hashes were verified. This is a modern-tree restoration, not a full historical-parent build/bisect.

Matched local event-time medians for this PR's implementation were eager WRITE 2.943→6.758 µs/op, graph single-operation nodes 2.555→3.909, and graph batches of 128 0.370→0.379 per operation. Shared hardware/unlocked clocks limit these cost estimates; cross-ASIC cost remains unqualified. Peer/NIC, metadata-queue hardware, full-model validation, and release backports remain unverified. Captured custom WAIT masks are unchanged.

Submission Checklist

A system-scope relaxed notification store can become visible to the host
before earlier stream data writes. The notification dispatch's ending
release cannot order data visibility before that store.

Insert a system-release barrier before ordinary WRITE/update blits and
batches containing writes. Capture the barrier into graphs so every replay
releases earlier stream writes. Keep metadata slots aligned and avoid an
additional barrier for WAIT-only batches.

Add a 32/64-bit data-publication regression covering eager operations,
native capture, captured batches, and explicit graphs. Only the host
re-arms the response; a preceding GPU reset WRITE can conceal this bug.
Both host input and the resulting GPU output use independent references.

On gfx1201 with same-develop CLR/ROCr, the same regression binary fails
all submission modes before this change and passes after it. Record the
WRITE packet cost and the unrelated suite-order hipGetLastError failure
in the PR test results.
@zihaomu
zihaomu requested review from a team as code owners October 21, 2026 03:42
Copilot AI balanced review requested due to automatic review settings October 21, 2026 03:42
@therock-pr-bot

therock-pr-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

✅ All Policy Checks Passed

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ⚠️ Warning Error: Source/code files changed without an accompanying unit test.
Expected: add at least one test file named like test_<name>.py / test_<name>.cpp (or <name>_test.*).
Current: code file(s) changed: projects/clr/hipamd/src/hip_graph_internal.cpp, projects/clr/hipamd/src/hip_graph_internal.hpp, projects/clr/hipamd/src/hip_stream_ops.cpp, projects/clr/rocclr/device/rocm/rocvirtual.cpp, projects/clr/rocclr/device/rocm/rocvirtual.hpp (+3 more); no test file found
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —

🎉 All policy checks passed!

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Graph updates that change the captured packet layout can leave completion-signal patches on incorrect or removed packets.

1 open issue
What changed in this PR

Adds a leading system-release barrier so stream memop notifications publish earlier writes, extending the dispatch-scope fix from #12692.

Changes:

  • Apply publication barriers to ordinary writes and write-containing batches.
  • Capture barriers for graph replay while preserving WAIT-only behavior.
  • Add 32/64-bit publication regression tests across five submission modes.
File Description
projects/​hip-tests/​catch/​unit/​stream/​hipStreamMemOpPublication.cc Adds publication regression coverage.
projects/​hip-tests/​catch/​unit/​stream/​CMakeLists.txt Builds the new test.
projects/​hip-tests/​catch/​config/​configs/​unit/​stream.yaml Registers test metadata.
projects/​clr/​rocclr/​platform/​command.hpp Tracks whether batches contain writes.
projects/​clr/​rocclr/​device/​rocm/​rocvirtual.hpp Declares the publication-barrier helper.
projects/​clr/​rocclr/​device/​rocm/​rocvirtual.cpp Emits or captures publication barriers.
projects/​clr/​hipamd/​src/​hip_stream_ops.cpp Detects writes in eager batches.
projects/​clr/​hipamd/​src/​hip_graph_internal.hpp Detects writes in graph batches.

Review effort: Balanced

Comment on lines +4296 to +4298
hsa_barrier_and_packet_t packet{};
packet.header = kBarrierPacketReleaseHeader;
std::memcpy(const_cast<uint8_t*>(cmd.getAqlPacket()), &packet, sizeof(packet));

@mangupta mangupta left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI-assisted pre-review (for @mangupta)

Comment on lines +4292 to +4307
void VirtualGPU::dispatchMemoryPublicationBarrier(amd::Command& cmd) {
if (cmd.getPktCapturingState()) {
// This release must precede the notification on every graph launch. Issuing
// a barrier on the capture queue would only fence graph instantiation.
hsa_barrier_and_packet_t packet{};
packet.header = kBarrierPacketReleaseHeader;
std::memcpy(const_cast<uint8_t*>(cmd.getAqlPacket()), &packet, sizeof(packet));
if (metadata_preloader_.HasMetadataQueue()) {
// Keep the metadata and AQL packet arrays parallel, with an invalid slot
// for this non-kernel packet.
cmd.getMetadataPacket();
}
} else {
// Also consumes pending cross-engine dependencies, including SDMA copies.
dispatchBarrierPacket(kBarrierPacketReleaseHeader);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI-assisted pre-review (for @mangupta)

[IMPORTANT] Captured batch memop packet count now depends on params, which can break graph-exec param updates

When a batch memop is captured, the barrier is emitted only if the batch contains a write. A WAIT-only batch captures one packet ([kernel]). A batch with a write captures two ([release barrier, kernel]), plus an extra metadata slot. The patch does not show the instantiated graph's per-packet bookkeeping being rebuilt when an update changes this layout. Calling hipGraphExecBatchMemOpNodeSetParams() to switch a node between WAIT-only and write-containing can leave the bookkeeping indexed to the old layout. That bookkeeping covers segment completion/synchronization patches and metadata slots. The update can then land a completion signal on the leading barrier instead of the memop kernel, so a dependent segment may start before the memops finish. The reverse update can leave a stale patch pointing past the shorter packet list.

Suggested fix: Make the captured layout independent of params, for example by always emitting the release barrier (or a no-op barrier slot) for graph-captured batch memops. Alternatively, have the exec param-update path detect a layout change and rebuild or retarget the segment synchronization and metadata plan, or reject the update. Add a test that updates a batch memop exec node WAIT-only→write and write→WAIT-only with hipGraphExecBatchMemOpNodeSetParams(), adds a dependent node/segment after it, and checks ordering and data.

Comment on lines +1 to +19
// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI-assisted pre-review (for @mangupta)

[IMPORTANT] New test file is missing the SPDX-License-Identifier header

The new source file has the full MIT license text but no SPDX-License-Identifier: MIT line, which new files are required to carry before merge.

Suggested fix: Add // SPDX-License-Identifier: MIT to the file header, following the convention used in other recent hip-tests sources.

Comment on lines +4379 to +4381
// The system-scope release at the end of the blit dispatch is too late:
// the host or a peer can observe the relaxed notification store first.
dispatchMemoryPublicationBarrier(cmd);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI-assisted pre-review (for @mangupta)

[SUGGESTION] Every eager hipStreamWriteValue now issues an extra barrier packet even when nothing is pending

ROCCLR_COMMAND_STREAM_WRITE_VALUE now always calls dispatchMemoryPublicationBarrier, which in eager mode dispatches a separate system-release barrier packet. Signaling patterns that issue many back-to-back writes pay one extra AQL packet and fence per write. This includes cases where the previous packet already ended with a system-scope release and no stream writes have happened since.

Suggested fix: Consider skipping the barrier when the last packet dispatched on this queue already carried a system-scope release and no later work has been enqueued. Reuse the existing fence-state tracking rather than adding new state. Document the measured overhead in the PR.

A memop graph node can grow from one packet to two when it starts writing,
or shrink when it becomes WAIT-only. Index-based packet replacement leaves
completion patches on the leading barrier or pointing at the removed
kernel, allowing dependent segments to run early or never complete.

Use the existing full recapture path when the packet count changes. This
clears pending batch references, rebuilds the sync plan and refreshes the
flat and filtered packet buffers. Keep incremental updates for unchanged
packet counts.

Add 32/64-bit regressions that alternate WAIT-only and WAIT+WRITE nodes at
a segment boundary with dependent consumers. Exercise executable setters
and whole-graph updates with an earlier pending kernel update, checking
actual producer data and the notification value on every replay.

Validated on gfx1201: default, round-robin, DFS and small-queue regressions;
existing graph update, segment ordering and node enable tests; strict
publication probes and shared-IO protocol. The old candidate fails the
same regression binary. A peer-device test skips on the single GPU.
@mangupta

Copy link
Copy Markdown
Contributor

🤖 AI-assisted pre-review (for @mangupta)

Re-reviewed the updated head (e736720ace2c) — no new findings at or above the posting threshold.

@mangupta

Copy link
Copy Markdown
Contributor

🤖 AI-assisted pre-review (for @mangupta)

Re-reviewed the updated head (1e6b4dad5574) — no new findings at or above the posting threshold.

@zihaomu

zihaomu commented Oct 21, 2026 •

Copy link
Copy Markdown
Member Author

@mangupta, the repeated CI failures have a configuration cause:

  • Policy job waits 900 s for pre-commit. The policy requires that name globally, but the producer workflows' PR path filters exclude this CLR/hip-tests change. At the recorded snapshot I fetched all 109 check-runs (both pages); none was named pre-commit, and there are no classic statuses.
  • The producer trigger already documents this missing-path timeout. Waiting or rerunning the unchanged configuration cannot create the missing check.
  • The old Multi-Arch summary reports cancelled Linux/Windows dependencies. The later same-head run 39555659418 replaced it after the label event; its actual build/test results should be used.

The refreshed snapshot at 2026-10-21 08:47 UTC contains 274 check-runs and still no pre-commit. The replacement Multi-Arch run remains active and now has failed Windows hip-tests (PAL) shards 3/4 and 4/4, plus an MIOpen shard. The failed PAL cases are the new Unit_hipStreamMemOp_PublishPriorCopies uint32_t/uint64_t tests: graph submissions 1–4 report served=0 and 2000 bad outputs each. These are genuine regression-test failures requiring backend-specific investigation, independent of the policy timeout. All four Linux ROCr HIP shards and all four Windows ROCr HIP shards succeeded in this snapshot. This CI-only change does not resolve the PAL failures; I am not claiming the full matrix passes.

I prepared a separate CI fix: #13884. It derives applicability from trusted producer workflows, retains covered/observed/unknown checks, and fetches all check-run pages. All 63 local unit/integration tests pass. A completely offline replay of this PR's saved metadata/checks returns 1 on the baseline and 0 on the candidate; no upstream check result was changed by that replay.

Could the CI maintainers review and merge that fix into the trusted base, then start a new policy event for this PR using the updated base SHA? The bot checks out the base, so putting the fix only on this HIP branch or rerunning an event pinned to the old base would not apply it. This account does not have upstream Actions write access. The HIP/CLR head remains unchanged.

@zihaomu

zihaomu commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Related downstream report and application-level workaround: FlashML-org/FreeToken#764.

FreeToken's CPU+single-GPU hybrid MoE path exhibited stale CPU expert inputs during ROCm graph replay even though capture succeeded. That PR bypasses the memcpy + memop handoff with existing mapped-memory publish/combine kernels and adds graph-output checks before model capture. Its R9700 / ROCm 7.14.1 validation includes 72 passing tests and Qwen3-30B-A3B-FP8 hybrid Graph requests at 512 and 32K input lengths.

This CLR PR addresses the underlying publication-ordering problem independently of FreeToken. The two changes operate at different layers: the FreeToken workaround protects unpatched runtimes; the CLR change fixes the notification's publication boundary.

Full-model validation against this patched CLR is still pending. The remaining end-to-end check is to restore FreeToken's original memcpy + memop path, run its multi-layer replay regression, then validate model outputs and hybrid Graph performance. FreeToken's existing results should not be treated as model-level validation or speedup evidence for this runtime patch.

@AlexXAmd AlexXAmd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR states:

“On gfx1201, the CPU can observe a memop notification before preceding D2H copies are visible, read stale inputs, and produce incorrect results.”

This is a serious claim. If correct, it suggests a fundamental ordering/visibility issue on gfx1201. Before making a broad change in CLR, I think we need to establish the following:

Prove the issue with a minimal reproducer.
Please provide a simpler reproducer that isolates the D2H copy and memop notification ordering, without graph-specific complexity.

Identify the root cause.
If the stated behavior is confirmed, we need to understand why it occurs specifically on gfx1201. In particular, we should determine whether an underlying software or hardware component is failing to provide the ordering/visibility required by the relevant HSA/AQL semantics.

Determine the correct component for the fix.
Even assuming the claim is correct, it does not establish that CLR is the appropriate layer to fix it. We should identify the component responsible for the missing guarantee before adding a workaround here.

I am also concerned that the proposed change appears to affect all ASICs while the reported problem is gfx1201-specific. If this introduces additional synchronization or release operations, we need to understand the performance cost imposed on unaffected ASICs and why that cost is necessary.

Finally, I don't understand why the fix is limited to the graph path. The stated problem is broader than graph execution: if a memop notification can become CPU-visible before preceding D2H copies, the same ordering issue should be considered wherever this sequence can occur.

For these reasons, I don't think this PR is ready to proceed in its current form. We first need proof of the isolated issue, root-cause analysis, and identification of the correct component in which to fix it.

@zihaomu

zihaomu commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

@AlexXAmd I have completed the isolated reproducer and additional HSA/AQL controls requested in your review. The evidence now identifies a specific missing operation in CLR's software sequence: publication to SYSTEM before the notification store becomes observable. Below are the source-level explanation, controls, and the proposed correction.

1. Graph-free proof and controlled CLR replacement

The 85-line HIP/C++ reproducer below tests one 544-byte D2H copy followed by one default WRITE. A CPU observer acquires the notification and checks a snapshot before the main thread synchronizes the stream. Device input preparation finishes before the tested sequence; the host destination is poisoned. There is no graph, batch, GPU WAIT, reset WRITE, custom GPU kernel, or application computation.

On one R9700/gfx1201, using the same binary, ROCr, COMGR, driver, and GPU, changing only the HIP/CLR DSO gives:

HIP/CLR CPU stale snapshots / iterations Errors after stream sync Exit
Rebuilt develop baseline 02d6cbc71114 9964 / 10000 0 1
PR candidate 0 / 10000 0 0

Neither run timed out. The current locked ROCm 10.1 foundation (HIP 7.16.26385) separately fails 9962/10000; that is a different runtime control, not part of the DSO-only comparison above. The develop libraries are local rebuilds reporting HIP 7.17.26422, not a release-package claim. I have not tested a newer develop revision in this follow-up.

2. The concrete CLR composition and why the existing scopes are insufficient

The eager call chain is:

hipStreamWriteValue64
  → ihipStreamOperation / StreamOperationCommand
  → VirtualGPU::submitStreamOperation (WRITE branch)
  → KernelBlitManager::streamOpsWrite / streamOpsUpdate
  → __amd_rocclr_streamOpsWrite
  → device-library __amd_streamOpsWrite

The baseline WRITE branch calls addSystemScope() and launches the blit. The CLR blit wrapper calls __amd_streamOpsWrite; the matching LLVM device-library source implements that primitive with memory_order_relaxed, memory_scope_all_svm_devices for both widths.

The actual AQL trace for the failing minimal sequence is:

D2H compute-blit: 0x0d02  barrier=1, acquire=SYSTEM, release=AGENT
notification:     0x1502  barrier=1, acquire=SYSTEM, release=SYSTEM
                           └─ system-scope relaxed notification store

The HSA header definitions distinguish three guarantees:

  • The barrier bit orders packet execution/completion.
  • Acquire makes previously released data available to subsequent device loads; it does not publish prior GPU writes to the CPU.
  • Dispatch release occurs after kernel completion, before packet completion. The CPU observes a store inside the notification kernel, rather than waiting for that packet's completion signal.

Consequently, the notification's ending SYSTEM release is too late to provide the required pre-store publication. addSystemScope() and adjustHeader() configure dispatch scopes; neither inserts a release before the relaxed store. The trace is the effective header result after that processing. Disabling SDMA still fails (9941/10000), so this counterexample does not require a broken SDMA dependency chain.

This locates the insufficiency in the sequence CLR constructs from a copy and a low-level relaxed notification primitive. The primitive itself is allowed to be relaxed; its system scope is not a substitute for release ordering. Under the intended default WRITE publication contract, the layer composing those operations must establish the missing boundary.

3. Independent HSA controls distinguish a missing request from a violated guarantee

I bypassed CLR's copy/memop submission and queued the tested copy and notification directly on an independent HSA queue. HIP is used only for allocation/input setup. The copy kernel and memory types are fixed, and the notification dispatch always has acquire/release SYSTEM. Only the indicated publication operation changes:

Direct AQL sequence Stale CPU snapshots / 10000 Errors after completion
Copy release AGENT → relaxed notification 9951 0
Copy release SYSTEM → same relaxed notification 0 0
Copy release AGENT → release notification store 0 0
Copy release AGENT → SYSTEM-release barrier → relaxed notification 0 0
Copy release AGENT → no-fence barrier → relaxed notification 86 0

All groups completed without timeouts. The no-fence barrier changes timing but does not restore correctness. In the separately compiled control kernels, relaxed notification emits global_store_b64 ... scope:SCOPE_SYS; the release version additionally emits global_wb scope:SCOPE_SYS and s_wait_storecnt 0 before that store. This ISA evidence is from the control kernels, not an extracted runtime blit code object.

These results are consistent with HSA executing the requested weak/strong sequences. They do not require a hardware or ROCr guarantee violation to explain the failure. The demonstrated software defect is the absence of the publication request in the default-memop composition, not evidence that a requested SYSTEM release was ignored.

4. Proposed CLR fix, including eager and graph paths

The proposed helper changes the sequence to:

copy [release AGENT]
  → BARRIER_AND 0x1103 [barrier=1, acquire=NONE, release=SYSTEM]
  → notification dispatch [existing scopes; existing relaxed store]
  • Eager: submitStreamOperation()'s WRITE/update branch calls the helper before launching the blit. It uses the existing barrier dispatcher, including pending dependency signals. The fix is therefore not limited to graphs.
  • Batch/graph: submitBatchMemoryOperation() requests the release only when a batch contains WRITE32/64. Captured stream writes become single-element batch nodes and reach this path, as do explicit batch nodes. During capture the barrier packet itself is stored before the blit packet; it must replay on every launch, rather than executing only during instantiation. WAIT-only batches add no barrier, and metadata slots remain parallel.
  • Graph updates: changing WAIT-only ↔ write-containing changes [kernel] ↔ [release, kernel]. The layout-change fix recaptures packets and rebuilds synchronization patches, so segment-completion signals remain on the final kernel. Tests cover both directions with dependent segments, including node setters and whole-graph updates.

CLR is a concrete fix point because it owns the API-to-command translation, dispatch scopes, pending dependencies, and graph packet replay. A release operation in the device helper is another implementation option; the controls show it works for this case, but it would require its own integration and dependency analysis. I am not claiming that an extra AQL barrier is the only correct or optimal implementation.

5. Scope, API contract, and cost

The contract being implemented is default stream WRITE publishing earlier stream writes before its value can serve as a notification, consistent with the intent of #12692. HIP's public header describes execution after earlier stream commands, but is less explicit about CPU publication. I would appreciate confirmation of that intended contract; I am not using CUDA documentation as a substitute for a HIP specification.

Only gfx1201 was tested; I cannot explain a gfx1201-only distinction without measurements on another ASIC. This is an absent software publication boundary, rather than evidence of a hardware-specific erratum, so the candidate currently supplies it on the ROCr path for all ASICs. That does not establish that every existing sequence needs an additional packet, or that all-ASIC cost is acceptable.

Historical local GPU-event medians on gfx1201 were eager WRITE 2.889→5.988 µs/op, graph single WRITE node 2.554→3.833, graph batch of 128 0.331→0.342. Seven samples of 64000 operations were used, without exclusive hardware or locked clocks; these are cost estimates, not cross-ASIC qualification. Proving when an existing release can safely be reused remains necessary before eliding barriers. PAL test gating/CI failures, peer/NIC publication, metadata-queue hardware, and other ASICs remain open. The PR description has been revised to distinguish these limits from the demonstrated CLR sequence defect.

85-line graph-free reproducer: copy_notify_repro.cpp

Select exactly one GPU. EXPECT_PCI is an optional identity guard; omit it on another machine. The standard-SDK build command below is the portable equivalent; the tested wheel-SDK build used host g++ with the same HIP header and DSO (no device compilation).

hipcc -O2 -pthread copy_notify_repro.cpp -o repro
HIP_VISIBLE_DEVICES=GPU-541f8c51daca3a7e EXPECT_PCI=0000:43:00.0 timeout 60s ./repro 10000
// Graph-free publication reproducer. Build: hipcc -O2 -pthread copy_notify_repro.cpp -o repro
#include <hip/hip_runtime.h>
#include <array>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>

#define HIP(call) do { hipError_t e = (call); if (e != hipSuccess) { \
  std::fprintf(stderr, "%s: %s\n", #call, hipGetErrorString(e)); std::exit(2); } } while (0)
constexpr unsigned words = 136;
static uint32_t expected(unsigned epoch, unsigned index) {
  return 0x80000001u ^ (epoch * 107u) ^ (index * 13007u);
}
int main(int argc, char** argv) {
  const unsigned iterations = argc > 1 ? std::strtoul(argv[1], nullptr, 10) : 10000;
  int count = 0, version = 0;
  HIP(hipGetDeviceCount(&count));
  if (count != 1 || !iterations) return 2;
  HIP(hipSetDevice(0));
  HIP(hipRuntimeGetVersion(&version));
  char pci[32]{};
  HIP(hipDeviceGetPCIBusId(pci, sizeof(pci), 0));
  const char* wanted_pci = std::getenv("EXPECT_PCI");
  if (wanted_pci && std::strcmp(wanted_pci, pci)) return 2;
  std::printf("IDENTITY hip=%d pci=%s iterations=%u\n", version, pci, iterations);
  std::ifstream maps("/proc/self/maps");
  for (std::string line; std::getline(maps, line);)
    if (line.find("libamdhip64") != std::string::npos || line.find("libhsa-runtime64") != std::string::npos)
      std::cout << "MAP " << line << '\n';
  std::cout.flush();
  uint32_t *device = nullptr, *host = nullptr;
  uint64_t* ready = nullptr;
  HIP(hipMalloc(reinterpret_cast<void**>(&device), words * sizeof(*device)));
  HIP(hipHostMalloc(reinterpret_cast<void**>(&host), words * sizeof(*host),
                    hipHostMallocMapped | hipHostMallocCoherent));
  HIP(hipExtMallocWithFlags(reinterpret_cast<void**>(&ready), sizeof(*ready), hipMallocSignalMemory));
  __atomic_store_n(ready, uint64_t{0}, __ATOMIC_RELEASE);
  hipStream_t stream{};
  HIP(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
  std::atomic<unsigned> checked{0};
  std::atomic<bool> stopped{false};
  unsigned wrong_before_sync = 0, wrong_after_sync = 0;
  std::thread observer([&] {
    std::array<uint32_t, words> snapshot{};
    for (unsigned epoch = 1; epoch <= iterations; ++epoch) {
      auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
      while (__atomic_load_n(ready, __ATOMIC_ACQUIRE) != epoch) {
        if (stopped.load()) return;
        if (std::chrono::steady_clock::now() > deadline) { stopped = true; return; }
      }
      std::memcpy(snapshot.data(), host, sizeof(snapshot));
      for (unsigned i = 0; i < words; ++i) {
        if (snapshot[i] != expected(epoch, i)) { ++wrong_before_sync; break; }
      }
      checked.store(epoch, std::memory_order_release);
    }
  });
  std::array<uint32_t, words> input{};
  for (unsigned epoch = 1; epoch <= iterations && !stopped.load(); ++epoch) {
    for (unsigned i = 0; i < words; ++i) input[i] = expected(epoch, i);
    HIP(hipMemcpyAsync(device, input.data(), sizeof(input), hipMemcpyHostToDevice, stream));
    HIP(hipStreamSynchronize(stream));  // Finish input setup before the tested sequence.
    std::memset(host, 0xa5, sizeof(input));
    HIP(hipMemcpyAsync(host, device, sizeof(input), hipMemcpyDeviceToHost, stream));
    HIP(hipStreamWriteValue64(stream, ready, epoch, hipStreamWriteValueDefault));
    while (checked.load(std::memory_order_acquire) != epoch && !stopped.load()) std::this_thread::yield();
    if (stopped.load()) break;
    HIP(hipStreamSynchronize(stream));  // CPU snapshot was already checked.
    for (unsigned i = 0; i < words; ++i) {
      if (host[i] != expected(epoch, i)) { ++wrong_after_sync; break; }
    }
  }
  const bool timeout = checked.load() != iterations;
  stopped = true;
  observer.join();
  std::printf("RESULT checked=%u before_sync_errors=%u after_sync_errors=%u timeout=%d\n",
              checked.load(), wrong_before_sync, wrong_after_sync, int(timeout));
  HIP(hipStreamDestroy(stream)); HIP(hipFree(ready)); HIP(hipHostFree(host)); HIP(hipFree(device));
  return timeout ? 124 : (wrong_before_sync || wrong_after_sync ? 1 : 0);
}
Direct HSA/AQL control sources and build/run instructions

These are the exact control sources used above. raw_aql.cpp deliberately has UUID/PCI guards for my reserved GPU; replace those two identity strings for another GPU, and change the compilation target accordingly. The copy/notification packets are submitted by this host code to its own HSA queue.

raw_kernels.cpp:

#include <hip/hip_runtime.h>
#include <cstdint>
extern "C" __global__ void raw_copy(const uint32_t* src, uint32_t* dst, uint32_t words) {
  for (uint32_t i = 0; i < words; ++i) dst[i] = src[i];
}
extern "C" __global__ void raw_relaxed(uint64_t* flag, uint64_t epoch) {
  __hip_atomic_store(flag, epoch, __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_SYSTEM);
}
extern "C" __global__ void raw_release(uint64_t* flag, uint64_t epoch) {
  __hip_atomic_store(flag, epoch, __ATOMIC_RELEASE, __HIP_MEMORY_SCOPE_SYSTEM);
}

raw_aql.cpp:

#include <hip/hip_runtime.h>
#include <hsa/hsa.h>
#include <hsa/hsa_ext_amd.h>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <unistd.h>
#include <vector>

static void hip_check(hipError_t s) { if (s) throw std::runtime_error(hipGetErrorString(s)); }
static void hsa_check(hsa_status_t s) {
  if (s) { const char* msg = nullptr; hsa_status_string(s, &msg); throw std::runtime_error(msg ? msg : "HSA error"); }
}
static uint32_t pattern(unsigned epoch, size_t i) {
  return 0x80000001u ^ (epoch * 107u) ^ (static_cast<uint32_t>(i) * 13007u);
}
static hsa_status_t find_agent(hsa_agent_t agent, void* p) {
  hsa_device_type_t type{};
  hsa_agent_get_info(agent, HSA_AGENT_INFO_DEVICE, &type);
  if (type != HSA_DEVICE_TYPE_GPU) return HSA_STATUS_SUCCESS;
  char uuid[32]{};
  hsa_agent_get_info(agent, static_cast<hsa_agent_info_t>(HSA_AMD_AGENT_INFO_UUID), uuid);
  if (std::string(uuid) == "GPU-541f8c51daca3a7e") *static_cast<hsa_agent_t*>(p) = agent;
  return HSA_STATUS_SUCCESS;
}
static hsa_status_t find_kernarg(hsa_region_t region, void* p) {
  hsa_region_segment_t segment{};
  hsa_region_get_info(region, HSA_REGION_INFO_SEGMENT, &segment);
  if (segment == HSA_REGION_SEGMENT_GLOBAL) {
    uint32_t flags = 0;
    hsa_region_get_info(region, HSA_REGION_INFO_GLOBAL_FLAGS, &flags);
    if (flags & HSA_REGION_GLOBAL_FLAG_KERNARG) *static_cast<hsa_region_t*>(p) = region;
  }
  return HSA_STATUS_SUCCESS;
}
struct Kernel {
  uint64_t object = 0;
  uint32_t private_size = 0, group_size = 0, arg_size = 0;
  void* args = nullptr;
};
static Kernel load_kernel(hsa_executable_t executable, hsa_agent_t agent, hsa_region_t region,
                          const char* name) {
  hsa_executable_symbol_t symbol{};
  hsa_check(hsa_executable_get_symbol_by_name(executable, name, &agent, &symbol));
  Kernel k;
  hsa_check(hsa_executable_symbol_get_info(symbol, HSA_EXECUTABLE_SYMBOL_INFO_KERNEL_OBJECT, &k.object));
  hsa_check(hsa_executable_symbol_get_info(symbol, HSA_EXECUTABLE_SYMBOL_INFO_KERNEL_PRIVATE_SEGMENT_SIZE, &k.private_size));
  hsa_check(hsa_executable_symbol_get_info(symbol, HSA_EXECUTABLE_SYMBOL_INFO_KERNEL_GROUP_SEGMENT_SIZE, &k.group_size));
  hsa_check(hsa_executable_symbol_get_info(symbol, HSA_EXECUTABLE_SYMBOL_INFO_KERNEL_KERNARG_SEGMENT_SIZE, &k.arg_size));
  hsa_check(hsa_memory_allocate(region, k.arg_size, &k.args));
  std::memset(k.args, 0, k.arg_size);
  return k;
}
static uint16_t header(unsigned type, unsigned acquire, unsigned release) {
  return type | (1u << HSA_PACKET_HEADER_BARRIER) |
         (acquire << HSA_PACKET_HEADER_SCACQUIRE_FENCE_SCOPE) |
         (release << HSA_PACKET_HEADER_SCRELEASE_FENCE_SCOPE);
}
static void dispatch(hsa_queue_t* queue, uint64_t index, const Kernel& k, uint16_t hdr,
                     hsa_signal_t completion) {
  hsa_kernel_dispatch_packet_t packet{};
  packet.setup = 1 << HSA_KERNEL_DISPATCH_PACKET_SETUP_DIMENSIONS;
  packet.workgroup_size_x = packet.workgroup_size_y = packet.workgroup_size_z = 1;
  packet.grid_size_x = packet.grid_size_y = packet.grid_size_z = 1;
  packet.kernel_object = k.object;
  packet.kernarg_address = k.args;
  packet.private_segment_size = k.private_size;
  packet.group_segment_size = k.group_size;
  packet.completion_signal = completion;
  auto* target = static_cast<hsa_kernel_dispatch_packet_t*>(queue->base_address) + (index & (queue->size - 1));
  std::memcpy(target, &packet, sizeof(packet));
  __atomic_store_n(&target->header, hdr, __ATOMIC_RELEASE);
}
int main(int argc, char** argv) {
  try {
    if (argc < 3) return 2;
    const std::string mode = argv[2];
    const unsigned iterations = argc > 3 ? std::stoul(argv[3]) : 10000;
    if (mode != "agent-relaxed" && mode != "system-relaxed" && mode != "agent-release" &&
        mode != "agent-barrier" && mode != "agent-nofence") return 2;
    hip_check(hipSetDevice(0));
    int count = 0;
    hip_check(hipGetDeviceCount(&count));
    char pci[32]{};
    hip_check(hipDeviceGetPCIBusId(pci, sizeof(pci), 0));
    if (count != 1 || std::string(pci) != "0000:43:00.0") throw std::runtime_error("Wrong device selection");
    hsa_check(hsa_init());
    hsa_agent_t agent{};
    hsa_check(hsa_iterate_agents(find_agent, &agent));
    if (!agent.handle) throw std::runtime_error("Target HSA agent unavailable");
    hsa_region_t region{};
    hsa_check(hsa_agent_iterate_regions(agent, find_kernarg, &region));
    if (!region.handle) throw std::runtime_error("No kernarg region");
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) throw std::runtime_error("Cannot open HSACO");
    hsa_code_object_reader_t reader{};
    hsa_check(hsa_code_object_reader_create_from_file(fd, &reader));
    hsa_executable_t executable{};
    hsa_check(hsa_executable_create_alt(HSA_PROFILE_FULL, HSA_DEFAULT_FLOAT_ROUNDING_MODE_DEFAULT, nullptr, &executable));
    hsa_check(hsa_executable_load_agent_code_object(executable, agent, reader, nullptr, nullptr));
    hsa_check(hsa_executable_freeze(executable, nullptr));
    Kernel copy = load_kernel(executable, agent, region, "raw_copy.kd");
    Kernel notify = load_kernel(executable, agent, region, mode == "agent-release" ? "raw_release.kd" : "raw_relaxed.kd");
    hsa_queue_t* queue = nullptr;
    hsa_check(hsa_queue_create(agent, 64, HSA_QUEUE_TYPE_SINGLE,
        [](hsa_status_t e, hsa_queue_t*, void*) { std::fprintf(stderr, "QUEUE_ERROR %d\n", int(e)); std::_Exit(2); },
        nullptr, UINT32_MAX, UINT32_MAX, &queue));
    hsa_signal_t complete{};
    hsa_check(hsa_signal_create(1, 0, nullptr, &complete));
    constexpr size_t words = 136, bytes = words * 4;
    uint32_t *device = nullptr, *host = nullptr;
    uint64_t* ready = nullptr;
    hip_check(hipMalloc(reinterpret_cast<void**>(&device), bytes));
    hip_check(hipHostMalloc(reinterpret_cast<void**>(&host), bytes, hipHostMallocMapped | hipHostMallocCoherent));
    hip_check(hipExtMallocWithFlags(reinterpret_cast<void**>(&ready), 8, hipMallocSignalMemory));
    __atomic_store_n(ready, uint64_t{0}, __ATOMIC_RELEASE);
    struct CopyArgs { uint32_t* src; uint32_t* dst; uint32_t words; } ca{device, host, words};
    struct NotifyArgs { uint64_t* flag; uint64_t value; } na{ready, 0};
    const size_t copy_arg_bytes = offsetof(CopyArgs, words) + sizeof(ca.words);
    if (copy.arg_size < copy_arg_bytes || notify.arg_size < sizeof(na))
      throw std::runtime_error("Invalid kernarg layout: copy=" + std::to_string(copy.arg_size) +
                               " notify=" + std::to_string(notify.arg_size));
    std::memcpy(copy.args, &ca, copy_arg_bytes);
    std::atomic<unsigned> checked{0};
    std::atomic<bool> stop{false}, timeout{false};
    unsigned bad = 0, bad_after = 0;
    std::cout << "IDENTITY pci=" << pci << " mode=" << mode << " hsa_queue=" << queue << std::endl;
    std::ifstream maps("/proc/self/maps");
    for (std::string line; std::getline(maps, line);)
      if (line.find(".so") != std::string::npos) std::cout << "MAP " << line << '\n';
    std::thread observer([&] {
      std::vector<uint32_t> snapshot(words);
      for (unsigned epoch = 1; epoch <= iterations; ++epoch) {
        auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
        while (__atomic_load_n(ready, __ATOMIC_ACQUIRE) != epoch) {
          if (stop.load()) return;
          if (std::chrono::steady_clock::now() > deadline) { timeout = true; return; }
        }
        std::memcpy(snapshot.data(), host, bytes);
        for (size_t i = 0; i < words; ++i) if (snapshot[i] != pattern(epoch, i)) { ++bad; break; }
        checked.store(epoch, std::memory_order_release);
      }
    });
    try {
      std::vector<uint32_t> input(words);
      for (unsigned epoch = 1; epoch <= iterations; ++epoch) {
        for (size_t i = 0; i < words; ++i) input[i] = pattern(epoch, i);
        hip_check(hipMemcpy(device, input.data(), bytes, hipMemcpyHostToDevice));
        std::memset(host, 0xa5, bytes);
        na.value = epoch;
        std::memcpy(notify.args, &na, sizeof(na));
        hsa_signal_store_screlease(complete, 1);
        const bool barrier = mode == "agent-barrier" || mode == "agent-nofence";
        const unsigned packets = barrier ? 3 : 2;
        uint64_t start = hsa_queue_add_write_index_relaxed(queue, packets);
        while (start + packets - hsa_queue_load_read_index_scacquire(queue) > queue->size) std::this_thread::yield();
        dispatch(queue, start, copy, header(HSA_PACKET_TYPE_KERNEL_DISPATCH, 2, mode == "system-relaxed" ? 2 : 1), {});
        if (barrier) {
          hsa_barrier_and_packet_t p{};
          auto* dst = static_cast<hsa_barrier_and_packet_t*>(queue->base_address) + ((start + 1) & (queue->size - 1));
          std::memcpy(dst, &p, sizeof(p));
          __atomic_store_n(&dst->header, header(HSA_PACKET_TYPE_BARRIER_AND, 0, mode == "agent-barrier" ? 2 : 0), __ATOMIC_RELEASE);
        }
        dispatch(queue, start + packets - 1, notify, header(HSA_PACKET_TYPE_KERNEL_DISPATCH, 2, 2), complete);
        hsa_signal_store_screlease(queue->doorbell_signal, start + packets - 1);
        auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
        while (checked.load(std::memory_order_acquire) != epoch && !timeout.load()) {
          if (std::chrono::steady_clock::now() > deadline) timeout = true;
          std::this_thread::yield();
        }
        if (timeout.load()) break;
        while (hsa_signal_load_scacquire(complete) != 0) {
          if (std::chrono::steady_clock::now() > deadline) { timeout = true; break; }
        }
        if (timeout.load()) break;
        for (size_t i = 0; i < words; ++i) if (host[i] != pattern(epoch, i)) { ++bad_after; break; }
      }
    } catch (...) { stop = true; observer.join(); throw; }
    stop = true;
    observer.join();
    std::printf("RESULT iterations=%u checked=%u bad=%u bad_after_completion=%u timeout=%d\n", iterations, checked.load(), bad, bad_after, int(timeout.load()));
    if (timeout.load()) return 124;
    hsa_check(hsa_queue_destroy(queue));
    hsa_check(hsa_signal_destroy(complete));
    hsa_check(hsa_memory_free(copy.args));
    hsa_check(hsa_memory_free(notify.args));
    hsa_check(hsa_executable_destroy(executable));
    hsa_check(hsa_code_object_reader_destroy(reader));
    close(fd);
    hip_check(hipFree(ready)); hip_check(hipHostFree(host)); hip_check(hipFree(device));
    hsa_check(hsa_shut_down());
    return bad || bad_after ? 1 : 0;
  } catch (const std::exception& e) { std::cerr << "ERROR " << e.what() << '\n'; return 2; }
}

The following are the tested wheel-SDK build steps, with output paths made relative. The shim supplies an unversioned HIP link only inside the experiment directory; it does not modify the SDK. Use a matched compiler/device-library/ROCr installation. The bundler step is needed when hipcc --genco emits an offload bundle rather than an ELF code object.

sdk=/opt/venv/lib/python3.13/site-packages/_rocm_sdk_core
mkdir -p sdk-shim/lib
ln -s "$sdk/include" sdk-shim/include
ln -s "$sdk/lib/libamdhip64.so.7" sdk-shim/lib/libamdhip64.so
hipcc --genco -O2 --offload-arch=gfx1201 \
  --rocm-device-lib-path="$sdk/lib/llvm/amdgcn/bitcode" \
  --hip-path="$PWD/sdk-shim" raw_kernels.cpp -o raw-kernels.bundle
"$sdk/lib/llvm/bin/clang-offload-bundler" --unbundle --type=o \
  --targets=hipv4-amdgcn-amd-amdhsa--gfx1201 \
  --input=raw-kernels.bundle --output=raw-kernels.hsaco
g++ -O2 -std=c++17 -D__HIP_PLATFORM_AMD__ -I"$sdk/include" \
  raw_aql.cpp -L"$sdk/lib" -l:libamdhip64.so.7 -l:libhsa-runtime64.so.1 \
  -pthread -o raw-aql
for mode in agent-relaxed system-relaxed agent-release agent-barrier agent-nofence; do
  HIP_VISIBLE_DEVICES=GPU-541f8c51daca3a7e timeout 60s ./raw-aql raw-kernels.hsaco "$mode" 10000
done

The two negative controls return 1 for data errors; the three publication controls return 0. Exit 124/timeouts are failures. GPU_STREAMOPS_CP_WAIT=1 was not used.

Recorded source, binary, and runtime SHA256 identities
copy_notify_repro.cpp  19ae726c83baaa2cef802ac495c188835d390ccc898a1b45f5d8220b87284ecc
raw_aql.cpp  9c3a27d2a6d08a5bd04718e17122033cb7dbc01d6c84e71eb084f37e7ee0d253
raw_kernels.cpp  4ca21fc5e0b67c7aa19e0cb94e15ae636a3873097788529e7ebb2d7f7918de3a
reproducer binary  91c25b6119c9d63398753a8f37f9eb10cbf5143be53a10dfd6c9e8f2d9360723
direct-HSA binary  85924cbaaaa3642b12d50d085c13a63b851fe775fe7e4d78bb40b1569ee99b6a
direct-HSA HSACO   9cf3f137c5341760bdb1db1da944d7be4a262bc26a985e72319c09fa2c7b8ce7
repro-foundation libhsa-runtime64.so.1  12d49db16881b9505629192231d5b3971e9030b26a326fc2c003e92da7de0c44
repro-foundation libamdhip64.so.7  6d1dbee8cb3ad924e4c1bca7734e22f2c93026eb3bc871a7f64a916778d55e36
repro-develop-base libamdhip64.so.7.17.26422-0000000  572889cbd0014429be2c39cdc87b63bb975cdeeba260e558d09dcee7a7c33fb7
repro-develop-base libhsa-runtime64.so.1.21.0  fe3cf243b89560ce89129d7c995e27db594f1cd7a54171ca586d89589e564e04
repro-develop-fix libamdhip64.so.7.17.26422-0000000  919f5f5d476479e0de1fc9c4b389101ea071b59f245bfe5b15a48be028418724
repro-develop-fix libhsa-runtime64.so.1.21.0  fe3cf243b89560ce89129d7c995e27db594f1cd7a54171ca586d89589e564e04
fixed COMGR  6bf2f6fe6b243c5ac3621cd5745887933d3a41edd944a447c88c21ec77da5d5b
foundation image  sha256:a79c374ab2bc93c0e4358da6c7bda5cb84cf1308e8aff74fc99fc95c511cbd03
LLVM compiler/device-library revision  d6f6cb691863e9258034ab48c1dfce21689d5606

These distinguish the two separately rebuilt HIP DSOs, the fixed ROCr control, and the current foundation controls. The candidate was built at source HEAD 538d0d4 with diff SHA256 2d05884e50868e3883924a4e44808517c6de4bd0a5ebb98d33476f59678dacf4; that diff was subsequently committed verbatim as e736720 (verified against the commit delta). Current PR head 1e6b4da adds only the test SPDX header. The original evidence ZIP is preserved locally with SHA256 dba967fe06d6949c2b4e52ccfebb08abb059f2b8da8549adea7bd4e5a519d7b7; the reproducible source and result tables are included in this comment so access to my local archive is not required.

Add a graph-free D2H-to-WRITE regression for 32/64-bit notifications. Check
poisoned host data on a CPU observer before stream synchronization, without
a GPU WAIT, reset WRITE, result copy, or application GPU kernel. Bound
notification waits and join the observer during exceptional teardown.

Register eager publication separately from batch/captured modes so PAL
retains its supported coverage. Gate batch tests by backend selection
rather than treating single-WAIT capability as batch support. Require an
explicit ROCr selection for ambiguous automatic backend choices.

Replace the Windows-wide layout-test skip with the same backend gate and
retain the classic-executor guard, allowing Windows ROCr segmented graphs.
@zihaomu

zihaomu commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

@AlexXAmd Follow-up in commit 65467398df: the isolated case is now an automated regression, and the test backend scope has been corrected. This commit changes only hip-tests; the CLR publication/synchronization implementation is unchanged.

  • Unit_hipStreamMemOp_D2HWritePublication checks one 544-byte D2H followed by one default WRITE, with 2000 iterations for each width. A CPU observer checks poisoned destination memory before completion synchronization. There is no graph, GPU WAIT, reset WRITE, result H2D, or custom device kernel. Timeout is a failure; the observer is joined during exceptional teardown.
  • Eager publication is registered independently from batch/captured modes. PAL retains the minimal and eager input/output checks when its single-operation capability is present. Unsupported batch, native-capture, captured-batch, and explicit-graph modes are skipped separately.
  • The shared backend-selection gate follows explicit GPU_ENABLE_PAL selection and platform defaults. Windows GPU_ENABLE_PAL=0 now reaches the layout-update regression; the previous OS-wide skip is removed. Classic execution is still excluded from this segmented-layout test. Auto mode =2 requires an explicit ROCr selection for batch tests because HIP exposes no public batch-capability query.

The same new test binary and matching ROCr were used for the baseline/candidate comparison on gfx1201:

Check Result
Minimal case, baseline CLR, two widths 1992/2000 and 1985/2000 stale snapshots; expected Catch2 failure, exit 42
Minimal case, candidate CLR 0/2000 for both; 8 assertions pass
Full publication suite, segmented and classic Each passes 58 assertions in 6 cases
Layout update: default, round-robin, DFS, small queue Each passes 1276 assertions

Post-sync data is correct in both minimal-case libraries; no run timed out. All actual test-process HIP/ROCr mappings and SHA256 identities were recorded. These are correctness runs on shared hardware, with no new performance claim.

Ten CPU selection checks, including the simulated Windows branch, and Windows YAML registration pass. PAL/auto/classic skip-only paths were exercised before HIP initialization; these checks are not Windows/PAL runtime validation. Actual Windows ROCr/PAL results remain pending CI, as do the API-contract and cross-ASIC/cost questions from the earlier root-cause reply.

timeout 90s ./hipStreamMemOpPublication \
  'Unit_hipStreamMemOp_D2HWritePublication*' --reporter compact
timeout 90s ./hipStreamMemOpPublication --reporter compact
GPU_ENABLE_PAL=0 timeout 90s ./hipGraphExecBatchMemOpNodeSetParams \
  'Unit_hipGraphExecBatchMemOpNodeSetParams_LayoutChange*' --reporter compact

@mangupta

Copy link
Copy Markdown
Contributor

🤖 AI-assisted pre-review (for @mangupta)

Re-reviewed the updated head (65467398df1f) — no new findings at or above the posting threshold.

@zihaomu

zihaomu commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

@AlexXAmd I traced the caller/helper ownership to an existing CLR ordering boundary and a specific change that removed it. This follow-up adds evidence; the published code remains 65467398df.

1. The ordering was provided by CLR before calling the relaxed helper

The device-library WRITE helper was introduced as a relaxed SYSTEM-scope store in 6918714d635c. When CLR switched to that helper, cbb8d82bdb2b retained this operation before the WRITE blit:

// Ensure memory ordering preceding the write
dispatchBarrierPacket(kBarrierPacketReleaseHeader);

#2304, commit d552491985dc, replaced it with addSystemScope(), explicitly intending to preserve the same memory-ordering guarantee with less overhead. I checked its parent source: the removed packet is barrier=1, acquire=NONE, release=SYSTEM (0x1103).

2. Restoring that one caller-side operation fixes the isolated counterexample

On the fixed modern baseline 02d6cbc71114, the only functional change in the diagnostic library was replacing the WRITE branch's addSystemScope() with that old release-barrier call. Device-libs, blit kernel, compiler, ROCr, driver, batch, and graph code were unchanged.

Same automated minimal D2H→WRITE regression, 2000 rounds per width Stale CPU snapshots Errors after synchronization Exit
Matched modern baseline 1586 + 1563 = 3149/4000 0 42
Restore the pre-#2304 caller-side release 0/4000 0 0

There were no timeouts. Actual process mappings show identical non-HIP library content hashes, and the same test binary was used. The regression source is already in this PR. The exact diagnostic diff and identities are below so this result does not depend on access to my workspace.

The restored AQL trace is:

copy            0x0d02  acquire=SYSTEM release=AGENT
release barrier 0x1103  acquire=NONE   release=SYSTEM
notify          0x0d02  acquire=SYSTEM release=AGENT

This is a controlled restoration on a modern tree, not a full build/bisect of the 2025 parent, a complete replacement implementation, or new Graph validation of the published candidate.

3. The missing guarantee is before the observable store

The original barrier releases completed producers before the notification kernel starts. The replacement upgrades the notification dispatch's scopes, but its SYSTEM release occurs after that kernel's active phase; the CPU can already have observed the relaxed flag store. The HSA packet definitions and HSA specification §2.9.2/§3.3.8 distinguish this completion release from acquire's guarantee for subsequent device loads. The two sequences therefore do not provide the same pre-notification publication boundary.

The earlier direct-AQL controls and their source honor explicitly requested SYSTEM release and packet-completion observations. Weak ordinary-payload cases are diagnostics, not HSA conformance failures. Those controls support the timing explanation; the caller/helper history and controlled restoration above provide the additional ownership evidence. This does not require a firmware/driver violation or a compiler dropping a requested fence.

4. Batch/capture is a separate path, and cost still needs review

The batch submission introduction did not include the ordinary WRITE's pre-release. #12692 later added dispatch scopes only. Native captured WRITE is represented as a batch node, so that path also needs the publication boundary. I am not attributing all batch/graph behavior to #2304 or proposing a blind whole-PR revert.

This provides a concrete reason to review the correction in CLR: restore the caller-owned ordering that #2304 intended to preserve, and give the independent batch path the corresponding guarantee. Formal HIP wording and per-item batch semantics still deserve owner confirmation. The smallest production implementation, safe fence reuse, all-ASIC cost, and remaining platform CI are still open; the existing packet-layout/synchronization-plan fix remains in the published code.

Exact diagnostic diff, command and runtime identities

The diagnostic source is 02d6cbc71114d691b674a444a948d5d0d705c3a8 plus this diff (local diagnostic commit 7df1b5e37302a2e4db27d1efa499cac62a22413c, not pushed into this PR):

diff --git a/projects/clr/rocclr/device/rocm/rocvirtual.cpp b/projects/clr/rocclr/device/rocm/rocvirtual.cpp
index df24a95a3e..a178d9c9bc 100644
--- a/projects/clr/rocclr/device/rocm/rocvirtual.cpp
+++ b/projects/clr/rocclr/device/rocm/rocvirtual.cpp
@@ -4357,9 +4357,8 @@ void VirtualGPU::submitStreamOperation(amd::StreamOperationCommand& cmd) {
       }
     }
   } else if (type == ROCCLR_COMMAND_STREAM_WRITE_VALUE) {
-    // Even though the blit kernel uses system scope atomic, we still need to add system scope on
-    // the AQLPacket because atomics on kernels can bypass L2 cache on some hardware.
-    addSystemScope();
+    // Diagnostic: restore the caller-owned publication boundary removed by #2304.
+    dispatchBarrierPacket(kBarrierPacketReleaseHeader);
 
     bool result;
     switch (flags) {

The existing regression executable was run unchanged against the two private library prefixes:

timeout 90s ./hipStreamMemOpPublication \
  'Unit_hipStreamMemOp_D2HWritePublication*' --reporter compact -s

Both libraries use the same compiler/build options and dependency paths, with only source/build/install prefixes differing. They are local rebuilds reporting HIP 7.17.0-0000000 / 71700000, with git version stamping disabled; they are not release-package identities. Target: R9700/gfx1201, PCI 0000:43:00.0, UUID GPU-541f8c51daca3a7e. GPU_STREAMOPS_CP_WAIT=0.

SHA256 identities:

Artifact SHA256
Matched base HIP DSO 5c0f0937898b09edb18e7dd04c090b81de8f76e87e73a35cea78c5c3dd983ddb
Restored-call HIP DSO 71d8a7d9612142fadce9459383ccb7d6d7a168771052f538d5e40893e99ad7fe
Fixed ROCr DSO fe3cf243b89560ce89129d7c995e27db594f1cd7a54171ca586d89589e564e04
Fixed COMGR DSO 6bf2f6fe6b243c5ac3621cd5745887933d3a41edd944a447c88c21ec77da5d5b
Unchanged regression binary 16ac7bd687544725ab3def12dffb4ffcc6f5a2db0a988dace2cb7c1a7b8259fb
Diagnostic source diff 22c64a32ce91c2528957fb8fb43c18bacc037a20e9812562bc5d267a7ef30f1a

Locked foundation image: sha256:a79c374ab2bc93c0e4358da6c7bda5cb84cf1308e8aff74fc99fc95c511cbd03; compiler/device-libs source d6f6cb691863e9258034ab48c1dfce21689d5606 (HIP 7.16.26385 compilation SDK). No kernel, driver, or firmware replacement was made.

The baseline's two cases each failed the stale-snapshot assertion (exit 42); the restoration passed all 8 assertions in 2 cases (exit 0). Both completed every iteration, with zero post-sync errors and no timeout.

// HIP has no public batch capability query. Follow explicit backend selection
// and platform defaults; pin auto-selection to GPU_ENABLE_PAL=0 to run these
// ROCr-only tests rather than assuming that an automatic fallback supports them.
inline bool IsRocrMemOpBackendSelected() {

@AlexXAmd AlexXAmd Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For API design, if HIP wants to expose certain feature to application, add an info/property into HIP API so the HIP application can query about the feature. HIP application or HIP test should not decide whether a certain feature is supported by a specific implementation.

unsigned badAfterSync = 0;
for (unsigned epoch = 1; epoch <= iterations && !observer.timedOut.load(); ++epoch) {
for (unsigned i = 0; i < kCopyWords; ++i) input[i] = CopyPattern(epoch, i);
HIP_CHECK(hipMemcpyAsync(deviceInput.ptr(), input.data(), sizeof(input), hipMemcpyHostToDevice,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my offline communication. This is not guaranteed behavior by HIP API document.

} else {
HIP_CHECK(hipStreamWriteValue64(stream, ready, epoch, hipStreamWriteValueDefault));
}
while (observer.checked.load(std::memory_order_acquire) != epoch && !observer.timedOut.load()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my offline communication. This is not guaranteed behavior by HIP API document.

@zihaomu

zihaomu commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

@AlexXAmd Following your suggested kernel-based test, I completed the CUDA comparison on an RTX 5090 (the actual tested model), with nvcc 13.0.88, libcudart 13.0.96, driver 615.71.09, and native sm_120 compilation. The driver reports CUDA UMD capability 13.4; that is distinct from the 13.0 compiler/runtime used here.

The sequence is:

same non-default stream: writeDataShader(mapped payload) -> default stream WRITE(ready)
CPU observer: acquire ready -> snapshot and validate every payload word
main thread: wait for CPU validation -> stream synchronize -> separate post-sync check

The producer has 256 blocks × 256 threads. Payload and notification use separate mapped pinned allocations and their device aliases. There is no memcpy, application-side GPU fence, GPU WAIT, or reset WRITE. Payload is poisoned each round; ready uses increasing epochs. All six processes exited 0, completed every iteration, and had no skips or timeouts:

Payload Rounds per width WRITE32 stale snapshots WRITE64 stale snapshots Post-sync errors
4 KiB 250 0/250 0/250 0
1 MiB 2000 0/2000 0/2000 0
8 MiB 250 0/250 0/250 0

That is 5000 completed CUDA rounds with zero stale snapshots. For the corresponding 1 MiB mapped Eager kernel cases, both widths total 4000 rounds:

Implementation Stale snapshots Post-sync errors
RTX 5090 / CUDA 0/4000 0
gfx1201 / matched HIP baseline 02d6cbc71114 4000/4000 0
gfx1201 / matched rebuild of this PR's barrier implementation 0/4000 0
gfx1201 / local device-release alternative 0/4000 0

These are separate platform runs using each platform's mapped-memory API, not one common memory pool or a newer-develop/full-stack bisect. The HIP libraries are local rebuilds reporting HIP 7.17.0-0000000 / 71700000, not release-package identities. The CUDA test is Eager only; it does not validate CUDA Graph or D2H publication.

This supplies the requested CUDA behavior comparison and supports discussing publication as a compatibility target. It does not itself settle the HIP API contract or prove a firmware/driver violation. Could we confirm whether the intended default WRITE contract is:

Prior writes ordered before this operation in the stream are released at system scope before the notification value can be observed. The observer uses an appropriate acquire operation, and the allocations must satisfy their access/coherence requirements.

This is proposed wording for owner review. If this should be treated as a new guarantee, I can proceed on that basis with the corresponding documentation and tests.

I also prepared a local one-runtime-file alternative: four release fences in CLR's WRITE blit variants and batch WRITE32/64 branches, before the relaxed stores. It retains existing AQL scopes/dependencies and adds no packets, so the Graph executor stays at baseline. Its exact diff is below. This alternative has not replaced the public PR head (65467398df). Earlier local costs favored the device fence for single writes, but the 128-write graph batch was about 27% slower than the entry-barrier variant; those shared-hardware measurements are not cross-ASIC performance qualification. Shared PAL impact, cross-dispatch proof, batch cost, and public capability discovery remain separate review items; the test code below is an explicit contract investigation, not a substitute for a capability query.

The folded sections include complete CUDA/HIP test source, exact commands, the minimal alternative diff, all six raw CUDA logs, and the original CUDA manifest. Source/binary hashes were independently recomputed against the supplied archive. CUDA DSO hashes are the test host's recorded values, consistent across cases; the DSO files themselves were not included in that archive.

Reproduce: full CUDA source and commands

The source below is the exact tested CUDA file. On a different GPU, adapt the target-name guard and compiler architecture while retaining the protocol. Link stub directories in the recorded command are link-only; actual runtime mappings below use the real driver.

# Recorded CUDA target; select the corresponding UUID when reproducing elsewhere.
export CUDA_VISIBLE_DEVICES=GPU-17390cc0-bf3c-53e3-170c-21eb949de43b
export CUDA_LAUNCH_BLOCKING=0

# Exact recorded CUDA compilation:
/usr/local/cuda-13.0/bin/nvcc -std=c++17 -O2 -lineinfo -arch=sm_120 --cudart shared -Xcompiler -pthread /work/cuda_memop_publication.cu -o /work/results-20261027T050314.144422Z/cuda_memop_publication -L /usr/local/cuda-13.0/lib64/stubs -L /usr/local/cuda-13.0/targets/x86_64-linux/lib/stubs -lcuda

# Matching invocations (the original absolute output paths are in the manifest):
timeout 180s ./cuda_memop_publication 32 250 4096
timeout 180s ./cuda_memop_publication 32 2000 1048576
timeout 180s ./cuda_memop_publication 32 250 8388608
timeout 180s ./cuda_memop_publication 64 250 4096
timeout 180s ./cuda_memop_publication 64 2000 1048576
timeout 180s ./cuda_memop_publication 64 250 8388608

# HIP standalone build and matching Eager calls on the recorded gfx1201 target:
export HIP_VISIBLE_DEVICES=GPU-541f8c51daca3a7e
export GPU_STREAMOPS_CP_WAIT=0
hipcc -x hip -O2 -std=c++17 -pthread --offload-arch=gfx1201 -DHIP_MEMOP_PUBLICATION_STANDALONE=1 hipStreamMemOpKernelPublication.cc -o hip-kernel-publication
timeout 90s ./hip-kernel-publication 32 2000 1048576 eager mapped
timeout 90s ./hip-kernel-publication 64 2000 1048576 eager mapped
// SPDX-License-Identifier: MIT
// Kernel -> default stream WRITE -> CPU snapshot, without an application fence.
#include <cuda.h>
#include <cuda_runtime.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <set>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

#if CUDA_VERSION < 12000
#error "Use CUDA Toolkit 12 or newer for this handoff."
#endif

static void runtime_check(cudaError_t rc, const char* call) {
  if (rc == cudaSuccess) return;
  std::fprintf(stderr, "CUDA_RUNTIME_ERROR call=%s code=%d text=%s\n",
               call, int(rc), cudaGetErrorString(rc));
  std::fflush(nullptr);
  std::_Exit(2);
}
static void driver_check(CUresult rc, const char* call, bool allow_skip = false) {
  if (rc == CUDA_SUCCESS) return;
  const char *name = nullptr, *text = nullptr;
  cuGetErrorName(rc, &name);
  cuGetErrorString(rc, &text);
  const int exit_code = allow_skip && rc == CUDA_ERROR_NOT_SUPPORTED ? 77 : 2;
  std::fprintf(stderr, "%s call=%s code=%d name=%s text=%s\n",
               exit_code == 77 ? "SKIP" : "CUDA_DRIVER_ERROR", call, int(rc),
               name ? name : "unknown", text ? text : "unknown");
  std::fflush(nullptr);
  std::_Exit(exit_code);
}
#define RT(call) runtime_check((call), #call)
#define DR(call) driver_check((call), #call)

static __host__ __device__ uint32_t pattern(uint32_t epoch, size_t i) {
  return 0x80000001u ^ (epoch * 107u) ^ (uint32_t(i) * 13007u);
}
__global__ void writeDataShader(uint32_t* output, size_t words, uint32_t epoch) {
  for (size_t i = size_t(blockIdx.x) * blockDim.x + threadIdx.x; i < words;
       i += size_t(gridDim.x) * blockDim.x)
    output[i] = pattern(epoch, i);
}

static void dump_dsos() {
  std::ifstream maps("/proc/self/maps");
  std::set<std::string> paths;
  for (std::string line; std::getline(maps, line);) {
    const auto pos = line.find('/');
    if (pos != std::string::npos && line.find(".so", pos) != std::string::npos)
      paths.insert(line.substr(pos));
  }
  for (const auto& path : paths) std::printf("DSO %s\n", path.c_str());
  std::fflush(stdout);
}

template <class T>
static int run(unsigned iterations, size_t bytes) {
  using Clock = std::chrono::steady_clock;
  const size_t words = bytes / sizeof(uint32_t);
  uint32_t *host = nullptr, *device = nullptr;
  T *ready_host = nullptr, *ready_device = nullptr;
  RT(cudaHostAlloc(reinterpret_cast<void**>(&host), bytes, cudaHostAllocMapped));
  RT(cudaHostGetDevicePointer(reinterpret_cast<void**>(&device), host, 0));
  // Separate allocation keeps notification and payload out of the same cache line.
  RT(cudaHostAlloc(reinterpret_cast<void**>(&ready_host), 4096, cudaHostAllocMapped));
  RT(cudaHostGetDevicePointer(reinterpret_cast<void**>(&ready_device), ready_host, 0));
  if (reinterpret_cast<uintptr_t>(ready_host) % alignof(T)) {
    std::fprintf(stderr, "SETUP_ERROR unaligned notification\n");
    return 2;
  }
  __atomic_store_n(ready_host, T{0}, __ATOMIC_RELEASE);
  cudaStream_t stream = nullptr;
  RT(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking));
  dump_dsos();
  std::vector<uint32_t> snapshot(words);
  std::atomic<unsigned> checked{0};
  std::atomic<bool> started{false}, stop{false}, timed_out{false};
  unsigned bad = 0, bad_after_sync = 0;
  uint64_t bad_words = 0;
  std::thread observer([&] {
    started.store(true, std::memory_order_release);
    for (unsigned epoch = 1; epoch <= iterations; ++epoch) {
      const auto deadline = Clock::now() + std::chrono::seconds(10);
      while (__atomic_load_n(ready_host, __ATOMIC_ACQUIRE) != T(epoch)) {
        if (stop.load(std::memory_order_relaxed)) return;
        if (Clock::now() > deadline) { timed_out.store(true); return; }
      }
      std::memcpy(snapshot.data(), host, bytes);
      size_t wrong = 0, first = 0;
      for (size_t i = 0; i < words; ++i) {
        if (snapshot[i] != pattern(epoch, i)) {
          if (wrong == 0) first = i;
          ++wrong;
        }
      }
      if (wrong) {
        ++bad;
        bad_words += wrong;
        if (bad <= 5)
          std::printf("MISMATCH epoch=%u wrong_words=%zu first_index=%zu "
                      "actual=%08x expected=%08x\n", epoch, wrong, first,
                      snapshot[first], pattern(epoch, first));
      }
      checked.store(epoch, std::memory_order_release);
    }
  });
  while (!started.load(std::memory_order_acquire)) std::this_thread::yield();
  for (unsigned epoch = 1; epoch <= iterations; ++epoch) {
    std::memset(host, 0xa5, bytes);
    writeDataShader<<<256, 256, 0, stream>>>(device, words, epoch);
    RT(cudaGetLastError());
    CUresult rc;
    if constexpr (sizeof(T) == 4)
      rc = cuStreamWriteValue32(reinterpret_cast<CUstream>(stream),
                               reinterpret_cast<CUdeviceptr>(ready_device),
                               cuuint32_t(epoch), 0);
    else
      rc = cuStreamWriteValue64(reinterpret_cast<CUstream>(stream),
                               reinterpret_cast<CUdeviceptr>(ready_device),
                               cuuint64_t(epoch), 0);
    driver_check(rc, "cuStreamWriteValue(default)", true);
    const auto deadline = Clock::now() + std::chrono::seconds(10);
    while (checked.load(std::memory_order_acquire) != epoch && !timed_out.load()) {
      if (Clock::now() > deadline) timed_out.store(true);
      std::this_thread::yield();
    }
    if (timed_out.load()) break;
    // CPU snapshot and validation already finished. This cannot repair that snapshot.
    RT(cudaStreamSynchronize(stream));
    for (size_t i = 0; i < words; ++i) {
      if (host[i] != pattern(epoch, i)) { ++bad_after_sync; break; }
    }
  }
  stop.store(true);
  observer.join();
  const unsigned completed = checked.load();
  const int timeout = timed_out.load() || completed != iterations;
  std::printf("RESULT {\"width\":%zu,\"bytes\":%zu,\"iterations\":%u,"
              "\"checked\":%u,\"bad_before_sync\":%u,\"bad_words\":%llu,"
              "\"bad_after_sync\":%u,\"timeout\":%d}\n",
              sizeof(T) * 8, bytes, iterations, completed, bad,
              static_cast<unsigned long long>(bad_words), bad_after_sync, timeout);
  dump_dsos();
  if (timeout) { std::fflush(nullptr); std::_Exit(124); }
  RT(cudaStreamDestroy(stream));
  RT(cudaFreeHost(ready_host));
  RT(cudaFreeHost(host));
  return bad || bad_after_sync ? 1 : 0;
}

int main(int argc, char** argv) {
  std::setvbuf(stdout, nullptr, _IOLBF, 0);
  try {
    if (argc != 4) throw std::runtime_error("usage: binary WIDTH ITERATIONS BYTES");
    const unsigned long width = std::stoul(argv[1]);
    const unsigned long rounds = std::stoul(argv[2]);
    const size_t bytes = std::stoull(argv[3]);
    if ((width != 32 && width != 64) || !rounds || rounds > 1000000 ||
        bytes < 4 || bytes % 4 || bytes > (size_t{1} << 30))
      throw std::runtime_error("invalid width, iterations or byte size");
    const char* blocking = std::getenv("CUDA_LAUNCH_BLOCKING");
    if (blocking && *blocking && std::strcmp(blocking, "0"))
      throw std::runtime_error("CUDA_LAUNCH_BLOCKING must be unset or 0");
    DR(cuInit(0));
    int count = 0;
    DR(cuDeviceGetCount(&count));
    if (count != 1) throw std::runtime_error("expose exactly one RTX 5090 with CUDA_VISIBLE_DEVICES");
    RT(cudaSetDeviceFlags(cudaDeviceMapHost));
    RT(cudaSetDevice(0));
    cudaDeviceProp prop{};
    RT(cudaGetDeviceProperties(&prop, 0));
    if (!std::strstr(prop.name, "5090") || !prop.canMapHostMemory)
      throw std::runtime_error("requires a 5090 with mapped host memory support");
    CUdevice dev;
    DR(cuDeviceGet(&dev, 0));
    int can64 = 0, driver = 0, runtime = 0;
    DR(cuDeviceGetAttribute(&can64, CU_DEVICE_ATTRIBUTE_CAN_USE_64_BIT_STREAM_MEM_OPS, dev));
    DR(cuDriverGetVersion(&driver));
    RT(cudaRuntimeGetVersion(&runtime));
    char pci[32]{};
    RT(cudaDeviceGetPCIBusId(pci, sizeof(pci), 0));
    std::printf("IDENTITY name=%s pci=%s sm=%d%d header=%d runtime=%d driver=%d "
                "can64=%d mapped=%d host_native_atomics=%d flags=0 blocks=256 threads=256\n",
                prop.name, pci, prop.major, prop.minor, CUDA_VERSION, runtime, driver,
                can64, prop.canMapHostMemory, prop.hostNativeAtomicSupported);
    dump_dsos();
    if (width == 64 && !can64) {
      std::fprintf(stderr, "SKIP 64-bit stream memops not supported\n");
      return 77;
    }
    return width == 32 ? run<uint32_t>(unsigned(rounds), bytes)
                       : run<uint64_t>(unsigned(rounds), bytes);
  } catch (const std::exception& error) {
    std::fprintf(stderr, "SETUP_ERROR %s\n", error.what());
    return 2;
  }
}
Full HIP kernel counterpart and runtime alternative (local, not pushed)

HIP source: local candidate 800ace13365ae2431a39789091375427ed16529f. Its standalone main guards the recorded PCI 0000:43:00.0; adjust that hardware guard when reproducing elsewhere. All numbered comparison rows above use eager mapped. The extra batch/Graph modes are controlled qualification paths, not a safe public capability probe on an unknown backend.

// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Opt-in contract investigation. API-owner confirmation of publication semantics
// and public batch/capture capability discovery precede normal CI registration.
// No OS or backend environment variable is interpreted as a feature query.
#include <hip/hip_runtime.h>

#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

#ifndef HIP_MEMOP_PUBLICATION_STANDALONE
#include <hip_test_common.hh>
#endif

namespace {
void Check(hipError_t error, const char* call) {
  if (error != hipSuccess) {
    throw std::runtime_error(std::string(call) + ": " + hipGetErrorString(error));
  }
}
#define MEMOP_CHECK(call) Check((call), #call)

enum class Submission { Eager, Batch, Capture, CaptureBatch, ExplicitGraph };

__host__ __device__ uint32_t Pattern(unsigned epoch, size_t i) {
  return 0x80000001u ^ (epoch * 107u) ^ (static_cast<uint32_t>(i) * 13007u);
}

__global__ void WriteDataShader(uint32_t* output, size_t words, unsigned epoch) {
  for (size_t i = size_t{blockIdx.x} * blockDim.x + threadIdx.x; i < words;
       i += size_t{gridDim.x} * blockDim.x) {
    output[i] = Pattern(epoch, i);
  }
}

struct Buffer {
  void* host = nullptr;
  void* device = nullptr;
  bool signal;
  Buffer(size_t bytes, bool signalMemory) : signal(signalMemory) {
    if (signal) {
      MEMOP_CHECK(hipExtMallocWithFlags(&host, bytes, hipMallocSignalMemory));
      device = host;
    } else {
      MEMOP_CHECK(hipHostMalloc(&host, bytes, hipHostMallocMapped | hipHostMallocCoherent));
      const auto error = hipHostGetDevicePointer(&device, host, 0);
      if (error != hipSuccess) {
        static_cast<void>(hipHostFree(host));
        host = nullptr;
        Check(error, "hipHostGetDevicePointer");
      }
    }
  }
  ~Buffer() {
    if (host) static_cast<void>(signal ? hipFree(host) : hipHostFree(host));
  }
  Buffer(const Buffer&) = delete;
  Buffer& operator=(const Buffer&) = delete;
};

struct Stream {
  hipStream_t handle = nullptr;
  Stream() { MEMOP_CHECK(hipStreamCreateWithFlags(&handle, hipStreamNonBlocking)); }
  ~Stream() { static_cast<void>(hipStreamDestroy(handle)); }
};

struct Graph {
  hipGraph_t graph = nullptr;
  hipGraphExec_t exec = nullptr;
  ~Graph() {
    if (exec) static_cast<void>(hipGraphExecDestroy(exec));
    if (graph) static_cast<void>(hipGraphDestroy(graph));
  }
};

template <typename T> struct Observer {
  std::atomic<unsigned> checked{0};
  std::atomic<bool> started{false}, stop{false}, timedOut{false};
  unsigned bad = 0;
  uint64_t badWords = 0;
  std::vector<uint32_t> snapshot;
  std::thread worker;
  Observer(T* ready, const uint32_t* host, size_t words, unsigned iterations)
      : snapshot(words), worker([this, ready, host, words, iterations] {
          started.store(true, std::memory_order_release);
          for (unsigned epoch = 1; epoch <= iterations; ++epoch) {
            const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
            while (__atomic_load_n(ready, __ATOMIC_ACQUIRE) != static_cast<T>(epoch)) {
              if (stop.load(std::memory_order_relaxed)) return;
              if (std::chrono::steady_clock::now() > deadline) {
                timedOut = true;
                return;
              }
            }
            std::memcpy(snapshot.data(), host, words * sizeof(uint32_t));
            size_t wrong = 0;
            for (size_t i = 0; i < words; ++i) wrong += snapshot[i] != Pattern(epoch, i);
            if (wrong) {
              ++bad;
              badWords += wrong;
              if (bad <= 5) std::printf("MISMATCH epoch=%u wrong_words=%zu\n", epoch, wrong);
            }
            checked.store(epoch, std::memory_order_release);
          }
        }) {}
  void Join() {
    stop = true;
    if (worker.joinable()) worker.join();
  }
  ~Observer() { Join(); }
};

struct Result {
  unsigned checked, badBeforeSync, badAfterSync;
  uint64_t badWords;
  bool timeout;
};

template <typename T>
Result Run(Submission submission, unsigned iterations, size_t bytes, bool signalReady) {
  const size_t words = bytes / sizeof(uint32_t);
  Buffer payload(bytes, false), readyMemory(signalReady ? sizeof(uint64_t) : 4096, signalReady);
  auto* host = static_cast<uint32_t*>(payload.host);
  auto* device = static_cast<uint32_t*>(payload.device);
  auto* ready = static_cast<T*>(readyMemory.host);
  __atomic_store_n(ready, T{0}, __ATOMIC_RELEASE);
  Stream stream;
  Graph graph;
  unsigned epoch = 1;
  size_t kernelWords = words;
  void* kernelArgs[] = {&device, &kernelWords, &epoch};
  hipKernelNodeParams kernelParams{};
  kernelParams.func = reinterpret_cast<void*>(WriteDataShader);
  kernelParams.gridDim = dim3(256);
  kernelParams.blockDim = dim3(256);
  kernelParams.kernelParams = kernelArgs;
  hipStreamBatchMemOpParams write{};
  write.operation = sizeof(T) == 4 ? hipStreamMemOpWriteValue32 : hipStreamMemOpWriteValue64;
  write.writeValue.address = readyMemory.device;
  write.writeValue.value64 = 1;
  hipBatchMemOpNodeParams batchParams{};
  MEMOP_CHECK(hipCtxGetCurrent(&batchParams.ctx));
  batchParams.count = 1;
  batchParams.paramArray = &write;
  hipGraphNode_t producer = nullptr, notification = nullptr;

  const auto enqueueWrite = [&] {
    if constexpr (sizeof(T) == 4) {
      MEMOP_CHECK(hipStreamWriteValue32(stream.handle, readyMemory.device, epoch,
                                        hipStreamWriteValueDefault));
    } else {
      MEMOP_CHECK(hipStreamWriteValue64(stream.handle, readyMemory.device, epoch,
                                        hipStreamWriteValueDefault));
    }
  };
  if (submission == Submission::Capture || submission == Submission::CaptureBatch) {
    MEMOP_CHECK(hipStreamBeginCapture(stream.handle, hipStreamCaptureModeThreadLocal));
    hipLaunchKernelGGL(WriteDataShader, dim3(256), dim3(256), 0, stream.handle, device, words,
                       epoch);
    MEMOP_CHECK(hipGetLastError());
    if (submission == Submission::CaptureBatch) {
      MEMOP_CHECK(hipStreamBatchMemOp(stream.handle, 1, &write, 0));
    } else {
      enqueueWrite();
    }
    MEMOP_CHECK(hipStreamEndCapture(stream.handle, &graph.graph));
    size_t count = 0;
    MEMOP_CHECK(hipGraphGetNodes(graph.graph, nullptr, &count));
    std::vector<hipGraphNode_t> nodes(count);
    MEMOP_CHECK(hipGraphGetNodes(graph.graph, nodes.data(), &count));
    for (const auto node : nodes) {
      hipGraphNodeType type;
      MEMOP_CHECK(hipGraphNodeGetType(node, &type));
      if (type == hipGraphNodeTypeKernel && !producer) producer = node;
      if (type == hipGraphNodeTypeBatchMemOp && !notification) notification = node;
    }
    if (count != 2 || !producer || !notification) {
      throw std::runtime_error("capture did not record one producer and one notification");
    }
  } else if (submission == Submission::ExplicitGraph) {
    MEMOP_CHECK(hipGraphCreate(&graph.graph, 0));
    MEMOP_CHECK(hipGraphAddKernelNode(&producer, graph.graph, nullptr, 0, &kernelParams));
    MEMOP_CHECK(hipGraphAddBatchMemOpNode(&notification, graph.graph, &producer, 1, &batchParams));
  }
  if (graph.graph) MEMOP_CHECK(hipGraphInstantiate(&graph.exec, graph.graph, nullptr, nullptr, 0));

  Observer<T> observer(ready, host, words, iterations);
  while (!observer.started.load(std::memory_order_acquire)) std::this_thread::yield();
  unsigned badAfterSync = 0;
  for (epoch = 1; epoch <= iterations; ++epoch) {
    std::memset(host, 0xa5, bytes);
    write.writeValue.value64 = epoch;
    if (graph.exec) {
      MEMOP_CHECK(hipGraphExecKernelNodeSetParams(graph.exec, producer, &kernelParams));
      MEMOP_CHECK(hipGraphExecBatchMemOpNodeSetParams(graph.exec, notification, &batchParams));
      MEMOP_CHECK(hipGraphLaunch(graph.exec, stream.handle));
    } else {
      hipLaunchKernelGGL(WriteDataShader, dim3(256), dim3(256), 0, stream.handle, device, words,
                         epoch);
      MEMOP_CHECK(hipGetLastError());
      if (submission == Submission::Batch) {
        MEMOP_CHECK(hipStreamBatchMemOp(stream.handle, 1, &write, 0));
      } else {
        enqueueWrite();
      }
    }
    const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
    while (observer.checked.load(std::memory_order_acquire) != epoch && !observer.timedOut.load()) {
      if (std::chrono::steady_clock::now() > deadline) observer.timedOut = true;
      std::this_thread::yield();
    }
    if (observer.timedOut.load()) break;
    MEMOP_CHECK(hipStreamSynchronize(stream.handle));  // Snapshot is already checked.
    for (size_t i = 0; i < words; ++i) {
      if (host[i] != Pattern(epoch, i)) {
        ++badAfterSync;
        break;
      }
    }
  }
  observer.Join();
  const auto checked = observer.checked.load();
  return {checked, observer.bad, badAfterSync, observer.badWords,
          observer.timedOut.load() || checked != iterations};
}
}  // namespace

#ifdef HIP_MEMOP_PUBLICATION_STANDALONE
int main(int argc, char** argv) {
  std::setvbuf(stdout, nullptr, _IOLBF, 0);
  try {
    if (argc != 6) throw std::runtime_error("usage: WIDTH ITERATIONS BYTES MODE mapped|signal");
    const auto width = std::stoul(argv[1]);
    const auto iterations = std::stoul(argv[2]);
    const auto bytes = std::stoull(argv[3]);
    const std::string mode = argv[4], ready = argv[5];
    if ((width != 32 && width != 64) || !iterations || iterations > 1000000 || bytes < 4 ||
        bytes % 4 || bytes > (size_t{1} << 30) || (ready != "mapped" && ready != "signal")) {
      throw std::runtime_error("invalid arguments");
    }
    Submission submission;
    if (mode == "eager")
      submission = Submission::Eager;
    else if (mode == "batch")
      submission = Submission::Batch;
    else if (mode == "capture")
      submission = Submission::Capture;
    else if (mode == "capture-batch")
      submission = Submission::CaptureBatch;
    else if (mode == "graph")
      submission = Submission::ExplicitGraph;
    else
      throw std::runtime_error("unknown submission mode");
    const char* blocking = std::getenv("HIP_LAUNCH_BLOCKING");
    if (blocking && blocking[0] && std::strcmp(blocking, "0")) {
      throw std::runtime_error("HIP_LAUNCH_BLOCKING must be unset or 0");
    }
    int count = 0, version = 0;
    MEMOP_CHECK(hipGetDeviceCount(&count));
    if (count != 1) throw std::runtime_error("requires exactly one visible target GPU");
    MEMOP_CHECK(hipSetDevice(0));
    MEMOP_CHECK(hipRuntimeGetVersion(&version));
    char pci[32]{};
    MEMOP_CHECK(hipDeviceGetPCIBusId(pci, sizeof(pci), 0));
    if (std::strcmp(pci, "0000:43:00.0")) throw std::runtime_error("unexpected target PCI");
    hipDeviceProp_t prop{};
    MEMOP_CHECK(hipGetDeviceProperties(&prop, 0));
    std::printf("IDENTITY hip=%d pci=%s arch=%s width=%lu bytes=%llu mode=%s ready=%s\n", version,
                pci, prop.gcnArchName, width, bytes, mode.c_str(), ready.c_str());
    const auto result = width == 32
                            ? Run<uint32_t>(submission, iterations, bytes, ready == "signal")
                            : Run<uint64_t>(submission, iterations, bytes, ready == "signal");
    std::printf(
        "RESULT {\"width\":%lu,\"bytes\":%llu,\"iterations\":%lu,\"checked\":%u,"
        "\"bad_before_sync\":%u,\"bad_after_sync\":%u,\"bad_words\":%llu,"
        "\"timeout\":%d}\n",
        width, bytes, iterations, result.checked, result.badBeforeSync, result.badAfterSync,
        static_cast<unsigned long long>(result.badWords), int(result.timeout));
    if (result.timeout) return 124;
    return result.badBeforeSync || result.badAfterSync ? 1 : 0;
  } catch (const std::exception& error) {
    std::fprintf(stderr, "ERROR %s\n", error.what());
    return 2;
  }
}
#else
// Hidden until the default publication contract is agreed. A matching explicit
// filter opts in; this does not claim a generic batch feature-discovery solution.
TEMPLATE_TEST_CASE("Unit_hipStreamMemOp_KernelPublication", "[.memop-publication-contract]",
                   uint32_t, uint64_t) {
  const size_t bytes = GENERATE(size_t{4096}, size_t{1048576}, size_t{8388608});
  const unsigned iterations = bytes == 1048576 ? 2000 : 250;
  const auto result = Run<TestType>(Submission::Eager, iterations, bytes, false);
  INFO("bytes=" << bytes << ", checked=" << result.checked << ", bad=" << result.badBeforeSync);
  CHECK_FALSE(result.timeout);
  CHECK(result.checked == iterations);
  CHECK(result.badBeforeSync == 0);
  CHECK(result.badAfterSync == 0);
}

TEMPLATE_TEST_CASE("Unit_hipStreamMemOp_KernelPublication_BatchGraph", "[.memop-batch-contract]",
                   uint32_t, uint64_t) {
  const auto mode = GENERATE(Submission::Batch, Submission::Capture, Submission::CaptureBatch,
                             Submission::ExplicitGraph);
  const auto result = Run<TestType>(mode, 64, 1048576, false);
  INFO("mode=" << static_cast<unsigned>(mode) << ", bad=" << result.badBeforeSync);
  CHECK_FALSE(result.timeout);
  CHECK(result.checked == 64);
  CHECK(result.badBeforeSync == 0);
  CHECK(result.badAfterSync == 0);
}
#endif

Runtime alternative relative to fixed baseline 02d6cbc71114d691b674a444a948d5d0d705c3a8:

diff --git a/projects/clr/rocclr/device/blitcl.cpp b/projects/clr/rocclr/device/blitcl.cpp
index 4db6780484..e2e6fcf545 100644
--- a/projects/clr/rocclr/device/blitcl.cpp
+++ b/projects/clr/rocclr/device/blitcl.cpp
@@ -8,6 +8,9 @@ namespace amd::device {
 
 #define BLIT_KERNELS(...) #__VA_ARGS__
 
+// Default WRITE helpers use a relaxed device-library store. Release prior writes
+// before that store; the dispatch release fence runs only at kernel completion.
+// An empty LLVM syncscope denotes full system scope, as in __threadfence_system.
 const char* BlitLinearSourceCode = BLIT_KERNELS(
     // Extern
     extern void __amd_fillBufferAligned2D(__global uchar*, __global ushort*, __global uint*,
@@ -237,6 +240,7 @@ const char* BlitLinearSourceCode = BLIT_KERNELS(
                                 (ulong)~0UL);
             break;
           case ROCCLR_STREAM_WRITE_VALUE_32:
+            __builtin_amdgcn_fence(__ATOMIC_RELEASE, "");
             __amd_streamOpsWrite((__global uint*)param[i].writeValue.address, NULL,
                                  (uint)param[i].writeValue.value);
             break;
@@ -246,6 +250,7 @@ const char* BlitLinearSourceCode = BLIT_KERNELS(
                                 (uint)param[i].waitValue.flags, (ulong)~0UL);
             break;
           case ROCCLR_STREAM_WRITE_VALUE_64:
+            __builtin_amdgcn_fence(__ATOMIC_RELEASE, "");
             __amd_streamOpsWrite(NULL, (__global ulong*)param[i].writeValue.address,
                                  param[i].writeValue.value64);
             break;
@@ -258,6 +263,7 @@ const char* BlitLinearSourceCode = BLIT_KERNELS(
 const char* HipExtraSourceCode = BLIT_KERNELS(
     __kernel void __amd_rocclr_streamOpsWrite(__global uint* ptrInt, __global ulong* ptrUlong,
                                               ulong value) {
+      __builtin_amdgcn_fence(__ATOMIC_RELEASE, "");
       __amd_streamOpsWrite(ptrInt, ptrUlong, value);
     }
 
@@ -286,6 +292,7 @@ const char* HipExtraSourceCode = BLIT_KERNELS(
 const char* HipExtraSourceCodeNoGWS = BLIT_KERNELS(
     __kernel void __amd_rocclr_streamOpsWrite(__global uint* ptrInt, __global ulong* ptrUlong,
                                               ulong value) {
+      __builtin_amdgcn_fence(__ATOMIC_RELEASE, "");
       __amd_streamOpsWrite(ptrInt, ptrUlong, value);
     }

Matched HIP DSO SHA256: base 5c0f0937898b09edb18e7dd04c090b81de8f76e87e73a35cea78c5c3dd983ddb; barrier aeed04703c86806a7b3b68baf513a6fe2186842424f461c3a0d26d1ddfdaa2bb; device release a742a35c063d9b1b5afa31e3c8ef9598dab177c35c28b39084b26f677630f8ca. HSA is fixed at fe3cf243b89560ce89129d7c995e27db594f1cd7a54171ca586d89589e564e04, COMGR at 6bf2f6fe6b243c5ac3621cd5745887933d3a41edd944a447c88c21ec77da5d5b. The device-release DSO is the prior private build with preprocessor-equivalent runtime source, not a newly built DSO from the test commit.

Raw CUDA environment and all six case logs

nvcc-version.log

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2025 NVIDIA Corporation
Built on Wed_Aug_20_01:58:59_PM_PDT_2025
Cuda compilation tools, release 13.0, V13.0.88
Build cuda_13.0.r13.0/compiler.36424714_0

nvidia-smi-L.log

GPU 0: NVIDIA GeForce RTX 5090 (UUID: GPU-17390cc0-bf3c-53e3-170c-21eb949de43b)

write32-4096bytes.log

IDENTITY name=NVIDIA GeForce RTX 5090 pci=0000:E1:00.0 sm=120 header=13000 runtime=13000 driver=13040 can64=1 mapped=1 host_native_atomics=0 flags=0 blocks=256 threads=256
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
RESULT {"width":32,"bytes":4096,"iterations":250,"checked":250,"bad_before_sync":0,"bad_words":0,"bad_after_sync":0,"timeout":0}
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96

write32-1048576bytes.log

IDENTITY name=NVIDIA GeForce RTX 5090 pci=0000:E1:00.0 sm=120 header=13000 runtime=13000 driver=13040 can64=1 mapped=1 host_native_atomics=0 flags=0 blocks=256 threads=256
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
RESULT {"width":32,"bytes":1048576,"iterations":2000,"checked":2000,"bad_before_sync":0,"bad_words":0,"bad_after_sync":0,"timeout":0}
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96

write32-8388608bytes.log

IDENTITY name=NVIDIA GeForce RTX 5090 pci=0000:E1:00.0 sm=120 header=13000 runtime=13000 driver=13040 can64=1 mapped=1 host_native_atomics=0 flags=0 blocks=256 threads=256
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
RESULT {"width":32,"bytes":8388608,"iterations":250,"checked":250,"bad_before_sync":0,"bad_words":0,"bad_after_sync":0,"timeout":0}
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96

write64-4096bytes.log

IDENTITY name=NVIDIA GeForce RTX 5090 pci=0000:E1:00.0 sm=120 header=13000 runtime=13000 driver=13040 can64=1 mapped=1 host_native_atomics=0 flags=0 blocks=256 threads=256
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
RESULT {"width":64,"bytes":4096,"iterations":250,"checked":250,"bad_before_sync":0,"bad_words":0,"bad_after_sync":0,"timeout":0}
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96

write64-1048576bytes.log

IDENTITY name=NVIDIA GeForce RTX 5090 pci=0000:E1:00.0 sm=120 header=13000 runtime=13000 driver=13040 can64=1 mapped=1 host_native_atomics=0 flags=0 blocks=256 threads=256
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
RESULT {"width":64,"bytes":1048576,"iterations":2000,"checked":2000,"bad_before_sync":0,"bad_words":0,"bad_after_sync":0,"timeout":0}
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96

write64-8388608bytes.log

IDENTITY name=NVIDIA GeForce RTX 5090 pci=0000:E1:00.0 sm=120 header=13000 runtime=13000 driver=13040 can64=1 mapped=1 host_native_atomics=0 flags=0 blocks=256 threads=256
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
RESULT {"width":64,"bytes":8388608,"iterations":250,"checked":250,"bad_before_sync":0,"bad_words":0,"bad_after_sync":0,"timeout":0}
DSO /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
DSO /usr/lib/x86_64-linux-gnu/libc.so.6
DSO /usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09
DSO /usr/lib/x86_64-linux-gnu/libdl.so.2
DSO /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
DSO /usr/lib/x86_64-linux-gnu/libm.so.6
DSO /usr/lib/x86_64-linux-gnu/libpthread.so.0
DSO /usr/lib/x86_64-linux-gnu/librt.so.1
DSO /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33
DSO /usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96
Original CUDA manifest and archive identity

Original supplied archive SHA256: fb2b52fe335b3acc96ef903f53f99b13ca6f31cd5f00be792d1f236ac0048479. Original CUDA source SHA256: 3a90793a23a751ab5c36677f4578ba512f0d734ecd119ceb60191f7b6b090ac4; executable SHA256: 700ef78e4cb772b82d5417824b94adc4e679b178fd7d3e563fcec0d7f37b0b51. The logs/source/manifest are reproduced here; this comment does not upload the executable or driver libraries.

{
  "created_utc": "20261027T050314.144422Z",
  "platform": "Linux-7.0.0-31-generic-x86_64-with-glibc2.39",
  "python": "3.12.3 (main, Aug 14 2025, 17:47:21) [GCC 13.3.0]",
  "source_sha256": "3a90793a23a751ab5c36677f4578ba512f0d734ecd119ceb60191f7b6b090ac4",
  "cases": [
    {
      "width": 32,
      "bytes": 4096,
      "iterations": 250,
      "status": "PASS",
      "exit_code": 0,
      "outer_timeout": false,
      "log": "write32-4096bytes.log",
      "result": {
        "width": 32,
        "bytes": 4096,
        "iterations": 250,
        "checked": 250,
        "bad_before_sync": 0,
        "bad_words": 0,
        "bad_after_sync": 0,
        "timeout": 0
      },
      "loaded_dsos": [
        {
          "path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
          "sha256": "6222a16be7f2d458d6870efe6e715fc0c8d45766fb79cf7dcc3125538d703e28"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
          "sha256": "511f825ee075610ac9c0f7f91e2c13de2000d0f7b859f6461137e809a0a009d0"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09",
          "sha256": "85a8cc8cf00419ceb295ec62c481d05b4cdbdab10f25c5375d6d24bdbe00b4d9"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libdl.so.2",
          "sha256": "feee7549138dc862300b9ba9b2b02502059c838a6c0b92d2ea4c0a357e8e0b45"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1",
          "sha256": "02f3f192bf5f79b811f1e34a650fea443d407408703448906585232383957f60"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libm.so.6",
          "sha256": "f06f2ce1f1833df5f41cf13b6447ff07bea993ad9b27297d3428c2f70ab3f0e7"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libpthread.so.0",
          "sha256": "4472cde19acc87a431ad1eefe9e86f8238dd1f26f8dac692cff7040474d9d085"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/librt.so.1",
          "sha256": "4b3a59575a0812a466235bc745814ca5b13825ef5f8ad5680637872ac64cc7ca"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33",
          "sha256": "a68762c86d371e6041f03f03a33a78fa235809ae7d81c90185940c93c3535aed"
        },
        {
          "path": "/usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96",
          "sha256": "96c42e418cec19054186b9429c321603cc190bf26a18104e19408117a2a817b0"
        }
      ],
      "cuda_dso_identity_valid": true
    },
    {
      "width": 32,
      "bytes": 1048576,
      "iterations": 2000,
      "status": "PASS",
      "exit_code": 0,
      "outer_timeout": false,
      "log": "write32-1048576bytes.log",
      "result": {
        "width": 32,
        "bytes": 1048576,
        "iterations": 2000,
        "checked": 2000,
        "bad_before_sync": 0,
        "bad_words": 0,
        "bad_after_sync": 0,
        "timeout": 0
      },
      "loaded_dsos": [
        {
          "path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
          "sha256": "6222a16be7f2d458d6870efe6e715fc0c8d45766fb79cf7dcc3125538d703e28"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
          "sha256": "511f825ee075610ac9c0f7f91e2c13de2000d0f7b859f6461137e809a0a009d0"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09",
          "sha256": "85a8cc8cf00419ceb295ec62c481d05b4cdbdab10f25c5375d6d24bdbe00b4d9"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libdl.so.2",
          "sha256": "feee7549138dc862300b9ba9b2b02502059c838a6c0b92d2ea4c0a357e8e0b45"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1",
          "sha256": "02f3f192bf5f79b811f1e34a650fea443d407408703448906585232383957f60"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libm.so.6",
          "sha256": "f06f2ce1f1833df5f41cf13b6447ff07bea993ad9b27297d3428c2f70ab3f0e7"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libpthread.so.0",
          "sha256": "4472cde19acc87a431ad1eefe9e86f8238dd1f26f8dac692cff7040474d9d085"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/librt.so.1",
          "sha256": "4b3a59575a0812a466235bc745814ca5b13825ef5f8ad5680637872ac64cc7ca"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33",
          "sha256": "a68762c86d371e6041f03f03a33a78fa235809ae7d81c90185940c93c3535aed"
        },
        {
          "path": "/usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96",
          "sha256": "96c42e418cec19054186b9429c321603cc190bf26a18104e19408117a2a817b0"
        }
      ],
      "cuda_dso_identity_valid": true
    },
    {
      "width": 32,
      "bytes": 8388608,
      "iterations": 250,
      "status": "PASS",
      "exit_code": 0,
      "outer_timeout": false,
      "log": "write32-8388608bytes.log",
      "result": {
        "width": 32,
        "bytes": 8388608,
        "iterations": 250,
        "checked": 250,
        "bad_before_sync": 0,
        "bad_words": 0,
        "bad_after_sync": 0,
        "timeout": 0
      },
      "loaded_dsos": [
        {
          "path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
          "sha256": "6222a16be7f2d458d6870efe6e715fc0c8d45766fb79cf7dcc3125538d703e28"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
          "sha256": "511f825ee075610ac9c0f7f91e2c13de2000d0f7b859f6461137e809a0a009d0"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09",
          "sha256": "85a8cc8cf00419ceb295ec62c481d05b4cdbdab10f25c5375d6d24bdbe00b4d9"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libdl.so.2",
          "sha256": "feee7549138dc862300b9ba9b2b02502059c838a6c0b92d2ea4c0a357e8e0b45"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1",
          "sha256": "02f3f192bf5f79b811f1e34a650fea443d407408703448906585232383957f60"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libm.so.6",
          "sha256": "f06f2ce1f1833df5f41cf13b6447ff07bea993ad9b27297d3428c2f70ab3f0e7"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libpthread.so.0",
          "sha256": "4472cde19acc87a431ad1eefe9e86f8238dd1f26f8dac692cff7040474d9d085"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/librt.so.1",
          "sha256": "4b3a59575a0812a466235bc745814ca5b13825ef5f8ad5680637872ac64cc7ca"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33",
          "sha256": "a68762c86d371e6041f03f03a33a78fa235809ae7d81c90185940c93c3535aed"
        },
        {
          "path": "/usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96",
          "sha256": "96c42e418cec19054186b9429c321603cc190bf26a18104e19408117a2a817b0"
        }
      ],
      "cuda_dso_identity_valid": true
    },
    {
      "width": 64,
      "bytes": 4096,
      "iterations": 250,
      "status": "PASS",
      "exit_code": 0,
      "outer_timeout": false,
      "log": "write64-4096bytes.log",
      "result": {
        "width": 64,
        "bytes": 4096,
        "iterations": 250,
        "checked": 250,
        "bad_before_sync": 0,
        "bad_words": 0,
        "bad_after_sync": 0,
        "timeout": 0
      },
      "loaded_dsos": [
        {
          "path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
          "sha256": "6222a16be7f2d458d6870efe6e715fc0c8d45766fb79cf7dcc3125538d703e28"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
          "sha256": "511f825ee075610ac9c0f7f91e2c13de2000d0f7b859f6461137e809a0a009d0"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09",
          "sha256": "85a8cc8cf00419ceb295ec62c481d05b4cdbdab10f25c5375d6d24bdbe00b4d9"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libdl.so.2",
          "sha256": "feee7549138dc862300b9ba9b2b02502059c838a6c0b92d2ea4c0a357e8e0b45"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1",
          "sha256": "02f3f192bf5f79b811f1e34a650fea443d407408703448906585232383957f60"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libm.so.6",
          "sha256": "f06f2ce1f1833df5f41cf13b6447ff07bea993ad9b27297d3428c2f70ab3f0e7"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libpthread.so.0",
          "sha256": "4472cde19acc87a431ad1eefe9e86f8238dd1f26f8dac692cff7040474d9d085"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/librt.so.1",
          "sha256": "4b3a59575a0812a466235bc745814ca5b13825ef5f8ad5680637872ac64cc7ca"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33",
          "sha256": "a68762c86d371e6041f03f03a33a78fa235809ae7d81c90185940c93c3535aed"
        },
        {
          "path": "/usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96",
          "sha256": "96c42e418cec19054186b9429c321603cc190bf26a18104e19408117a2a817b0"
        }
      ],
      "cuda_dso_identity_valid": true
    },
    {
      "width": 64,
      "bytes": 1048576,
      "iterations": 2000,
      "status": "PASS",
      "exit_code": 0,
      "outer_timeout": false,
      "log": "write64-1048576bytes.log",
      "result": {
        "width": 64,
        "bytes": 1048576,
        "iterations": 2000,
        "checked": 2000,
        "bad_before_sync": 0,
        "bad_words": 0,
        "bad_after_sync": 0,
        "timeout": 0
      },
      "loaded_dsos": [
        {
          "path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
          "sha256": "6222a16be7f2d458d6870efe6e715fc0c8d45766fb79cf7dcc3125538d703e28"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
          "sha256": "511f825ee075610ac9c0f7f91e2c13de2000d0f7b859f6461137e809a0a009d0"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09",
          "sha256": "85a8cc8cf00419ceb295ec62c481d05b4cdbdab10f25c5375d6d24bdbe00b4d9"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libdl.so.2",
          "sha256": "feee7549138dc862300b9ba9b2b02502059c838a6c0b92d2ea4c0a357e8e0b45"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1",
          "sha256": "02f3f192bf5f79b811f1e34a650fea443d407408703448906585232383957f60"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libm.so.6",
          "sha256": "f06f2ce1f1833df5f41cf13b6447ff07bea993ad9b27297d3428c2f70ab3f0e7"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libpthread.so.0",
          "sha256": "4472cde19acc87a431ad1eefe9e86f8238dd1f26f8dac692cff7040474d9d085"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/librt.so.1",
          "sha256": "4b3a59575a0812a466235bc745814ca5b13825ef5f8ad5680637872ac64cc7ca"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33",
          "sha256": "a68762c86d371e6041f03f03a33a78fa235809ae7d81c90185940c93c3535aed"
        },
        {
          "path": "/usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96",
          "sha256": "96c42e418cec19054186b9429c321603cc190bf26a18104e19408117a2a817b0"
        }
      ],
      "cuda_dso_identity_valid": true
    },
    {
      "width": 64,
      "bytes": 8388608,
      "iterations": 250,
      "status": "PASS",
      "exit_code": 0,
      "outer_timeout": false,
      "log": "write64-8388608bytes.log",
      "result": {
        "width": 64,
        "bytes": 8388608,
        "iterations": 250,
        "checked": 250,
        "bad_before_sync": 0,
        "bad_words": 0,
        "bad_after_sync": 0,
        "timeout": 0
      },
      "loaded_dsos": [
        {
          "path": "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
          "sha256": "6222a16be7f2d458d6870efe6e715fc0c8d45766fb79cf7dcc3125538d703e28"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libc.so.6",
          "sha256": "511f825ee075610ac9c0f7f91e2c13de2000d0f7b859f6461137e809a0a009d0"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libcuda.so.615.71.09",
          "sha256": "85a8cc8cf00419ceb295ec62c481d05b4cdbdab10f25c5375d6d24bdbe00b4d9"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libdl.so.2",
          "sha256": "feee7549138dc862300b9ba9b2b02502059c838a6c0b92d2ea4c0a357e8e0b45"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libgcc_s.so.1",
          "sha256": "02f3f192bf5f79b811f1e34a650fea443d407408703448906585232383957f60"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libm.so.6",
          "sha256": "f06f2ce1f1833df5f41cf13b6447ff07bea993ad9b27297d3428c2f70ab3f0e7"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libpthread.so.0",
          "sha256": "4472cde19acc87a431ad1eefe9e86f8238dd1f26f8dac692cff7040474d9d085"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/librt.so.1",
          "sha256": "4b3a59575a0812a466235bc745814ca5b13825ef5f8ad5680637872ac64cc7ca"
        },
        {
          "path": "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33",
          "sha256": "a68762c86d371e6041f03f03a33a78fa235809ae7d81c90185940c93c3535aed"
        },
        {
          "path": "/usr/local/cuda-13.0/targets/x86_64-linux/lib/libcudart.so.13.0.96",
          "sha256": "96c42e418cec19054186b9429c321603cc190bf26a18104e19408117a2a817b0"
        }
      ],
      "cuda_dso_identity_valid": true
    }
  ],
  "commands": [
    {
      "argv": [
        "/usr/local/cuda-13.0/bin/nvcc",
        "--version"
      ],
      "log": "nvcc-version.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "nvidia-smi",
        "-L"
      ],
      "log": "nvidia-smi-L.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "nvidia-smi",
        "-q"
      ],
      "log": "nvidia-smi-q.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/usr/local/cuda-13.0/bin/nvcc",
        "-std=c++17",
        "-O2",
        "-lineinfo",
        "-arch=sm_120",
        "--cudart",
        "shared",
        "-Xcompiler",
        "-pthread",
        "/work/cuda_memop_publication.cu",
        "-o",
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "-L",
        "/usr/local/cuda-13.0/lib64/stubs",
        "-L",
        "/usr/local/cuda-13.0/targets/x86_64-linux/lib/stubs",
        "-lcuda"
      ],
      "log": "build.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "32",
        "250",
        "4096"
      ],
      "log": "write32-4096bytes.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "32",
        "2000",
        "1048576"
      ],
      "log": "write32-1048576bytes.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "32",
        "250",
        "8388608"
      ],
      "log": "write32-8388608bytes.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "64",
        "250",
        "4096"
      ],
      "log": "write64-4096bytes.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "64",
        "2000",
        "1048576"
      ],
      "log": "write64-1048576bytes.log",
      "exit_code": 0,
      "outer_timeout": false
    },
    {
      "argv": [
        "/work/results-20261027T050314.144422Z/cuda_memop_publication",
        "64",
        "250",
        "8388608"
      ],
      "log": "write64-8388608bytes.log",
      "exit_code": 0,
      "outer_timeout": false
    }
  ],
  "environment": {
    "CUDA_VISIBLE_DEVICES": "GPU-17390cc0-bf3c-53e3-170c-21eb949de43b",
    "CUDA_LAUNCH_BLOCKING": "0",
    "LD_LIBRARY_PATH": "/usr/local/lib/python3.12/dist-packages/torch/lib:/usr/local/lib/python3.12/dist-packages/torch_tensorrt/lib:/usr/local/cuda/compat/lib:/usr/local/nvidia/lib:/usr/local/nvidia/lib64",
    "CUDA_MODULE_LOADING": "LAZY",
    "CUDA_FORCE_PTX_JIT": null,
    "CUDA_DEVICE_MAX_CONNECTIONS": null,
    "LD_PRELOAD": null,
    "CUDA_INJECTION64_PATH": null
  },
  "nvcc": "/usr/local/cuda-13.0/bin/nvcc",
  "nvcc_sha256": "b8e2347d9d7fcadf8812a27324d515de99d033d3e48611811ece7e66d7895f8a",
  "binary_sha256": "700ef78e4cb772b82d5417824b94adc4e679b178fd7d3e563fcec0d7f37b0b51",
  "effective_LD_LIBRARY_PATH": "/usr/local/cuda-13.0/lib64:/usr/local/cuda-13.0/targets/x86_64-linux/lib:/usr/local/lib/python3.12/dist-packages/torch/lib:/usr/local/lib/python3.12/dist-packages/torch_tensorrt/lib:/usr/local/cuda/compat/lib:/usr/local/nvidia/lib:/usr/local/nvidia/lib64",
  "status": "PASS"
}

@zihaomu

zihaomu commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

@AlexXAmd Following the RTX 5090/kernel evidence, I completed application qualification of the separate, one-file pre-store release candidate. This is not validation of the currently submitted barrier/Graph implementation, whose head remains 65467398df.

Contract and implementation are still separate review questions. One documentation reference may help: CUDA 13.0's CUstreamWriteValue_flags describes normal WRITE behavior as a pre-write fence with system-fence-like semantics scoped to the stream, and says the no-barrier flag is unsupported in v2. The HIP 10.1 page describes prior-command completion without equivalent publication wording. CUDA's documentation and the 5090 observation do not automatically establish a HIP guarantee.

On the same R9700/gfx1201, matched local CLR builds and identical ROCr/COMGR give these new no-reset-WRITE results:

Check Unmodified rebuilt base Pre-store release candidate
Kernel producer, WRITE32/64, eager/capture/explicit graph 5997/6000 stale snapshots 0/6000
Strict D2H round trip, WRITE64, eager/native capture 733/2000 incorrect CPU inputs and corresponding GPU outputs 0/2000

No timeout or diagnostic unlock occurred. The application then passed eight Graph/Eager logits comparisons (NRMSE=0, matching argmax). Fifteen independent model services, including matched rebuilt controls, completed three rounds at 512/32K inputs; all 30 measured requests produced matching text within each round/context. These tests validate the tested candidate/configuration, not every ASIC or the missing HIP contract.

There is no demonstrated stable model speedup. Release/raw and stock/event both averaged about 84.49 tok/s at 512. Long inter-token pauses occurred in stock/kernel, stock/event and release/raw; all slow rounds are retained and the pauses are not attributed to this patch. Kernel IO also fuses copies/accumulation and skips empty CPU tasks, so removing an event alone does not reproduce that application optimization.

My proposed next code revision is the one-file CLR approach already shown above: four pre-store release sites in the ordinary GWS/NoGWS WRITE helpers and the batch WRITE32/64 cases. It adds no AQL packets, so it would remove the publication-packet-related Graph runtime changes while retaining the dependent WAIT-to-WRITE and WRITE-to-WAIT tests. WAIT-only batches acquire no new release. Backend-name inference would not be retained as public capability discovery; that API/error-handling question should be handled separately.

Could we settle whether HIP should provide this stream-scoped pre-notification publication guarantee, including each default WRITE in a batch, and whether this CLR direction is appropriate? I am happy to proceed as a contract clarification/restoration or an enhancement. The remaining technical work is to establish the full producer-completion/acquire-to-notification chain, including other workgroups and SDMA, then qualify the final revision and cross-ASIC cost. A fence in one work-item is not, by itself, proof of that stream-wide chain. I am not claiming a firmware/driver violation or proposing to merge based only on single-GPU pass counts.

Build identities and all model rounds
  • Foundation: locked ROCm10.1, Torch2.14.0, gfx1201; stock HIP7.16.26385.
  • Private base: 02d6cbc71114d691b674a444a948d5d0d705c3a8; candidate local commit 7af95ffeb0e533607169ee5af1455ed3408c396d, with the same CLR tree as the local refinement previously discussed. Both were rebuilt with KPACK enabled for Torch and report HIP7.17.0-0000000, not a distribution HIP version. This is not a test of a newer upstream revision.
  • Private HIP base SHA256: 1aebec4e7eaef298760ba8ae64d4a867d5214ce71f9d66658e4da05c0aaf09f5.
  • Private HIP candidate SHA256: ce14d25f854ab8fa28804d09efb5dcdf0486eb92dc4acf296b8b8f4a182c5139.
  • Shared private ROCr SHA256: fe3cf243b89560ce89129d7c995e27db594f1cd7a54171ca586d89589e564e04.
  • COMGR SHA256: 6bf2f6fe6b243c5ac3621cd5745887933d3a41edd944a447c88c21ec77da5d5b.
  • Actual process mappings were checked for a single HIP/ROCr, with matching COMGR/KPACK; host and application SDKs were not overwritten.

Qwen3-30B-A3B FP8, BF16 compute/KV, TP1, concurrency1, 20 GiB/4550-slot expert cache, 17 CPU workers, 256 output tokens; warmup is separate. The private event/control and release/raw comparison changes the whole solution (release plus event removal), not just the fence cost.

Configuration 512 tok/s, rounds 1/2/3 32K tok/s, rounds 1/2/3
Stock HIP + kernel IO 88.24 / 94.86 / 91.67 61.78 / 61.18 / 37.81
Stock HIP + event 86.58 / 83.68 / 83.19 47.98 / 58.07 / 58.74
Private release + memcpy/memop, no event 77.91 / 87.82 / 87.74 46.99 / 41.82 / 58.97
Unmodified private HIP + kernel IO 92.66 / 94.95 / 93.44 61.56 / 61.93 / 62.20
Unmodified private HIP + event 86.18 / 86.74 / 84.03 58.35 / 58.28 / 57.56

Maximum observed 32K token gaps in the affected measured runs: stock/kernel 2.053 s, stock/event 0.536 s, release/raw 0.877 s and 1.812 s. The correctness result is not a localization of these pauses.

The exact commands, sources, DSO identities, raw requests and failure/cleanup records are retained in the local evidence bundle, SHA256 6e490f797a00411a3ec873e81ed5182ab69179b067fb2ed02c9cdb92decf065c; the bundle has not been uploaded in this comment.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants