Skip to content

[rocjitsu] Reuse decoded instructions in the CU issue loop - #12632

Merged
kuhar merged 1 commit into
developfrom
users/kuhar/mt19937-simulation-performance
Oct 1, 2026
Merged

kuhar merged 1 commit into
developfrom
users/kuhar/mt19937-simulation-performance

Conversation

@kuhar

@kuhar kuhar commented Oct 1, 2026

Copy link
Copy Markdown
Member

MT19937 initialization repeatedly executes a long scalar and LDS loop. The byte instruction cache avoids fetches, but each issue still decodes, constructs and destroys the same instruction and operand objects.

Retain completed non-memory instructions in a bounded, lazily allocated 256-entry cache per CU. Match PC, VMID and all fetched words, including literals. Preserve fetch permission, revocation and byte-cache checks before reuse, bypass reuse while debugging, and clear entries before replacing the decoder.

Allocate CU instructions on the heap without installing a decoder pool on the constructing thread. Keep ownership exclusive during issue and asynchronous execution, independently of worker and decoder lifetimes. Exclude memory operations, dynamic state, borrowed fetch-buffer encodings, failed execution and exception unwinding from retention.

Document decoded re-execution in Instruction and the execution generator. Cover current registers and EXEC, DPP delegate restoration, literal and VM changes, debugger writes, cache collisions and ownership transfer. Test unrelated allocator hooks and sequential worker lifetimes, and qualify the borrowed VOPD exclusion on RDNA3, RDNA3.5, RDNA4 and CDNA5.

On the reviewed base, all 162 selected cache, dispatch, async, matrix, wait-scoreboard and lane checks pass in native, forced-scalar and ASan/UBSan modes. The broad native suite retains only its two known DBT fixture failures. The registered launcher-handoff check and formatting checks pass.

Benchmarks use the reviewed cache implementation atop 0510825. Three alternating matched Release pairs against that lane-fixed base reduce median time for the exact gfx942 MT19937 jump kernel with 512 engines from 24.805 to 22.110 seconds at 32 dispatch threads (1.12x). Every raw state word matches the physical gfx1201 reference.

The complete exact-nightly 16,384-engine integer API case passes in 311.493 seconds with the original CI topology.

Matched IREE and controlled-compiler Gluon selections preserve every case outcome and compiled kernel hash. All 195 IREE cases pass; Gluon retains 129 passes, nine skips and its two baseline TDM gather failures.

Issue: https://github.com/ROCm/rocm-libraries/actions/runs/36740815382

MT19937 initialization repeatedly executes a long scalar and LDS loop.
The byte instruction cache avoids fetches, but each issue still decodes,
constructs and destroys the same instruction and operand objects.

Retain completed non-memory instructions in a bounded, lazily allocated
256-entry cache per CU. Match PC, VMID and all fetched words, including
literals. Preserve fetch permission, revocation and byte-cache checks
before reuse, bypass reuse while debugging, and clear entries before
replacing the decoder.

Allocate CU instructions on the heap without installing a decoder pool
on the constructing thread. Keep ownership exclusive during issue and
asynchronous execution, independently of worker and decoder lifetimes.
Exclude memory operations, dynamic state, borrowed fetch-buffer
encodings, failed execution and exception unwinding from retention.

Document decoded re-execution in Instruction and the execution
generator. Cover current registers and EXEC, DPP delegate restoration,
literal and VM changes, debugger writes, cache collisions and ownership
transfer. Test unrelated allocator hooks and sequential worker
lifetimes, and qualify the borrowed VOPD exclusion on RDNA3, RDNA3.5,
RDNA4 and CDNA5.

On the reviewed base, all 162 selected cache, dispatch, async, matrix,
wait-scoreboard and lane checks pass in native, forced-scalar and
ASan/UBSan modes. The broad native suite retains only its two known DBT
fixture failures. The registered launcher-handoff check and formatting
checks pass.

Benchmarks use the reviewed cache implementation atop 0510825. Three
alternating matched Release pairs against that lane-fixed base reduce
median time for the exact gfx942 MT19937 jump kernel with 512 engines
from 24.805 to 22.110 seconds at 32 dispatch threads (1.12x). Every raw
state word matches the physical gfx1201 reference.

