Skip to content

hk: give G17P host-read and aliased-memory flushes memory scope - #20

Open
DjDeveloperr wants to merge 3 commits into
integration/aurorafrom
aurora/g17p-memory-scope-flush
Open

DjDeveloperr wants to merge 3 commits into
integration/aurorafrom
aurora/g17p-memory-scope-flush

Conversation

@DjDeveloperr

Copy link
Copy Markdown
Collaborator

Mirror of aurora-silicon#98 (merged into aurora-wip as deed89d), cherry-picked onto integration/aurora without conflicts. On this branch it builds with -Dwerror=true and passes the asahi unit tests 76/76. The hardware results below were measured on the aurora-wip build of the same two commits.

On G17P (A18 Pro / Neo), a barrier made written data visible only to later reads through the same GPU virtual address. This PR gives the barrier's flush memory scope where a second address or the host can read the data. Both failures were found by the new gpubench stress suite (aurora-silicon/gpubench#4).

What broke

  • sparse.churn: a command buffer fills pages through a sparse buffer, records a transfer barrier, then copies the same pool pages through a plain buffer. The plain copy read stale memory: 494 of 526 pages, in every run. The sparse copy read the right data.
  • queue.depth: 32 compute and 32 render submissions ran on one queue. Compute results the host read back after the timeline signalled were stale 1–3 times in each 4-second run. A re-read 0.1–1.5 ms later found the right values. With the render ring on a second queue, three runs had no stale results in ~130,000 completions each.

Root cause

The G17P GPU caches are tagged by virtual address. The strong CDM barrier hk records for a dependency (flags 5, word 0x60001560) makes writes visible only through the same mapping. It does not write the data back to memory.

  • A read of the same memory through another mapping in the same command returns stale memory. That mapping can be a sparse binding next to a plain buffer, or a sparse buffer's read-only shadow.
  • The host also reads memory. hk records that same strong flush for HOST_READ availability, so the data stays in the GPU caches until the end of the command writes it back. That usually happens before the kernel reports completion, but not when render work shares the queue.

This is not the kernel's 50 ms stamp fallback. Tracepoints on four stale runs showed each stale compute submission retired from its normal completion stamp 35–240 µs before the host read it. The GPU had finished it 180–700 µs before the read.

Evidence

A diagnostic override of hk's strong-flush word was tested on the Neo with sparse.churn, 20 iterations, 526 pages:

