Skip to content

fix(shader): widen LDS atomic fences to device scope on AMD's proprietary driver - #8489

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
wachi35676:fix/shader-amd-lds-fence-scope
Oct 23, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
wachi35676:fix/shader-amd-lds-fence-scope

Conversation

@wachi35676

@wachi35676 wachi35676 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #8134, which has merged; this branch is now a single commit on main.

What

The recompiler puts a workgroup-scope OpMemoryBarrier(Workgroup, AcquireRelease | WorkgroupMemory) after three kinds of LDS update:

  • every LDS atomic;
  • the compare-exchange loop that writes a byte or a halfword of LDS (ds_write_b8/ds_write_b16);
  • misaligned LDS stores.

AMD's proprietary Windows driver compiles that memory barrier to a real s_barrier. I read the driver's code objects out of its pipeline cache to see this; capturing VK_KHR_pipeline_executable_properties internal representations fails with -13 for this pipeline.

These updates usually sit in branches that only some waves of a workgroup take. Those waves then execute extra s_barriers, which pair with the other waves' real barriers, so the other waves read LDS before it is written.

An OpMemoryBarrier is not an execution barrier, and SPIR-V allows it in non-uniform control flow. The SPIR-V is valid, so this is a driver bug. On the same driver, a device-scope OpMemoryBarrier compiles to buffer_gl0_inv + buffer_gl1_inv with no s_barrier.

