Repository navigation
feat(recompiler): run single-wave wave64 compute programs at one lane per invocation on 32-lane hosts - #5405
Conversation
|
Intel data for this, since the numbers above are NVIDIA only. Fedora 44, Intel Arc (Meteor Lake iGPU, Mesa 26.2.3 ANV, 32-lane host subgroup), Stray (PPSA02101), head
It helps much more on ANV than the NVIDIA table suggests. Stray's post-processing programs
The required 32-lane subgroup is what ANV can't handle. The second program, spanned, is 530,653 words and still didn't finish in 20 minutes. In spanned form Stray's two largest programs use no subgroup operation at all, and nothing else in the mode needs the subgroup to be 32 wide except the lane number (
With that, every spanned Stray program has no AI-assisted: yes. |
|
Demon's Souls (PPSA01342), CPU-side only: 92 captured requests through
All three columns fail the same requests: 3 stencil-export replay refusals, and in recompile also 17 SRT descriptor-source evaluations and 1 invalid runtime image descriptor. The 17 SRT failures are registration-time preparation dumps that carry no dispatch user data, replayed as if they were dispatch captures (#6134 names them apart). They are not stops in the title. Only the 78 compute programs with 64-invocation workgroups change. All of them get smaller (median ratio 0.70 for prepare, 0.64 for recompile), and none fall back. The 3 refused graphics requests go through the new fallback and are emitted twice (open item 3). The seconds are within noise: main and switch-off differ by up to 3%. Not tested: pipeline creation (no GPU, so the RTX 3060 was not involved), spirv-val, and running the game. AI-assisted: yes |
adriadam10
left a comment
There was a problem hiding this comment.
Thanks for the fix for the lock-loop deadlock. Reviewed on the diff; things to address before this can merge:
LdsMaskWaterfall.cpp: the test never turns the mode on.APS5_SPANNED_WAVEis read from the environment inRecompiler.cpp:527and the test registration doesn't set it, so in ctest/CI both loops run the two-lane form and the test passes without the fix. Make it exercise both modes explicitly (see the next point for how) and show it fails on0843282d1/3cf9a9bbewithouteccf0ce2a.Recompiler.cpp:527:spannedWaveschanges the emitted SPIR-V but is not inCacheKey.hpp(~l.263) orRequestSerializer.cpp(~l.804), so the disk cache can serve a module built in one mode to the other, and replays can't reproduce it. Carry it as aSpirvTargetfield set by the driver and include it in both (your open item 3), instead of a process-wide staticgetenv.SpirvEmitter.cpp:273: the spanned numberingSubgroupId * 32 + SubgroupLocalInvocationIdassumes two full 32-lane subgroups. WithoutREQUIRE_FULL_SUBGROUPS(your open item 2) that isn't guaranteed (e.g. varying subgroup sizes on ANV), so either set the flag or only enable the mode when it's guaranteed.Recompiler.cpp:535: the fallback catches everystd::exception; limit it to the spanned-specific failures so real emit errors still surface.- Open item 1 (cheaper ballot loses the device for an unknown reason) points at a barrier-placement problem not yet understood; the debt line says any other cross-lane exchange inside per-lane control flow hangs. Please document how spanned programs are checked for that (the SPIR-V scan you did for
0x80003af600) or make the emitter reject a barrier outside uniform control flow.
… per invocation on 32-lane hosts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A spanned wave64 program exchanges ballots and shuffles through workgroup-scope barriers. When a BDA fault stopped one invocation early, the other 63 would wait at the next barrier forever. Spanned programs now keep every invocation running and read zeros after a fault, as programs with wave LDS barriers already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t subgroup The lock loop that emulates 64-bit LDS atomics runs only on the lanes the guest's exec mask keeps, so a spanned wave's barrier-based ballot there was not reached by both host subgroups. When one subgroup had no active lane the other one paired its barriers with unrelated ones and left the loop with lanes still pending, or the GPU hung (Ghost of Yotei, program 0x80003af600). The elect and the lock are already per host subgroup, so each subgroup now loops until its own lanes are done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t subgroup Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APS5_SPANNED_WAVE is now read once by the driver, which sets SpirvTarget::spannedWaves only on a device with 32-lane compute subgroup control and computeFullSubgroups. The recompiler no longer reads the environment. The field is part of the cache key and of the request serialization (version 32), so a cached or replayed module keeps the mode it was built in. A spanned module numbers its lanes SubgroupId * 32 + SubgroupLocalInvocationId, which needs two full 32-lane subgroups. The artifact records this as fullSubgroups (disk cache and pipeline identity included), and the driver creates those pipelines with REQUIRE_FULL_SUBGROUPS. Compute pipeline captures accept the flag with a required subgroup size. The two-lane fallback now catches only SpannedWaveUnsupported, thrown for a shuffle wider than 32 bits; other emit errors surface as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test set neither mode, so ctest ran only the two-lane form. It now recompiles each program two-lane and then spanned through SpirvTarget::spannedWaves, checks that the spanned module asks for full subgroups, and skips the spanned pass on a device without full 32-lane compute subgroups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roup rule Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5fc7d68 to
5fb23f7
Compare
|
Thanks for the review. Rebased on main 65d56a2 and addressed in 7a6cc71, 641e53b and 5fb23f7 (fix commit is now a015284):
Tested on an RTX 4090: full suite 1318 of 1319 pass. |
adriadam10
left a comment
There was a problem hiding this comment.
Re-checked the new head against each point of the previous review:
LdsMaskWaterfallnow setsrequest.target.spannedWavesitself, runs two-lane then spanned and checksresult.fullSubgroups.spannedWavesis aSpirvTargetfield set by the driver, part of the cache key and of the request serialization (v32), with tests for both.fullSubgroupsis carried in the artifact, the disk cache and the pipeline identity, and spanned pipelines are created withREQUIRE_FULL_SUBGROUPS.- The two-lane fallback catches only
SpannedWaveUnsupported. - TechnicalDebt records how barriers are checked and that the emitter has no uniformity check.
Built and ran the Agc/AgcDriver tests on the merged tree: all pass except agc_driver_tessellation (RADV segfault in pipeline-library linking, unrelated to this change). Note the spanned pass of lds_mask_waterfall skips on RADV and lavapipe, so it only runs on 32-lane NVIDIA/ANV; it was covered by your RTX 4090 run. Merging once the full Build is green.
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5405 took request version 32 on main for spanned waves, so a version 32 request from main has no point-list tail. The point fields now come with version 33, after the spanned-wave flag, and the tests expect 33 and reject 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e hosts, off by default) at 9abe1c5
…groups Hand port of upstream 7a6cc71 (Pipodi, boykopovar#5405 follow-up) onto our stack. APS5_SPANNED_WAVE is read once by the driver, which sets SpirvTarget::spannedWaves only on a device with 32-lane compute subgroup control and computeFullSubgroups; the recompiler no longer reads the environment. The field is part of the cache key, so spanned and two-lane modules no longer share disk cache entries. A spanned module numbers its lanes SubgroupId * 32 + SubgroupLocalInvocationId, which needs two full 32-lane subgroups. The artifact records fullSubgroups (disk cache), and both compute pipeline creation paths create those pipelines with REQUIRE_FULL_SUBGROUPS; compute pipeline captures carry the flag. The two-lane fallback catches only SpannedWaveUnsupported (a shuffle wider than 32 bits); other emit errors surface as before. Differences from upstream: requests are not re-versioned (serialization stays at 31, like bindlessImages), so agc_shader_replay sets the target's spannedWaves from APS5_SPANNED_WAVE as the driver does, and --native-target names a spannedWaves difference. The pipeline identity mixing of fullSubgroups is left out (our pipeline keys hash the SPIR-V, which already differs between the modes). The LDS lock test sets the target flag instead of the environment. AI-assisted: yes
The mode is opt-in (
APS5_SPANNED_WAVE=1) and off by default, so nothing changes unless it is set. The Ghost of Yōtei device loss reported earlier is found and fixed in a015284 (see The Yōtei device loss).What
On a 32-lane host (NVIDIA) a wave64 compute program currently runs two guest lanes per invocation: every instruction is emitted twice, once per 32-lane half (
laneCount == 2). For large programs this doubles the module and roughly doubles the driver's compile time. Ghost of Yōtei's loading compute programs reach 1.1–1.8M SPIR-V words and take 25–100 s each to compile cold.This adds a spanned wave mode for compute programs whose workgroup is exactly one wave of 64 invocations. The program runs at one guest lane per invocation, the wave spans the workgroup's two 32-lane subgroups, and anything that crosses the two halves goes through workgroup memory:
SubgroupId * 32 + SubgroupLocalInvocationId. The module declares a 64×1×1 workgroup, and the guest'sLocalInvocationId/GlobalInvocationIdare derived from the lane, the same way the two-lane form already does.ctx.Ballot,EmitSubgroupBallot): each lane ORs its bit into a two-dwordwave_exchangeslot between workgroup barriers, so both halves see the 64-bit mask.FindLSBworks over both words.v_permlane16/x16, the 32-lane permute, andds_swizzle/ds_bpermutethroughEmitDsMaskedLaneRead. This goes through the newctx.ShuffleInHalfandEmitSubgroupShuffleInHalf.v_readlane, read-first-lane) go through workgroup memory: store, barrier, load.electand its "any lane still pending" ballot both use the host subgroup (EmitHostSubgroupBallot), and the lock is still exclusive with two electors.EmitHalfWaveReductionis skipped; the genericv_readlanepath handles it.tableIndexNonUniform, since its read-first-lane keys are wave-wide.APS5_SPANNED_WAVEonce and setsSpirvTarget::spannedWaves, only on a device with 32-lane compute subgroup control andcomputeFullSubgroups. The field is in the cache key and the request serialization (version 32).fullSubgroupsand the driver creates spanned pipelines withREQUIRE_FULL_SUBGROUPSand a required size of 32.SpannedWaveUnsupported, a shuffle wider than 32 bits), the program is emitted again in the two-lane form. Other emit errors are not caught.The two-lane form only ever branches on wave-uniform values, because both halves share one invocation. So the barriers added here are always reached in uniform control flow.
Measured
Offline (
agc_compute_pipeline_replay, RTX 4090, driver 617.42, fresh driver cache per run, modules materialized from dumped Yōtei requests):0x80002893000x800022d1000x80001fd4000x8000305900(654 shuffles, 556 ballots)These spanned numbers were measured with a subgroup-ballot variant that is replaced here; see open item 1.
In game (Ghost of Yōtei, cold driver cache for every spanned module, earlier version of the mode): about 40 minutes with no errors or device loss. The giant programs' cold builds dropped from 14/183/68 s to 4/36 s (
0x8000284e00), from 19/24/94 s to 7/10 s (0x8000289300) and from 25/25/59 s to 8/11 s (0x800022d100). The boot reached its slow loading phase 13.6 minutes earlier than the two-lane run.Other hardware and titles, from the comments here: @ThatsLeo ran Stray (PPSA02101) on Intel Arc / ANV with the full suite and the game, and @fdfawef324 replayed 92 Demon's Souls (PPSA01342) requests (−27.7% SPIR-V words with the mode on). Neither saw a fault.
The Yōtei device loss
With the mode on, Ghost of Yōtei lost the device (NVIDIA TDR) in every run around frame 817. It looked tied to
0x80001e5f00's second variant, but that program has no LDS instructions. The program behind it is0x80003af600. Its new variant was built right before every loss, and a loop guard caught a loop in it running past a million exits.How it was found
0x80003af600request with its captured SRT regions and synthetic buffers. With random buffers over 64×36 groups, spanned lost the device with the same device-fault signature, while two-lane passed. Every other case gave bit-identical outputs in both modes.s_endpgmat block starts put it after the first pass of an inners_ff1/v_readfirstlanewaterfall.OpControlBarriersit only inside loops. The other 3 sit inside per-invocation conditionals, lane active and LDS address in bounds. They are the spanned ballot ofLockedLdsUpdate, the spin lock that emulates the program'sds_or_b64.When a whole host subgroup has no active lane at that
ds_or_b64, it never reaches the ballot's three barriers. The other subgroup then pairs them with unrelated barriers. The loop either leaves with a lane still pending, so an OR is lost, or it hangs. a015284 ballots the lock loop within its host subgroup.LdsMaskWaterfall.cpp(6006d45, 641e53b) runs an LDS bitmask waterfall and an exec-maskedds_or_b64, two-lane and then spanned, setting the mode on the request itself. Without the fix (rebuilt on this branch), spanned fails withexec 0xffffffff00000000: lane 0 slot word 1 is 1, expected 65537. After it, both modes pass.The TechnicalDebt entry records the general limit: any other cross-lane exchange emitted inside per-lane control flow would hang a spanned wave the same way, and the emitter does not reject one. It also records how a program is checked today (
spirv-dis, the constructs enclosing eachOpControlBarrier, the condition of each enclosing selection). Only this program's SPIR-V has been checked. #6670 (@MarcZX100) fixes the same lock's initialization, a separate race.Tested
Windows 11 Pro 26200, RTX 4090, driver 617.42. This branch at 5fb23f7 on
main65d56a2:agc_driver_pixel_ldsfails with avkWaitForFencestimeout in its wave32 pass, and fails the same way onmain65d56a2 without this PR.misaligned_writer_aheadis not built: main'sGpuIndirectArgumentsMisalignedWriterAhead.cppneeds 65536-byte alignment, which MinGW's object format does not allow.lds_mask_waterfall: both modes pass. With the lock-loop ballot reverted toEmitSubgroupBallot, the spanned pass fails withspanned masked ds_or_b64, exec 0xffffffff00000000: lane 0 slot word 1 is 1, expected 65537.APS5_SPANNED_WAVE=1(the driver logs the mode as on): all 12 pass. They arelds_mask_waterfall,lds_atomics,lds_atomics64,lds_integer_atomics,dpp8,dpp8_pk_fmac,dpp16_row_share_xmask,ds_swizzle,permlane16,read_first_lane,look_back_scanandwave_uniform.APS5_SPANNED_WAVE=1(earlier base): 66 GPU tests run 357 spanned shaders, and all of them pass. These cover DPP,ds_swizzle,permlane16, read-first-lane, look-back scan, wave-uniform logic, LDS/GDS atomics, append/consume and scalar control flow.agc_shader_memoryfails because it asserts the structure of the two-lane form.Open items
0x80003af600only.0x80001e5f00, 942 ballots) took 816 s to compile in the earlier spanned version. With this version its second variant compiles in 33–37 s.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt (none).mdfiles or images added to the repository🤖 Generated with Claude Code