The complete exact-nightly 16,384-engine integer API case passes in
311.493 seconds with the original CI topology.

Matched IREE and controlled-compiler Gluon selections preserve every
case outcome and compiled kernel hash. All 195 IREE cases pass; Gluon
retains 129 passes, nine skips and its two baseline TDM gather failures.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 15:32
@kuhar
kuhar requested review from a team and atgutier as code owners October 1, 2026 15:32
@kuhar
kuhar requested a review from amd-arosa October 1, 2026 15:32
@therock-pr-bot

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

Copy link
Copy Markdown

❌ PR Check — Action Required

Check Status Details
📝 PR Description ❌ Fail Error: PR description must reference a JIRA ID, ISSUE ID, or a GitHub closing keyword.
Expected: include a JIRA ID / ISSUE ID line (separator : or -, or omitted; value may be a JIRA key, a number with/without #, or a link), OR a closing keyword + issue reference. Accepted examples:
• JIRA ID : TESTAUTO-6039
• JIRA ID - #330
• JIRA ID #330
• JIRA ID (on separate line)
ROCM-25757
• ISSUE ID : TESTUTO-3334
• ISSUE ID #3334
• ISSUE ID - TESTAUTO-3433
• ISSUE ID (on separate line)
AIRUNTIME-2352
• ISSUE ID : https://github.com/<org_name>/<repo_name>/issues/1234
• Closes #10
• Fixes octo-org/octo-repo#100
• Resolves: #123
• #123
• https://github.com/<org_name>/<repo_name>/issues/123
Current: no valid JIRA/ISSUE/closing-keyword reference found
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —

⚠️ 1 policy check(s) failed. Please address the issues above before this PR can be Reviewed.

🚫 Please fix the failed policies

  • ❌ PR Description

The Not ready to Review label was added to this PR. Once all policies pass, the label is removed automatically.

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

🙋 Wish to Override Policy?

@therock-pr-bot

therock-pr-bot Bot commented Oct 1, 2026

Copy link
Copy Markdown

🚫 Please fix the failed policies before requesting reviews.

The following policy checks failed:

  • ❌ PR Description

The Not ready to Review label has been added to this PR.
Once all policies pass, the label will be removed automatically.

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.

Copilot review overview

🔵 Needs a closer look

Cross-ISA instruction reuse and allocator ownership affect broad execution and asynchronous lifetime semantics requiring final human validation.

Review effort: Balanced
Findings: None

What changed in this PR

Adds bounded decoded-instruction reuse to reduce repeated decode overhead while preserving fetch validation and execution ownership.

Changes:

  • Introduces a 256-entry per-CU decoded instruction cache.
  • Integrates cache reuse with debugging, async execution, and heap allocation.
  • Documents and tests safe instruction re-execution and allocator lifetimes.
File Description
emulation/​rocjitsu/​tests/​instruction_cache_test.cpp Adds cache correctness and lifetime tests.
emulation/​rocjitsu/​lib/​rocjitsu/​src/​rocjitsu/​vm/​amdgpu/​decoded_instruction_cache.h Implements the decoded instruction cache.
emulation/​rocjitsu/​lib/​rocjitsu/​src/​rocjitsu/​vm/​amdgpu/​compute_unit.h Adds per-CU cache ownership and reset behavior.
emulation/​rocjitsu/​lib/​rocjitsu/​src/​rocjitsu/​vm/​amdgpu/​compute_unit.cpp Uses cached decodes in the issue loop.
emulation/​rocjitsu/​lib/​rocjitsu/​src/​rocjitsu/​vm/​amdgpu/​async_scoreboard.h Updates async lifetime documentation.
emulation/​rocjitsu/​lib/​rocjitsu/​src/​rocjitsu/​isa/​instruction.h Documents reusable-instruction and allocation contracts.
emulation/​rocjitsu/​lib/​rocjitsu/​src/​rocjitsu/​isa/​decoder.h Clarifies decoder-pool lifetime requirements.
emulation/​rocjitsu/​lib/​python/​amdisa/​codegen/​_generator.py Documents generated executor reuse requirements.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@amd-eochoalo amd-eochoalo 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.

Ran peanut review with two reviewers (irene and felix). Looks good to them!

@kuhar
kuhar merged commit e226141 into develop Oct 1, 2026
149 of 159 checks passed
@kuhar
kuhar deleted the users/kuhar/mt19937-simulation-performance branch October 1, 2026 17:50

@sjain-stanford sjain-stanford left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you extend the performance evidence to cover the allocation change, following emulation/rocjitsu/docs/benchmarking.md?

The three reported MT19937 pairs do not balance run order under that protocol. Please use an even number of alternating pairs and include fresh-process peak RSS plus cycles:u/instructions:u where available, stating any measurement limitation.

The cache retains up to 256 heap instructions per active CU, while each decoder still embeds its 64 KiB arena. A representative large-topology run would establish the actual footprint; object-size estimates alone do not establish RSS.

Please also include a memory-heavy or low-reuse workload at one engine/dispatch thread, plus a representative short-kernel or suite aggregate. When construction and execution share a thread, the old path could use the decoder pool; memory instructions now allocate on the heap on every issue and cannot benefit from retention. The 32-thread MT19937 timing and unchanged IREE/Gluon outcomes do not measure that tradeoff. This requests qualification of a possible regression, rather than asserting one.

We should update the ownership and reuse documentation together so it describes the new issue path consistently. Paths below are relative to emulation/rocjitsu/:

  • docs/mma-admission.md:46 still says ordinary execution decodes every issued instruction. Explain that eligible completed instructions can now be reused, while admission plans remain a separate cache of byte snapshots and decisions. A short addition at docs/vm-design.md:141 can connect this to the authoritative fetch checks, PC/VMID/word key, debugger bypass, and retention exclusions.
  • lib/rocjitsu/src/rocjitsu/vm/amdgpu/async_scoreboard.h:54, matrix_coexecution.h:65 in the same directory, and docs/async-instructions.md:80 still tie destruction to the decoder allocator's thread. Issued instructions are heap-backed; the issuer owns and retires queued MMA instructions after helper completion.
  • Scope the blanket CU heap claim in lib/rocjitsu/src/rocjitsu/isa/instruction.h:116 and decoder.h:34 to issued instructions. Admission lookahead calls Decoder::decode() directly (mma_admission.h:184), so an ambient pool can still serve its short-lived temporaries, which never escape the scan.
  • Put a reference to the Instruction::execute reuse contract beside the encoding-member/execute_impl emitters in lib/python/amdisa/codegen/_generator.py (for example near line 4464). The new comment at line 1927 is beside the split-backend trampoline, away from where persistent executor state would be introduced.

It would help to pin the reuse contract with a few targeted regressions in emulation/rocjitsu/tests/instruction_cache_test.cpp:

  • Exercise a memory instruction with no DynamicInstState through ScopedReturn, then require a fresh decode. The existing exclusion tests use non-memory instructions, so they do not isolate the is_memory_op() check (decoded_instruction_cache.h:71).
  • Warm the CU cache before replacing its decoder, then require the replacement decoder to run at the same PC. Current replacement tests install the decoder before issue, so they do not exercise the invalidation in compute_unit.h:1060.
  • Keep two waves resident in VectorReadsCurrentExecAcrossDispatches (instruction_cache_test.cpp:479), assert distinct register bases, and step them with different values and EXEC masks. It currently halts each wave before launching the next, allowing first-fit allocation to reuse the same base. The DPP test covers distinct bases through direct execution, but not through the issue loop.

A trimmed, parameterized fresh-decode-versus-reexecution test would also protect the generator contract across ISAs. The supplemental probe found no mismatches across five ISA families and DPP/SDWA variants; making a representative subset permanent would catch future per-execution state accidentally stored in decoded objects. These are coverage gaps, not observed execution failures.


/// @brief Return only completed, stateless instructions.
///
/// @details Construct immediately after decode() with the same key and policy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would be safer if decode() returned an RAII lease capturing the key, reuse policy, and original fetch span, with access to the owned instruction for pipeline handoff.

The CU passes the same arguments correctly today (compute_unit.cpp:1512–1531), but ScopedReturn accepts them independently. In particular, a byte-identical copy of words satisfies the documented “same key” requirement while defeating borrows_fetch_buffer(): a VOPD instruction still points into the original decode buffer, which the copied span does not cover. A mismatched PC or policy can likewise return an object under the wrong key.

Capturing these inputs once would preserve the existing move/release and failed-execution paths without leaving this lifetime contract to every caller. This is an API safety suggestion; the current CU call site uses the correct span.

@systems-assistant

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

yashjha123 added a commit that referenced this pull request Nov 7, 2026
…ache (#14801)

A quick win: VOPD instructions were never reused by the CU's
decoded-instruction cache. Their constructor pointed raw_encoding_ at
the caller's fetch words, and the cache does not keep instructions that
borrow the fetch buffer, a deliberate exclusion from #12632. So every
VOPD issue on RDNA3, RDNA3.5, RDNA4 and gfx1250 paid a full decode plus
a heap allocation.

This PR copies the encoding into the instruction (raw_words_ + memcpy),
as other generated encodings already do, which lifts that exclusion.

| Workload | Target | Cache hit before → after | VOPD share | Region
before → after | Speedup |
|---|---|---|---:|---:|---:|
| flaggems amax-fp32-65536x4096 | gfx1250 | — | 45% | 11.38 → 8.20 s |
**1.387x** |
| torch batch-norm-train-128x256x4096 | gfx1250 | 73.0% → 85.0% | 12% |
29.96 → 28.51 s | 1.051x |
| torch cumsum-fp32-8192x4096 | gfx1250 | 96.1% → 98.2% | 2.2% | 12.08 →
11.64 s | 1.038x |
| rocprim merge-sort-u32-4m | gfx1201 | 95.1% → 95.8% | 0.65% | 22.67 →
21.86 s | 1.037x |
| torch batch-norm-train (control, no VOPD) | gfx950 | 85.5% → 85.5% |
0% | 9.25 → 9.29 s | 0.996x |

<details><summary>Full details for agents and humans</summary>

#### Cause and fix

| | Before | After |
|---|---|---|
| `Vopd::raw_encoding_` | points at the caller's fetch words | points at
`raw_words_` inside the instruction |
| Decoded-cache return | rejected (`borrows_fetch_buffer`) | kept, like
any other ALU instruction |
| Cost per VOPD issue | decode + heap allocation + free | cache hit |

The change is in the generator template (`_generator.py`). The
regenerated output touches only the eight `vopd.{h,cpp}` files for
rdna3, rdna3_5, rdna4 and cdna5. The copy happens once `size_` is known:
8 bytes, or 12 with a literal or in the VOPD3 form.

#### Tests

*Changed.* `BorrowedVopdEncodingIsNotRetainedAcrossIssues` asserted the
old borrowing behaviour, so it now covers two things separately:
- `BorrowedEncodingIsNotRetainedAcrossIssues` keeps the cache's
borrowed-encoding guard covered with a test-only decoder whose
instruction borrows its words. This is the same pattern as
`decoder_window_test.cpp`.
- `VopdOwnsItsEncodingAndIsReusedAcrossIssues` checks all four VOPD
targets, in the 8-byte form and the 12-byte literal form. The encoding
is owned, the second issue reuses the decode (one decoder call), and the
encoding is intact after the first fetch buffer is cleared.

*Mutation check.* Restoring `raw_encoding_ = words` in the RDNA4 output
alone fails the new test for RDNA4 only, in both forms.

*Suite.* Full `ctest` passes. On the test host, `hsa_init_test` doesn't
link against its older system HSA runtime, and that is unrelated to this
change. `pre-commit` is clean.

#### Performance

*Method.* Region time from the workload's own markers. Each run is a
fresh process. There is one warm-up run per build, then 5 paired rounds
in alternating order. Each run has a thread budget of 1 and is pinned to
4 cores / 8 threads of an otherwise idle Ryzen 9950X3D2. Base is
`9f686546da7`. Base run-to-run variation is 0.1–1.0% per workload.

*Where it applies.* VOPD only exists on RDNA3, RDNA3.5, RDNA4 and
gfx1250, so other targets are unaffected (see the gfx950 control). The
gain scales with the workload's VOPD share. Reductions, argmax/amax,
RNG, sort and norm kernels on gfx1250 use VOPD the most.

#### Review focus

1. Is a copy (`raw_words_`) preferable to making the cache accept
borrowers by rebinding `raw_encoding_` on return?
2. `raw_words_` duplicates `word0_`..`word2_`. Should `word*_` read from
`raw_words_` instead, or is the small duplication fine in generated
code?

</details>
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