PS5LM Plus (PPSA99581, llama.cpp's Vulkan backend) drifted into different garbage on every run on that driver with flash attention on. Its flash-attention shader stores two halfwords to LDS inside the KV loop, in a branch that waves 0-1 of its four take. Results from replaying that dispatch with identical inputs:

LDS fence on AMD s_barrier in the driver's code (guest has 21) Replay vs NVIDIA (0x7380 bytes of output)
Workgroup scope (before) 23 159 words differ, up to 1.7e38
Device scope (this PR) 21 9 words differ in the last bit (≤ 3e-8)
Subgroup scope / no fence (experiments only) 21 the same 9 last-bit differences

The guest's 21 s_barriers map to 21 OpControlBarriers, which the driver compiles to one s_barrier each, none moved or duplicated. The 2 extra come from the two halfword stores.

Changes:

  • WorkgroupScopeFenceUsable(VkDriverId) in SubgroupBarrier.hpp is false only for VK_DRIVER_ID_AMD_PROPRIETARY. NVIDIA, RADV, AMDVLK, Intel, llvmpipe and MoltenVK keep the workgroup scope. AMDVLK is excluded only because it was not tested.
  • The device passes that as the new SpirvTarget::deviceScopeLdsMemoryBarriers, which is part of the shader cache key.
  • With the flag set, the LDS fence (LdsMemoryBarrierScope) is device scope, with the same semantics. Device scope includes the workgroup, so it orders everything the old fence did. Modules use the GLSL450 memory model, and device-scope fences are already emitted for buffer atomics.
  • SharedAtomicUpdate (ds_min/max_f32, ds_cmpst_f32 and the other read-modify-write LDS operations) emitted a second fence identical to the one AtomicUpdate already emits. The duplicate is dropped, so each update has one fence on every driver.
  • agc_shader_replay --device-scope-lds-memory-barriers replays a request the way that driver compiles it. Like --wave-lds-memory-barriers, the flag is not serialized.
  • One debt line in docs/dev/debt/shader.md.

The fences inside the LDS lock path are untouched, because that path emits none. On that driver a ds_add_u64 program (the lock path) had no s_barrier besides its uniform lock-initialisation barrier, and an LDS exchange after it in a branch passed.

Not covered:

  • Only one GPU and driver was measured: the Radeon iGPU of a Ryzen 5 9600X (0x13c0, RDNA 2), Windows driver 32.0.21042.62.
  • LeandroLP23 reports that a Radeon 860M (RDNA 3.5, 32.0.13058.8001) gives correct flash-attention output even without fix(shader): order wave LDS accesses with a memory barrier on AMD's proprietary driver #8134. The gate still covers every AMD proprietary version.
  • The cost of the wider fence on AMD (GL0 and GL1 invalidation per LDS update) was not timed separately. PS5LM Plus decodes at the same speed with and without it (2-4 tokens/s on that iGPU).

Tested

Windows 11, WinLibs GCC 15.2, full Release build of 98f4019 (on #8134 473497c, main 3aa35dc; the branch also merges cleanly with main 44e3389). ctest -R "agc|shader|spirv|recomp" ran with ANYPS5_REQUIRE_VULKAN=1 and -j4; failures were rerun alone. The baseline is #8134's head 473497c.

  • NVIDIA GeForce RTX 5070 Ti (617.42): 827 tests, 2 failed: agc_driver_msaa_dcc_clear_4 and agc_driver_pass_input_hazard. The baseline has the same 2 of 826.
  • AMD Radeon Graphics (0x13c0, 32.0.21042.62): 827 tests, 5 failed:
    • agc_trimmed_import, agc_driver_msaa_dcc_clear_4, agc_driver_draw_cache_reuse_off and agc_driver_stencil_replay_no_gpl also fail on the baseline.
    • agc_driver_layered_volume_vertex_no_gpl fails with a device fault reading 0xbeefdeadc000, a fault the debt file already lists for this driver. The family's failures depend on test order: on the baseline the same fault hits agc_driver_layered_volume_draw and agc_driver_layered_volume_vertex instead (baseline: 8 of 826 failed).

Linux (WSL Ubuntu 24.04, GCC 13.3, llvmpipe): full build with -DBUILD_TESTING=ON, the same ctest set, 817 tests. The same 17 tests fail as on #8134; they need watched guest memory or llvmpipe features. agc_subgroup_barrier and agc_shader_disk_cache pass, and agc_driver_lds_subword_store_branch skips (no 32-lane subgroups).

New and changed tests:

  • agc_driver_lds_subword_store_branch runs four wave32 waves. Waves 0-1 store a halfword to LDS in a branch and then spin for 4096 iterations. After that, every thread writes an LDS slot, passes one s_barrier and reads the slot of thread tid ^ 64. The test runs 16 rounds.
    • With the scope widening disabled (negative control), it fails 3 of 3 times on AMD: round 0: thread 64 (wave 2) read 868449278, expected 65536.
    • With the change it passes 3 of 3 on AMD and on NVIDIA. NVIDIA passes either way.
  • agc_subgroup_barrier also checks the new quirk, and that ds_write_b16, ds_add_u32 and ds_max_f32 each emit exactly one fence of the expected scope with the flag off and on. It fails without the change.
  • agc_shader_disk_cache checks that the new flag changes the cache key.

PS5LM Plus v0.8.4 was run with qwen2.5-0.5b-instruct-q4_k_m.gguf, -fa on -ngl 999, the prompt "The capital of France is" and temperature 0, on the AMD iGPU with a warmed shader cache per configuration. Flash attention also needs #7410: without it, NVIDIA too answers garbage with -fa on. So these runs use a local integration branch, this PR (#8134 + this commit) merged with #7410 (a80f17d). The "before" rows are the same build with the new flag forced off.

KV cache Before (flag off) After
q8_0 4 runs, 4 different texts, e.g. " Paris. It is not a city and not to\",__ F 12G" 3 of 3: " Paris. It is the largest city in Europe and the second largest in the world"
f16 4 runs, 4 different texts, e.g. " Paris. It is in France, in France and or in France, in France" 4 of 4: the same text as q8_0

The correct text matches NVIDIA and the CPU. The first cold q8_0 run of the final commit hit the guest's own 10 s fence timeout while shaders compiled. The previous revision of this commit, with the duplicate fence still in place, gave the same correct text on 5 q8_0 and 4 f16 runs.

Checklist

🤖 Generated with Claude Code

https://claude.ai/code/session_01BM2kfDbr6MydxsfTckZJDR

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6959 docs/dev/debt/shader.md L191

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1395 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.cpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
docs/dev/debt/shader.md
PR 5371 docs/dev/debt/shader.md
PR 5431 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5441 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5609 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.hpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.hpp
PR 6202 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6235 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/shader.md
PR 6280 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 6412 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/shader.md
PR 6488 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
docs/dev/debt/shader.md
PR 6539 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 6858 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
PR 6924 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 6960 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 7085 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 7201 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
PR 7275 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7307 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7369 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7484 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
docs/dev/debt/shader.md
PR 7550 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
PR 7618 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
PR 7677 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
PR 7684 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 7689 core/shader/recompiler/Recompiler.hpp
PR 7922 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
docs/dev/debt/shader.md
PR 8004 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
docs/dev/debt/shader.md
PR 8014 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 8077 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 9020 core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
docs/dev/debt/shader.md
PR 9049 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
PR 9183 docs/dev/debt/shader.md
PR 9193 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/shader.md
PR 9335 core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
docs/dev/debt/shader.md
PR 9348 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 9356 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 9387 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 9400 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 9431 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9432 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9508 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
PR 9633 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 9672 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9673 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9674 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9675 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9676 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9748 core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
docs/dev/debt/shader.md
PR 9749 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 9842 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9852 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.hpp
PR 9889 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
docs/dev/debt/shader.md
PR 9891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9892 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9893 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9895 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp
PR 9897 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.hpp
PR 9898 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 9969 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 9989 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 10000 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/shader.md
PR 10049 core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
core/shader/recompiler/Recompiler.hpp
PR 10193 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
PR 10194 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 10348 core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.hpp
PR 10359 core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp

@xXPBSXx

xXPBSXx commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Independent Windows run of this PR on an RDNA 2 card (data only).

Environment: Windows 10 Pro 10.0.19045, Ryzen 7 5800X3D, AMD Radeon RX 6400 (AMD driver 32.0.21033.3005; the log shows Selected GPU name=AMD Radeon RX 6400 vendor=0x1002 device=0x743f subgroup=64, picked with ANYPS5_GPU="RX 6400"), WinLibs GCC 15.2.0 (MinGW-w64 UCRT), Release. PR head c1528a6e18; merge-tree against main is clean. I built agc_driver_lds_subword_store_branch_tests, agc_driver_lds_tree_reduction_tests and agc_subgroup_barrier_tests.

Results (3 runs each):

  • PR head: agc_driver_lds_subword_store_branch, agc_driver_lds_tree_reduction and agc_subgroup_barrier pass 3/3.
  • Baseline: I kept the PR's tests and changed only WorkgroupScopeFenceUsable in SubgroupBarrier.hpp to return true;, so AMD's proprietary driver keeps the workgroup-scope fence again, then rebuilt. agc_driver_lds_subword_store_branch fails 3/3 on this card with thread 64 (wave 2) read <stale value>, expected 65536 (the stale value was 1135280128 in two runs and 1065353216 in one); agc_subgroup_barrier fails 3/3 (driver 1: expected workgroup-scope fences to be unusable, a check of the quirk itself); agc_driver_lds_tree_reduction passes 3/3 in the baseline too, so it does not distinguish the two states on this card. I restored the PR's header afterwards.

So the stale LDS read the PR describes reproduces on an RX 6400 without the device-scope fence and is gone with it. I did not inspect the driver's code objects, so I can not confirm the s_barrier explanation, and my driver version differs from the one in the PR text.

Not run: PS5LM Plus or any title, the replay comparison against NVIDIA, other GPUs and platforms, the full repository CTest.

AI-assisted (Claude Code).

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@wachi35676
wachi35676 force-pushed the fix/shader-amd-lds-fence-scope branch from c1528a6 to f1a2103 Compare October 21, 2026 12:14
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@wachi35676

Copy link
Copy Markdown
Contributor Author

Rebased onto main 3e91c8edf now that #8134 has merged, so this is a single commit: f1a210313. The conflicts were in CacheKey.hpp, Recompiler.hpp and SpirvEmitter.cpp, where main added float32DenormPreserve beside this PR's deviceScopeLdsMemoryBarriers; both are kept. The code is otherwise unchanged.

Windows 11, WinLibs GCC 15.2.0, agc|shader|spirv|recomp|subgroup subset:

Thanks @xXPBSXx for the RX 6400 run.

AI-assisted: yes (Claude Code); results above were run on this machine.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@adriadam10 adriadam10 self-assigned this Oct 23, 2026
…tary driver

The recompiler orders an LDS atomic, and the compare-exchange loop that
writes a byte or a halfword of LDS (ds_write_b8/b16), with a
workgroup-scope OpMemoryBarrier after the update. AMD's proprietary
Windows driver 32.0.21042.62 (Radeon 0x13c0, RDNA 2) compiles that
memory barrier to s_barrier, as the code objects in its pipeline cache
show. Such updates usually sit in branches that only some waves of a
workgroup take, so those waves execute extra s_barriers, which pair
with the other waves' barriers, and the other waves read LDS before it
is written. An OpMemoryBarrier is not an execution barrier and SPIR-V
allows it in non-uniform control flow, so the SPIR-V is valid; a
device-scope OpMemoryBarrier compiles to GL0 and GL1 cache
invalidations without s_barrier there.

PS5LM Plus (PPSA99581, llama.cpp's Vulkan backend) drifted into
garbage on that driver with flash attention on. Its flash-attention
shader stores two halfwords to LDS in a branch that two of its four
waves take, inside the KV loop. The driver's code had 23 s_barrier for
the guest's 21; a replay of the dispatch differed from NVIDIA in 159
words, up to 1.7e38, and now has 21 and differs only in the last bit
of 9 words.

On that driver the fence is now device scope, which orders everything
the workgroup-scope fence did. Other drivers keep the workgroup scope.
The flag is part of the shader cache key, and agc_shader_replay takes
--device-scope-lds-memory-barriers to replay a request the way that
driver compiles it. The LDS update helper already fences, so the
second, identical fence after it in ds_min/max_f32 and the other
read-modify-write LDS operations is dropped.

agc_driver_lds_subword_store_branch stores a halfword to LDS in a
branch that waves 0-1 of four take, delays those waves and then
exchanges LDS slots across one s_barrier. Before, thread 64 read a
stale slot on the AMD driver; it passes on AMD and NVIDIA now.
agc_subgroup_barrier checks the driver quirk and that ds_write_b16,
ds_add_u32 and ds_max_f32 each emit one fence of the expected scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BM2kfDbr6MydxsfTckZJDR
@adriadam10
adriadam10 force-pushed the fix/shader-amd-lds-fence-scope branch from f1a2103 to 98df163 Compare October 23, 2026 19:11
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@adriadam10
adriadam10 merged commit e7f46f5 into boykopovar:main Oct 23, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the diff, the new tests and the third-party AMD run. The fence scope only changes for VK_DRIVER_ID_AMD_PROPRIETARY, it is part of the shader cache key, and agc_subgroup_barrier pins one fence of the expected scope for ds_write_b16, ds_add_u32 and ds_max_f32. Rebased onto current main (adjacent fields in Recompiler.hpp kept beside the new one); merged.

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

Labels

agc libSceAgc* build docs shader core/shader unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants