Skip to content

perf(vk): build Intel compute programs that cannot see their lanes without the 32-lane pin - #2564

Open
thomas-montano wants to merge 1 commit into
NotRexed:mainfrom
thomas-montano:perf/intel-compute-pin
Open

thomas-montano wants to merge 1 commit into
NotRexed:mainfrom
thomas-montano:perf/intel-compute-pin

Conversation

@thomas-montano

@thomas-montano thomas-montano commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

On Intel/Mesa, game compute programs are required to run at 32 lanes even when they cannot see their subgroup. On an Iris Xe that makes some first builds take 5-26 seconds, leaving the effects they draw missing while the player waits.

Part of #1001

What changed

  • spirv_uses_subgroups reads the SPIR-V's declared capabilities: GroupNonUniform*, the KHR ballot and vote, Groups and cooperative matrices mean the lane count matters. Unknown or malformed SPIR-V is treated as subgroup-using.
  • On Intel, pin_compute leaves a compute program without those capabilities unpinned, so the driver chooses its width. A program with shared memory and at most 32 invocations stays pinned: a compiler may drop barriers a single-warp workgroup does not need.
  • The property is recorded on translation and recalculated when a cached program is read; the cache format does not change. Every game compute pipeline creation goes through pin_compute.
  • The networks and the mesh fallback's programs use subgroup operations and stay pinned. Other GPUs keep the old behavior by default. RX_VK_PIN_ALL=1 pins every program as before; =0 enables the selective rule on any GPU.

This complements #2513: that change reduces the SPIR-V for table-heavy programs; this one removes the SIMD32 requirement only from programs that cannot observe their subgroup.

Checks

  • tests/test_compute_pin.py checks the capability scan, the pin decision, the shared-memory exception and every game compute pipeline's use of pin_compute.
  • tests/test_program_cache_cleanup.py compiles the scan with the cache loader.
  • tests/test_precompile_compute_guard.py compiles the precompile's compute arm with a pin_compute stand-in; which programs are pinned is left to tests/test_compute_pin.py.

Tested in the game

Iris Xe (Raptor Lake-P, i9-13900H, 32 GB), Mesa ANV 26.2.4, Bazzite. Headless at the Deku Tree, with the serial queue's escape:

  • 9790303532c0c309, 76b54f5bb0d7ce97, 37d9fd3069f0e5e4 and 0fe7f6c474c57be5 each built in under 0.2 s without the pin, against 5.5, 26.4, 14.7 and 14.5 s pinned.
  • The picture and frame rate were the same as with RX_VK_PIN_ALL=1 (9.2 against 9.1 fps).

Not tested: other Intel GPUs, Windows.

@thomas-montano
thomas-montano force-pushed the perf/intel-compute-pin branch 2 times, most recently from ea34279 to d238ae0 Compare November 11, 2026 23:25
…thout the 32-lane pin

On an Iris Xe a game compute program's first build took 5-26 s at 32 lanes,
one at a time, while the effects it draws stayed missing.

NVN's programs are translated for 32 lanes and every compute stage is pinned to
that size, which Mesa's compiler for Intel GPUs builds slowly (register
allocation at SIMD32). A program whose SPIR-V declares no subgroup capability
(GroupNonUniform*, the KHR ballot and vote, Groups, cooperative matrices) does
the same at any lane count, so on Intel it is now built without the pin and the
driver picks its width (spirv_uses_subgroups, Stage::subgroups, pin_compute).
One with shared memory and at most 32 invocations keeps the pin: NVIDIA's
compiler may drop the barriers a single-warp workgroup does not need. The
networks and the mesh fallback's programs use subgroup operations and stay
pinned. RX_VK_PIN_ALL=1 pins every program as before; =0 does this on any GPU.

Tested on an Iris Xe (Mesa ANV 26.2.4), headless at the Deku Tree, with the
serial queue's escape: 9790303532c0c309, 76b54f5bb0d7ce97, 37d9fd3069f0e5e4 and
0fe7f6c474c57be5 built in under 0.2 s each against 5.5, 26.4, 14.7 and 14.5 s
pinned; the picture and the frame rate are the same as with RX_VK_PIN_ALL=1
(9.2 against 9.1 fps). tests/test_compute_pin.py checks the scan, the decision
and that every game compute pipeline goes through pin_compute;
tests/test_program_cache_cleanup.py compiles the scan with the cache loader.
Not tested: other Intel GPUs, Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Joey-Magnus Joey-Magnus 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.

I merged this into current main. tests/test_compute_pin.py, test_program_cache_cleanup.py and test_env_on.py pass. test_precompile_compute_guard.py needs Windows SEH, so it skipped here, but CI ran it green. The capability scan is correct, and anything it can't parse counts as subgroup-using. Other vendors keep the pin by default. The 5.5–26 s first builds against under 0.2 s on the Iris Xe are a measurement worth having.

One risk I'd like you to address or rule out. vk_draw.cpp:198 keeps the pin for shared-memory programs only when they have at most 32 invocations. Guest code written for Maxwell can also be warp-synchronous inside a larger workgroup. The classic case is a reduction tail through shared memory without BAR for the last 32 lanes. That needs no subgroup capability in the SPIR-V. It was safe at SIMD32, and on ANV's SIMD8/16 it can read stale values. Could you check whether the four slow programs use shared memory at all? If they don't, keeping every shared-memory program pinned costs nothing and removes the question. The picture matching RX_VK_PIN_ALL=1 at the Deku Tree is good, but it is a single scene.

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.

2 participants