Barrier word Stale pages
0x60001560 (flags 5, today) 494
0x60001570 (+ bit 4) 0
0x6000157c (+ bits 4:2, M5's memory-scope record) 0
0x600215fe (the G15/G16G wide-visibility word) 0
0x60001564 / 0x60001568 (bit 2 / bit 3 alone) 494 / 495
  • Bit 4 also fixed the reverse direction (write through the plain buffer, read through the sparse one) and shader stores. It also fixed reads of pages the GPU had already cached through the second mapping.
  • With the memory-scope word, queue.depth at 32 compute + 32 render on one queue went from failing 3 of 3 runs to passing 3 of 3, at ~135,000 completions per run.
  • Splitting the command stream into two kernel commands in the same submit did not help. Separate submits with a host wait between them did.
  • M5 already sets the same bits (flush_memory, HAL300) on every strong flush, and the existing packing test notes that ordering flags alone do not make writes visible through another mapping.

Fix

  • asahi: the CDM barrier codec accepts the memory-scope record (AGX3_CDM_BARRIER_FLUSH_MEMORY, bits 4:2) on G17P as well. Each bit is documented. G17P keeps the three-bit form that the HAL300 and G15 words share.
  • hk: hk_cdm_visibility_flush() records the strong flush with memory scope on request. A trailing strong record without it is strengthened in place, the same way a weak record already is. Inline stream concatenation keeps the scope of the record it folds into.
  • Host-read availability always asks for memory scope on G17P.
  • Compute and transfer dependencies ask for it only while the device holds a resource with a GPU address of its own: a sparse buffer or image, or a capture/replay buffer. This covers pipeline barriers, the deferred-clear barrier, the transfer-before-graphics ordering and vkCmdSetEvent2. Devices that never create such a resource record exactly the barriers they did before.
  • Other chips are unchanged.

The gate reads device state when a barrier is recorded. Any resource the command buffer references exists by then. It is counted from creation, so a later vkQueueBindSparse that remaps it is covered. The memory-scope flush writes back every dirty line, so writes recorded in an earlier command buffer are covered too.

Not covered: an aliasing resource created after the command buffer was recorded and reached only through a device address the command buffer reads indirectly. This is legal Vulkan but unusual. Closing it would mean gating on the enabled features (sparseBinding, bufferDeviceAddressCaptureReplay) instead. DXVK and vkd3d-proton enable those whenever they are offered, so that gate would put the flush cost on every Proton game. I chose not to do that.

Qualification

Neo (A18 Pro), stock kernel kit (integration/aurora + #563), this branch built with -Dwerror=true:

  • Host: the asahi unit tests pass 75/75, including the updated agx3 barrier-packing test.
  • gpubench-stress, unmodified PR honeykrisp: partial-subgroup coopmat, narrow-pointer addressing, cache key and sin fixes (stacked on #3) #4 binary: sparse.churn passed 6/6 runs at about 1.18M checks each. Baseline fails at iteration 0.
  • queue.depth with this branch:
    • 7 runs at 64–128 compute + 64 render on one queue, 90k–156k checks each, had no stale result. The baseline has stale results within 0.2 s.
    • These deep runs only finish with the kernel's work-state limit in place. Without it, depth 128 + 64 still loses the device to a separate kernel stall, fixed in a separate aurora-silicon/linux PR.
    • The 3 clean runs at 32 + 32 in the evidence above used the diagnostic flush word on every strong flush, not this branch.
  • CTS: run with /var/tmp/cts/run.py, 6 workers.
Group Pass Fail
sparse_resources 9903 0
memory_model 3796 0
query_pool 18160 0
synchronization (rerun) 28647 0
synchronization2 36516 0
api.copy_and_blit 101867 0
memory 4945 0
compute 10743 0
ssbo 12162 0

The first synchronization pass had two rc=-9 crashes in signal_order.timeline_semaphore.write_image_*, after a GPU timeout under 6-worker load. Both pass 3/3 in isolation on this branch and on the baseline. The baseline shows the same crash class in its own full pass, and a full rerun on this branch was clean.

  • Performance:
    • For applications without host-read barriers and without alias resources, the recorded command streams are byte-identical to the baseline. gpubench records neither, and 20 light and heavy rows over 3 alternated rounds stayed within run-to-run spread.
    • Each host-read barrier gets one memory-scope flush.
    • With a live sparse buffer on the device, 64 dispatch + barrier pairs per command buffer cost +20% when each dispatch writes 256 KB, and nothing measurable at small writes.

The strong CDM barrier (flags 5) makes compute and transfer writes visible
only to later reads through the same virtual address, because the G17P
caches are tagged by virtual address. A read of the same memory through a
second mapping in the same command returns stale memory. That second
mapping can be a sparse binding next to a plain buffer over the same pages,
or a sparse buffer's read-only shadow.

Bit 4 of the barrier word adds memory scope. On a Neo (A18 Pro), one
command buffer fills pages through a sparse buffer, records a transfer
barrier, then copies the same pool pages through a plain buffer. Pages read
back stale, by barrier word:
- 0x60001560 (flags 5): 494 of 526
- 0x60001570 (bit 4): 0 of 526
- 0x6000157c (bits 4:2, the HAL300 memory record): 0 of 526
- 0x600215fe (the G15 wide-visibility word): 0 of 526
- 0x60001564 or 0x60001568 (bit 2 or bit 3 alone): 494 and 495 of 526

Bit 4 also fixes the reverse direction, shader stores, and reads of pages
the GPU had already cached through the second mapping.

Accept the HAL300 record (bits 4:2) on G17P as well, and name the bits.
On G17P a strong CDM flush leaves written data in the GPU caches, which are
tagged by virtual address. Two cases depended on more than that.

Host availability: a barrier to HOST_READ records hk's strong
cache-maintenance flush, which never reached memory on G17P. The end of the
command usually wrote the data back before completion was reported, but
not when render work shared the queue.

gpubench-stress queue.depth runs 32 compute and 32 render submissions on
one queue. Compute results were read back stale 1-3 times per 4-second run,
in every run. Tracepoints showed each stale submission had finished on the
GPU 180-700 us before the host read it, and the kernel had retired it from
its completion stamp 35-240 us before the read. The data reached memory
0.1-1.5 ms later. With the render ring on a second queue, or with a
memory-scope host flush, three runs each saw no stale result in ~135000
completions.

Aliased memory: sparse buffers and images, and capture/replay buffers, map
their memory at an address of their own as well. In sparse.churn, a copy
through a plain buffer over the pool read stale data for 494 of 526 pages
written through the sparse buffer. That happens after a barrier in the same
command buffer, whichever direction or kind of write is involved. Splitting
the stream into two kernel commands of the same submission did not help.

So:
- hk_cdm_visibility_flush() records the strong flush with memory scope
  (AGX3_CDM_BARRIER_FLUSH_MEMORY) on request. It strengthens a trailing
  strong record in place, as a weak one already is. Inline stream
  concatenation keeps the scope of the record it folds into.
- Host-read availability always asks for memory scope on G17P.
- Compute and transfer dependencies ask for it only while the device holds
  a resource with its own GPU address (hk_cdm_alias_scope()). This covers
  pipeline barriers, the deferred-clear barrier, the transfer-before-graphics
  ordering, and vkCmdSetEvent2. Devices that never create such a resource
  record the same barriers as before.

M5 already gives every strong flush memory scope. Other chips are unchanged.

In a microbenchmark of 1 and 64 dispatch-plus-barrier pairs per command
buffer, the memory-scope form cost nothing measurable: 106-109 us and
167-172 us per command buffer either way.
@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; the added tests address the previous coverage gap.

Summary

The PR gives G17P barriers memory scope for host reads and reads through another GPU address.

  • G17P barriers make writes visible to the host and other GPU addresses.

Reviews (2) · Last reviewed commit: "hk: test memory scope across CDM stream ..." · Reviewed by Greptile

Comment thread src/asahi/vulkan/hk_cmd_buffer.h
hk_cs_merge_cdm() folds the next stream's opening strong flush into a
trailing barrier and must keep the memory scope that record carried,
and keep memory_flush_end exact for later merges and cache flushes. The
merge test only checked metadata and links, so dropping either would
have passed it and brought back stale reads through another mapping.

Cover a memory-scoped tail folded with a plain opening barrier, a
barrier-only second stream, repeated merges, a memory-scoped opener
folded onto a plain tail and an unfolded copy, checking each barrier
word and memory_flush_end. With more than one call site the merge stays
out of line, so the test stubs hk_drm_attachment_set_add() like the
link emitter. The test fails when the fold drops the memory bit, and
when a barrier-only fold loses the marker.

asahi meson suite: 107 tests pass.

(cherry picked from commit c22e446)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant