Skip to content

[Perf] Cache adstack-sizer metadata per task across SPIR-V + LLVM-GPU; per-snode / DeviceAllocation invalidation (Part 2/2)#620

Merged
duburcqa merged 29 commits into
mainfrom
duburcqa/perf_adstack_metadata_cache
May 5, 2026
Merged

[Perf] Cache adstack-sizer metadata per task across SPIR-V + LLVM-GPU; per-snode / DeviceAllocation invalidation (Part 2/2)#620
duburcqa merged 29 commits into
mainfrom
duburcqa/perf_adstack_metadata_cache

Conversation

@duburcqa

@duburcqa duburcqa commented May 4, 2026

Copy link
Copy Markdown
Contributor

Cache adstack-sizer metadata per task across SPIR-V + LLVM-GPU; per-snode / DeviceAllocation invalidation

Part 2 of 2 in the adstack-perf series. Builds on the generic launcher infrastructure landed in Part 1 (duburcqa/perf_persistent_context_jit_pointer, #619). This PR adds the per-task metadata cache that turns publish_adstack_metadata's sizer-kernel + DtoH-stride round-trip into a host-only republish on cache hit. The launcher-side persistent scratch from Part 1 is what makes the republish DMA cheap enough to be worth caching.

TL;DR

Every reverse-mode launch with adstacks dispatches a sizer kernel per task to compute adstack_offsets / adstack_max_sizes / strides from the kernel's compile-time SerializedSizeExpr trees plus the current values of FieldLoad / ExternalTensorRead / ExternalTensorShape leaves. The sizer's output is a deterministic function of (per-task SerializedSizeExpr*, snode write generation per leaf snode, ndarray data generation per leaf ndarray). A long sequence of reverse-mode launches over the same kernel pays the sizer dispatch + 3x DtoH-stride round-trips per launch even though the inputs almost never change between launches.

This PR caches the sizer's output keyed by (AdStackSizingInfo *, dependency-generation snapshot). On cache hit the cached (offsets, max_sizes, stride_combined, stride_float, stride_int) is republished into the runtime struct via the same DMA path the host-eval branch already uses; the bytecode-encode + h2d + sizer-kernel + 3x DtoH stalls are skipped. Each launcher (CPU / CUDA / AMDGPU / SPIR-V) bumps snode_write_gen_ / ndarray_data_gen_ for everything its kernel can mutate before publish, so the snapshot catches any input change between launches and forces a fresh sizer dispatch.

// AdStackCache: lives on Program, owned via unique_ptr.
class AdStackCache {
 public:
  // Per-task entries on each backend's cache key.
  bool try_llvm_per_task_ad_stack_cache_hit(const void *task_key, LaunchContextBuilder *ctx,
                                            LlvmPerTaskAdStackCacheEntry &entry);
  void record_llvm_per_task_ad_stack_cache(const void *task_key, LlvmPerTaskAdStackCacheEntry entry);
  // Same shape for SPIR-V (kept separate so the bytecode + sizer-kernel paths can diverge).
  // ...
  // Generation bumps - called by each launcher on every kernel that may write to the source.
  void bump_snode_write_gen(int snode_id);
  void bump_ndarray_data_gen(const DeviceAllocation &devalloc);
};

The metadata cache machinery (struct definitions, methods, generation maps) was extracted from Program into a standalone AdStackCache class living in quadrants/program/adstack_size_expr_eval.{h,cpp}, with Program holding a std::unique_ptr<AdStackCache> and a single getter. Net effect: the surface area changed in Program is small (forward decl + getter + ctor init); all the cache-table ownership lives in adstack-specific files.

Why

Two costs the sizer dispatch was paying every launch:

  1. Bytecode encode + h2d + sizer-kernel + 3x DtoH stride read-back. On AMDGPU / CUDA this is 4-5 driver hops per task per launch, plus the CPU stalls waiting on the DtoH. Genesis test_differentiable_rigid runs ~30+ adstack-bearing tasks per kernel; over a 100-substep horizon that's thousands of synchronous round-trips per training step.
  2. CPU host-eval also pays a per-launch fixed cost - even though there's no kernel dispatch on CPU, the SizeExpr walk repeatedly reads the same FieldLoad / ExternalTensorRead values across launches whose inputs haven't changed.

The dependency snapshot (per-leaf snode write generation, per-leaf ndarray data generation) catches the actual mutation cases: a kernel that writes to field_a will bump snode_write_gen_[field_a.snode_id] before publish, so any cache entry depending on field_a is forced to re-evaluate. The cache amortises the dispatch + DMA cost to "once per generation bump" rather than "every launch".

Surface API

Zero user-visible change. Same qd.kernel.grad(...) invocation; same gradient values; same per-launch semantics.

Internal API surface (for the launchers):

// In each backend's KernelLauncher::launch_llvm_kernel (or gfx::Runtime::launch_kernel):
bump_writes_for_kernel_llvm(prog, &ctx, offloaded_tasks);   // CUDA / AMDGPU
bump_writes_for_kernel_llvm(prog, &ctx, snode_writes, arr_writes);  // CPU (precomputed at register time)
bump_writes_for_kernel_spirv(prog, &host_ctx, task_attribs, arr_access);  // gfx

Each helper iterates the kernel's compile-time write set (snode writes from codegen + ndarray writes from arg-access metadata) and bumps the matching generation counters on prog->adstack_cache(). The actual cache lookup happens inside publish_adstack_metadata (LLVM-GPU + CPU host-eval) and inside the gfx adstack-sizer-launch helper (SPIR-V).

Mechanism end-to-end

1. AdStackCache class extraction

quadrants/program/adstack_size_expr_eval.{h,cpp} - new class. Holds:

  • SizeExpr host-eval cache: (SerializedSizeExpr *) -> {result, observed_reads}. On hit, replays observed FieldLoad / ExternalTensorRead / ExternalTensorShape reads against the live state and confirms a match before reusing the result.
  • LlvmPerTaskAdStackCacheEntry: {offsets[], max_sizes[], stride_combined, stride_float, stride_int, snode_gen_snapshot, ndarray_gen_snapshot} keyed by AdStackSizingInfo *.
  • SpirvBytecodeCacheEntry + PerTaskAdStackCacheEntry for the gfx backend (kept separate from the LLVM entry so bytecode encode and sizer-launch paths can diverge per backend).
  • Generation maps: snode_write_gen_ (int snode_id -> uint64), ndarray_data_gen_ (DeviceAllocation -> uint64).

Program holds std::unique_ptr<AdStackCache> adstack_cache_; with adstack_cache() getter. The forward declaration + getter is the only Program surface change; cache method bodies and table ownership live in adstack-specific files.

2. bump_writes_for_kernel_llvm / bump_writes_for_kernel_spirv

quadrants/program/adstack_size_expr_eval.{h,cpp}. Called by each launcher just before kernel dispatch. Walks the kernel's compile-time write set:

  • snode writes: from codegen, attached to each OffloadedTask (LLVM-GPU) or TaskAttributes (gfx).
  • ndarray writes: from KernelContextAttributes::arr_access (gfx) or precomputed arg_writes_per_task (CPU launcher).

For each, calls prog->adstack_cache().bump_snode_write_gen(snode_id) / bump_ndarray_data_gen(devalloc). After this, any cache entry whose snapshot diverges from the new generation is invalidated on next lookup.

3. Cache lookup in publish_adstack_metadata

quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp. The GPU branch checks prog->adstack_cache().try_llvm_per_task_ad_stack_cache_hit(...) first; on hit, the cached payload republishes via publish_metadata_pinned_async (the same pinned-host async pipeline the host-eval branch uses) and skips the entire bytecode-encode + h2d + sizer-kernel + DtoH-stride pipeline. On miss, the original sizer dispatch runs and the resulting payload is recorded back into the cache via record_llvm_per_task_ad_stack_cache(...) along with the snapshot.

CPU host-eval has its own cache shape (no bytecode, just SizeExpr walk results); same pattern.

4. SPIR-V (gfx) adstack-sizer-launch helper

quadrants/runtime/gfx/adstack_sizer_launch.cpp - new file holding the SPIR-V sizer-launch path that previously lived inline in gfx/runtime.cpp. Lookup mirrors LLVM-GPU: bytecode cache hit short-circuits the encode + dispatch; per-task metadata cache hit short-circuits the entire sizer-kernel + DtoH pipeline.

5. Per-backend launcher integration

Backend Where bump fires Where cache fires
CPU quadrants/runtime/cpu/kernel_launcher.cpp (host-eval branch of publish_adstack_metadata) host-eval branch in llvm_adstack_lazy_claim.cpp
CUDA / AMDGPU quadrants/runtime/{cuda,amdgpu}/kernel_launcher.cpp (just before launch_offloaded_tasks) GPU branch in llvm_adstack_lazy_claim.cpp
SPIR-V (Metal / Vulkan) quadrants/runtime/gfx/runtime.cpp (just before cmdlist record) quadrants/runtime/gfx/adstack_sizer_launch.cpp

Per-backend coverage matrix

Backend SizeExpr host-eval cache Per-task metadata cache Bytecode cache Generation bump
CPU (host-eval only) (n/a, no bytecode) ✓ (bump_writes_for_kernel_llvm)
CUDA (used by host-eval if reached) ✓ (LLVM-GPU bytecode)
AMDGPU (used by host-eval if reached) ✓ (LLVM-GPU bytecode)
Metal (used by host-eval if reached) ✓ (gfx variant) ✓ (SPIR-V bytecode) ✓ (bump_writes_for_kernel_spirv)
Vulkan (used by host-eval if reached) ✓ (gfx variant) ✓ (SPIR-V bytecode)

Side-effect audit

Concern Verdict
Snode mutation between launches forces fresh sizer dispatch Each launcher bumps snode_write_gen_[snode_id] before launch for every snode the kernel writes; cache snapshot diverges, lookup misses.
Ndarray data mutation between launches forces fresh sizer dispatch Each launcher bumps ndarray_data_gen_[devalloc] before launch; same mechanism.
SizeExprReadObservation replay catches host-eval staleness On hit the cache replays the recorded FieldLoad / ExternalTensorRead reads and only short-circuits if every value matches; mismatch erases the entry and falls through to a full eval.
AdStackCache lifetime tied to Program Owned by std::unique_ptr<AdStackCache> member; created in Program ctor, destroyed with the program. Forward decl in program.h keeps the include surface narrow.
Cross-backend cache-entry types do not collide Distinct entry types per backend (LlvmPerTaskAdStackCacheEntry vs PerTaskAdStackCacheEntry vs SpirvBytecodeCacheEntry); Program does not see any of them.
Cache lookup happens after every snode/ndarray bump in the same launch Bump calls fire before launch_offloaded_tasks; lookup fires inside publish_adstack_metadata (called from inside the offloaded-task loop). Order is bump-then-lookup so the snapshot read for hit-checking sees the post-bump generation.
Persistent device scratch from Part 1 still safe Per-handle isolation (Part 1) means recursive snode_reader_* launches via host-eval FieldLoad cannot clobber the parent kernel's arg_buffer / RuntimeContext even when the cache miss path runs the sizer kernel that itself dereferences ctx->arg_buffer.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42e8ae81fd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread quadrants/program/adstack_size_expr_eval.cpp
Comment thread quadrants/program/adstack_size_expr_eval.h
@duburcqa

duburcqa commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from cddb61d to 02dcf40 Compare May 4, 2026 14:37
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Coverage Report (17a5873bd)

File Coverage Missing

Diff coverage: 0% · Overall: 67% · 0 lines, 0 missing

Full annotated report

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Coverage Report (02dcf40aa)

File Coverage Missing
🟢 tests/python/test_adstack.py 100%

Diff coverage: 100% · Overall: 74% · 66 lines, 0 missing

Full annotated report

@duburcqa duburcqa force-pushed the duburcqa/perf_persistent_context_jit_pointer branch from 0461c00 to 7db79ca Compare May 4, 2026 16:16
@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from 02dcf40 to 230a98e Compare May 4, 2026 16:34
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Coverage Report (230a98ee8)

File Coverage Missing
🟢 tests/python/test_adstack.py 100%

Diff coverage: 100% · Overall: 74% · 66 lines, 0 missing

Full annotated report

Base automatically changed from duburcqa/perf_persistent_context_jit_pointer to main May 4, 2026 20:41
@duburcqa duburcqa marked this pull request as draft May 4, 2026 21:20
@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from 8bfb057 to f6a8ddd Compare May 5, 2026 05:15
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

PR change report (f6a8ddd8c)

Code lines (excluding blank lines, comment-only lines, and Python multi-line strings).

File LoC Added Removed
quadrants/program/adstack_size_expr_eval.cpp 1168 +467 -49
quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp 882 +255 -190
quadrants/program/adstack_size_expr_eval.h 155 +131 -1
quadrants/runtime/llvm/llvm_runtime_executor.cpp 684 +86
quadrants/runtime/gfx/adstack_sizer_launch.cpp 328 +74
tests/python/test_adstack.py 2580 +73 -1
quadrants/codegen/llvm/codegen_llvm.cpp 2600 +69 -1
quadrants/program/program.cpp 465 +68 -5
quadrants/codegen/spirv/spirv_codegen.cpp 2479 +57 -22
tests/cpp/program/test_diagnose_adstack_overflow.cpp 48 +48
quadrants/runtime/cpu/kernel_launcher.cpp 138 +36 -24
quadrants/runtime/llvm/runtime_module/runtime.cpp 2010 +25 -7
quadrants/program/program.h 218 +22 -21
quadrants/runtime/gfx/runtime.cpp 907 +20 -6
quadrants/runtime/llvm/runtime_module/internal_functions.h 172 +13 -10
quadrants/runtime/llvm/llvm_runtime_executor.h 161 +8 -1
quadrants/runtime/program_impls/llvm/llvm_program.h 157 +5
quadrants/codegen/llvm/llvm_compiled_data.h 93 +4 -1
quadrants/program/snode_rw_accessors_bank.cpp 88 +4
quadrants/codegen/spirv/kernel_utils.h 211 +3 -1
quadrants/codegen/spirv/detail/spirv_codegen.h 208 +3
quadrants/program/ndarray.cpp 174 +3
quadrants/runtime/amdgpu/kernel_launcher.cpp 257 +3
quadrants/runtime/cpu/kernel_launcher.h 35 +3
quadrants/runtime/cuda/kernel_launcher.cpp 269 +3
quadrants/program/program_impl.h 118 +2

Total: 26 file(s) changed, +1485 -340 code lines.

Full per-function report

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

Coverage Report (f6a8ddd8c)

File Coverage Missing
🟢 tests/python/test_adstack.py 100%

Diff coverage: 100% · Overall: 74% · 68 lines, 0 missing

Full annotated report

Comment thread docs/source/user_guide/autodiff.md Outdated
Comment thread docs/source/user_guide/autodiff.md Outdated
Comment thread docs/source/user_guide/autodiff.md Outdated
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

PR change report (cf0c297ce)

Code lines (excluding blank lines, comment-only lines, and Python multi-line strings).

File LoC Added Removed
quadrants/program/adstack_size_expr_eval.cpp 1168 +467 -49
quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp 885 +258 -190
quadrants/program/program.cpp 565 +168 -5
quadrants/program/adstack_size_expr_eval.h 155 +131 -1
tests/python/test_adstack.py 2603 +96 -1
quadrants/runtime/llvm/llvm_runtime_executor.cpp 684 +86
quadrants/runtime/gfx/adstack_sizer_launch.cpp 328 +74
quadrants/codegen/llvm/codegen_llvm.cpp 2600 +69 -1
quadrants/codegen/spirv/spirv_codegen.cpp 2479 +57 -22
tests/cpp/program/test_diagnose_adstack_overflow.cpp 48 +48
quadrants/runtime/cpu/kernel_launcher.cpp 138 +36 -24
quadrants/program/program.h 222 +26 -21
quadrants/runtime/llvm/runtime_module/runtime.cpp 2010 +25 -7
quadrants/runtime/gfx/runtime.cpp 902 +21 -12
quadrants/runtime/llvm/runtime_module/internal_functions.h 172 +13 -10
quadrants/runtime/llvm/llvm_runtime_executor.h 161 +8 -1
quadrants/runtime/program_impls/llvm/llvm_program.h 157 +5
quadrants/codegen/llvm/llvm_compiled_data.h 93 +4 -1
quadrants/codegen/spirv/kernel_utils.h 212 +4 -1
quadrants/program/ndarray.cpp 175 +4
quadrants/program/snode_rw_accessors_bank.cpp 88 +4
quadrants/codegen/spirv/detail/spirv_codegen.h 208 +3
quadrants/runtime/amdgpu/kernel_launcher.cpp 257 +3
quadrants/runtime/cpu/kernel_launcher.h 35 +3
quadrants/runtime/cuda/kernel_launcher.cpp 269 +3
quadrants/program/program_impl.h 118 +2

Total: 26 file(s) changed, +1618 -346 code lines.

Full per-function report

@duburcqa duburcqa marked this pull request as ready for review May 5, 2026 07:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 114f817b3e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread quadrants/program/adstack_size_expr_eval.cpp Outdated
Comment thread quadrants/runtime/llvm/runtime_module/runtime.cpp
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

PR change report (114f817b3)

Code lines (excluding blank lines, comment-only lines, and Python multi-line strings).

File LoC Added Removed
quadrants/program/adstack_size_expr_eval.cpp 1168 +467 -49
quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp 893 +265 -189
quadrants/program/program.cpp 559 +162 -5
quadrants/program/adstack_size_expr_eval.h 161 +137 -1
tests/python/test_adstack.py 2634 +127 -1
quadrants/runtime/gfx/adstack_sizer_launch.cpp 370 +116
quadrants/runtime/llvm/llvm_runtime_executor.cpp 684 +86
tests/cpp/program/test_diagnose_adstack_overflow.cpp 73 +73
quadrants/codegen/llvm/codegen_llvm.cpp 2602 +71 -1
quadrants/codegen/spirv/spirv_codegen.cpp 2492 +70 -22
quadrants/runtime/cpu/kernel_launcher.cpp 138 +36 -24
quadrants/runtime/gfx/runtime.cpp 915 +34 -12
quadrants/program/program.h 228 +31 -20
quadrants/runtime/llvm/runtime_module/runtime.cpp 2010 +25 -7
quadrants/runtime/llvm/runtime_module/internal_functions.h 172 +13 -10
quadrants/runtime/llvm/llvm_runtime_executor.h 161 +8 -1
quadrants/codegen/spirv/kernel_utils.h 213 +5 -1
quadrants/runtime/program_impls/llvm/llvm_program.h 157 +5
quadrants/codegen/llvm/llvm_compiled_data.h 93 +4 -1
quadrants/codegen/spirv/detail/spirv_codegen.h 209 +4
quadrants/program/ndarray.cpp 175 +4
quadrants/program/snode_rw_accessors_bank.cpp 88 +4
quadrants/codegen/spirv/kernel_utils.cpp 109 +3
quadrants/runtime/amdgpu/kernel_launcher.cpp 257 +3
quadrants/runtime/cpu/kernel_launcher.h 35 +3
quadrants/runtime/cuda/kernel_launcher.cpp 269 +3
quadrants/program/program_impl.h 118 +2
quadrants/runtime/gfx/runtime.h 151 +2

Total: 28 file(s) changed, +1763 -344 code lines.

Full per-function report

@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from 114f817 to 75a2bb8 Compare May 5, 2026 08:34
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

Coverage Report (114f817b3)

File Coverage Missing
🟢 tests/python/test_adstack.py 89% 1243-1247,1265,1279,1301-1306

Diff coverage: 89% · Overall: 74% · 122 lines, 13 missing

Full annotated report

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

PR change report (9320e5f12)

Code lines (excluding blank lines, comment-only lines, and Python multi-line strings).

File LoC Added Removed
quadrants/program/adstack_size_expr_eval.cpp 1195 +494 -49
quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp 898 +269 -188
quadrants/program/program.cpp 559 +162 -5
tests/python/test_adstack.py 2651 +147 -4
quadrants/program/adstack_size_expr_eval.h 160 +136 -1
quadrants/runtime/gfx/adstack_sizer_launch.cpp 370 +116
quadrants/runtime/llvm/llvm_runtime_executor.cpp 684 +86
quadrants/codegen/llvm/codegen_llvm.cpp 2609 +78 -1
tests/cpp/program/test_diagnose_adstack_overflow.cpp 73 +73
quadrants/codegen/spirv/spirv_codegen.cpp 2492 +70 -22
quadrants/runtime/cpu/kernel_launcher.cpp 142 +40 -24
quadrants/runtime/gfx/runtime.cpp 917 +36 -12
quadrants/program/program.h 228 +31 -20
quadrants/runtime/llvm/runtime_module/runtime.cpp 2011 +26 -7
quadrants/runtime/llvm/runtime_module/internal_functions.h 172 +15 -12
python/quadrants/lang/impl.py 942 +8 -1
quadrants/runtime/llvm/llvm_runtime_executor.h 161 +8 -1
quadrants/codegen/llvm/llvm_compiled_data.h 94 +5 -1
quadrants/codegen/spirv/kernel_utils.h 213 +5 -1
quadrants/runtime/program_impls/llvm/llvm_program.h 157 +5
quadrants/codegen/spirv/detail/spirv_codegen.h 209 +4
quadrants/program/ndarray.cpp 175 +4
quadrants/program/snode_rw_accessors_bank.cpp 88 +4
quadrants/runtime/cpu/kernel_launcher.h 36 +4
quadrants/codegen/spirv/kernel_utils.cpp 109 +3
quadrants/runtime/amdgpu/kernel_launcher.cpp 257 +3
quadrants/runtime/cuda/kernel_launcher.cpp 269 +3
quadrants/program/program_impl.h 118 +2
quadrants/runtime/gfx/runtime.h 151 +2

Total: 29 file(s) changed, +1839 -349 code lines.

Full per-function report

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

Coverage Report (9320e5f12)

File Coverage Missing
🔴 python/quadrants/lang/impl.py 29% 582-586
🟢 tests/python/test_adstack.py 95% 1244-1248,1291,1305

Diff coverage: 92% · Overall: 72% · 149 lines, 12 missing

Full annotated report

@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from 8451301 to e7de926 Compare May 5, 2026 13:21
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

PR change report (e7de926c7)

Code lines (excluding blank lines, comment-only lines, and Python multi-line strings).

File LoC Added Removed
quadrants/program/adstack_size_expr_eval.cpp 1625 +924 -49
quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp 899 +270 -188
quadrants/program/adstack_size_expr_eval.h 200 +176 -1
tests/python/test_adstack.py 2651 +147 -4
quadrants/runtime/gfx/adstack_sizer_launch.cpp 370 +116
quadrants/runtime/llvm/llvm_runtime_executor.cpp 684 +86
quadrants/codegen/llvm/codegen_llvm.cpp 2610 +79 -1
tests/cpp/program/test_diagnose_adstack_overflow.cpp 79 +79
quadrants/codegen/spirv/spirv_codegen.cpp 2492 +70 -22
quadrants/runtime/cpu/kernel_launcher.cpp 142 +40 -24
quadrants/runtime/gfx/runtime.cpp 917 +36 -12
quadrants/runtime/llvm/runtime_module/runtime.cpp 2011 +26 -7
quadrants/runtime/llvm/runtime_module/internal_functions.h 172 +15 -12
quadrants/program/program.cpp 409 +12 -5
quadrants/program/program.h 205 +9 -21
python/quadrants/lang/impl.py 942 +8 -1
quadrants/runtime/llvm/llvm_runtime_executor.h 161 +8 -1
quadrants/codegen/llvm/llvm_compiled_data.h 94 +5 -1
quadrants/codegen/spirv/kernel_utils.h 213 +5 -1
quadrants/program/launch_context_builder.cpp 389 +5
quadrants/runtime/program_impls/llvm/llvm_program.h 157 +5
quadrants/codegen/spirv/detail/spirv_codegen.h 209 +4
quadrants/program/ndarray.cpp 175 +4
quadrants/program/snode_rw_accessors_bank.cpp 88 +4
quadrants/runtime/cpu/kernel_launcher.h 36 +4
quadrants/codegen/spirv/kernel_utils.cpp 109 +3
quadrants/runtime/amdgpu/kernel_launcher.cpp 257 +3
quadrants/runtime/cuda/kernel_launcher.cpp 269 +3
quadrants/program/program_impl.h 118 +2
quadrants/runtime/gfx/runtime.h 151 +2
quadrants/program/launch_context_builder.h 98 +1

Total: 31 file(s) changed, +2151 -350 code lines.

Full per-function report

@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from e7de926 to d987e9f Compare May 5, 2026 14:08
Comment thread docs/source/user_guide/autodiff.md Outdated
duburcqa added 7 commits May 5, 2026 16:32
…lean QuadrantsAssertionError on SPIR-V; per-task-only invalidation
…::map; show real required sizes instead of [?]
…s + use find() over operator[] in arr_reads kNone bump
@duburcqa duburcqa force-pushed the duburcqa/perf_adstack_metadata_cache branch from 5c41503 to 34e7006 Compare May 5, 2026 14:32
Comment thread docs/source/user_guide/debug.md Outdated
#### Adstack overflow

`debug=True` also enables a deferred bounds check on the adstack used by reverse-mode autodiff. A push past the per-stack capacity (set via `qd.init(ad_stack_size=...)` or per-alloca by `determine_ad_stack_size`) raises `RuntimeError("[Aa]dstack overflow")` on the next `qd.sync()`. Without the check, an adstack overflow silently writes past the per-thread slab and produces a wrong gradient.
The adstack overflow check on reverse-mode autodiff runs always, on every backend, regardless of `debug`. A push past the per-stack capacity (set via `qd.init(ad_stack_size=...)` or per-alloca by `determine_ad_stack_size`) raises `QuadrantsAssertionError("[Aa]dstack overflow")` at the next Quadrants Python entry - kernel launch, host-side field / ndarray read, or explicit `qd.sync()`. The error message describes the two possible causes (untracked tensor mutation between launches, or sizer under-estimate) and the recovery flow; see [Autodiff -> What can go wrong](autodiff.md) for the full description.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it guaranteed to be the next? I feel it is more likely to be the next launch or so on that happens after the error has occurred, (and not the next launch after the launch that led to a kernel erroring)?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

eg, we launhc:

  • A
  • B
  • C
  • at this point, A finishes running, and it had an error
  • D
    • receives the exception/error

?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(I suppose it depends on our definition of 'next')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only on CPU. Updated to be more accurate.

Comment thread docs/source/user_guide/autodiff.md Outdated
- split the inner section into its own `@qd.kernel`;
- pass `ad_stack_size=N` to `qd.init()` with `N` large enough to cover the real push count (bypasses the sizer).
- **Adstack overflow.** Surfaces as `QuadrantsAssertionError: Adstack overflow ...` at the next Quadrants Python entry. The message names the offending kernel + offload task and the most likely cause:
- *Untracked tensor mutation between launches.* A tensor backing a data-dependent loop bound was written to outside Quadrants's tracking - typically a DLPack zero-copy mutation through a torch tensor sharing storage with a Quadrants ndarray, or a raw pointer write through a non-torch consumer. The cached adstack capacity was sized against the value before the mutation; if the mutation grew the bound, the next launch overflows. Fix: route the write through a Quadrants API (`Ndarray.write` / `Ndarray.fill` / a kernel that writes the value). Alternatively, catch the exception and re-launch - Quadrants invalidates the cached bound on raise so the retry runs against the live state. Kernel state may be inconsistent after an overflow; do not retry the same step without addressing the cause and restarting from a clean state.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice explanation I feel

Comment thread docs/source/user_guide/autodiff.md Outdated
- pass `ad_stack_size=N` to `qd.init()` with `N` large enough to cover the real push count (bypasses the sizer).
- **Adstack overflow.** Surfaces as `QuadrantsAssertionError: Adstack overflow ...` at the next Quadrants Python entry. The message names the offending kernel + offload task and the most likely cause:
- *Untracked tensor mutation between launches.* A tensor backing a data-dependent loop bound was written to outside Quadrants's tracking - typically a DLPack zero-copy mutation through a torch tensor sharing storage with a Quadrants ndarray, or a raw pointer write through a non-torch consumer. The cached adstack capacity was sized against the value before the mutation; if the mutation grew the bound, the next launch overflows. Fix: route the write through a Quadrants API (`Ndarray.write` / `Ndarray.fill` / a kernel that writes the value). Alternatively, catch the exception and re-launch - Quadrants invalidates the cached bound on raise so the retry runs against the live state. Kernel state may be inconsistent after an overflow; do not retry the same step without addressing the cause and restarting from a clean state.
- *Sizer under-estimated the bound (Quadrants bug).* On unusually intricate nested loops - typically deeply nested `for i in range(arr[...])` with cumulative-index arithmetic - the sizer can compute a bound that is mathematically tighter than the actual push count. File a bug with `QD_DUMP_IR=1` set so the kernel IR ships with the report. Workaround: pass a generous `ad_stack_size=N` to `qd.init()` with `N` large enough to cover the real push count (bypasses the sizer).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"File a bug with QD_DUMP_IR=1 set". Whilst I can undersatnd what you mean, this seems to be a confusing way to state this? Maybe omsething like:

"File a bug report as follows, as an issue against the Quadrants repo:

  • rm /tmp/ir/*
  • run your job with the env var QD_DUMP_IR=1 defined
  • zip the contents of /tmp/ir
  • create an issue against the Quadrants repo
  • attach the zip of /tmp/ir to the issue"

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

Comment thread docs/source/user_guide/autodiff.md Outdated
- *Sizer under-estimated the bound (Quadrants bug).* On unusually intricate nested loops - typically deeply nested `for i in range(arr[...])` with cumulative-index arithmetic - the sizer can compute a bound that is mathematically tighter than the actual push count. File a bug with `QD_DUMP_IR=1` set so the kernel IR ships with the report. Workaround: pass a generous `ad_stack_size=N` to `qd.init()` with `N` large enough to cover the real push count (bypasses the sizer).
- **Out-of-memory before the kernel even runs.** A reverse pass through many loop-carried variables at a large ndrange can ask the runtime for more adstack memory than the device can physically back, even when the sizer's number is correct. Surfaces as an allocator OOM at launch time. Remedies are the ones listed under *Avoiding OOM on GPU* above: fewer loop-carried variables, a smaller ndrange, manual checkpointing, or more device-memory headroom.
- **Loop bounds backed by a mutated ndarray.** A reverse-mode kernel with `for i in range(n[j])` requires `n[j]` to hold the same value at the forward call and at `.grad()`. If anything writes to `n[j]` between those two points - the differentiable kernel itself, or any other kernel call - the computed gradient may come out wrong, sometimes as an `Adstack overflow` exception at `qd.sync()`, sometimes silently. The safe rule: populate loop-bound ndarrays before the forward call and leave them untouched until `.grad()` returns. The reason for that is Quadrants' adstack sizer design: it reads the loop bound separately at each dispatch, which includes forward and backward calls. Tape-based eager AD like [PyTorch's autograd](https://pytorch.org/docs/stable/notes/autograd.html) is not affected, since the trip count is recorded as the forward runs and reused at backward time.
- **Loop bounds backed by a mutated ndarray.** A reverse-mode kernel with `for i in range(n[j])` requires `n[j]` to hold the same value at the forward call and at `.grad()`. If anything writes to `n[j]` between those two points - the differentiable kernel itself, or any other kernel call - the computed gradient may come out silently wrong. The safe rule: populate loop-bound ndarrays before the forward call and leave them untouched until `.grad()` returns. The reason for that is Quadrants' adstack sizer design: it reads the loop bound separately at each dispatch, which includes forward and backward calls. Tape-based eager AD like [PyTorch's autograd](https://pytorch.org/docs/stable/notes/autograd.html) is not affected, since the trip count is recorded as the forward runs and reused at backward time.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

@hughperkins

Copy link
Copy Markdown
Collaborator
  • left some comments on the doc
  • waiting also for the agent-based checks to run
  • this touches a ton of different files, so lets have a full Genesis unit test run, and benchmarks, pelase

- pass `ad_stack_size=N` to `qd.init()` with `N` large enough to cover the real push count (bypasses the sizer).
- **Adstack overflow.** Surfaces as `QuadrantsAssertionError: Adstack overflow ...` at the next Quadrants Python entry. The message names the offending kernel + offload task and the most likely cause:
- *Untracked tensor mutation between launches.* A tensor backing a data-dependent loop bound was written to outside Quadrants's tracking - typically a DLPack zero-copy mutation through a torch tensor sharing storage with a Quadrants ndarray, or a raw pointer write through a non-torch consumer. The cached adstack capacity was sized against the value before the mutation; if the mutation grew the bound, the next launch overflows. Fix: route the write through a Quadrants API (`Ndarray.write` / `Ndarray.fill` / a kernel that writes the value). Alternatively, catch the exception and re-launch - Quadrants invalidates the cached bound on raise, so the retry runs against the live state. Kernel state may be inconsistent after an overflow; do not retry the same step without restarting from a clean state.
- *Sizer under-estimated the bound (Quadrants bug).* On unusually intricate nested loops - typically deeply nested `for i in range(arr[...])` with cumulative-index arithmetic - the sizer can compute a bound that is mathematically tighter than the actual push count. To file a bug: clear `/tmp/ir/`, rerun your script with `QD_DUMP_IR=1` set in the environment so Quadrants dumps the kernel IR there, then open an issue on the Quadrants repo with the contents of `/tmp/ir/` attached as a zip. Workaround: pass a generous `ad_stack_size=N` to `qd.init()` with `N` large enough to cover the real push count (bypasses the sizer).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Description of how to file the issue seems clear, complete and concise. Great! 🙌

@hughperkins

Copy link
Copy Markdown
Collaborator

doc looks good to me 🙌

I'm waiting now on:

  • agent-based checks to run
  • results of full Genesis unit test run (at least on field)
  • results of Genesis benchmarks run (at least on field)

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

PR change report (11577f99e)

Code lines (excluding blank lines, comment-only lines, and Python multi-line strings).

File LoC Added Removed
quadrants/program/adstack_size_expr_eval.cpp 1637 +936 -49
quadrants/runtime/llvm/llvm_adstack_lazy_claim.cpp 895 +266 -188
quadrants/program/adstack_size_expr_eval.h 205 +181 -1
tests/python/test_adstack.py 2680 +147 -4
quadrants/runtime/gfx/adstack_sizer_launch.cpp 373 +119
quadrants/runtime/llvm/llvm_runtime_executor.cpp 684 +86
quadrants/codegen/llvm/codegen_llvm.cpp 2610 +79 -1
tests/cpp/program/test_diagnose_adstack_overflow.cpp 79 +79
quadrants/codegen/spirv/spirv_codegen.cpp 2492 +70 -22
quadrants/runtime/cpu/kernel_launcher.cpp 142 +40 -24
quadrants/runtime/gfx/runtime.cpp 917 +36 -12
quadrants/program/program.cpp 424 +27 -5
quadrants/runtime/llvm/runtime_module/runtime.cpp 2011 +26 -7
quadrants/runtime/llvm/runtime_module/internal_functions.h 172 +15 -12
quadrants/program/program.h 205 +9 -21
python/quadrants/lang/impl.py 942 +8 -1
quadrants/runtime/llvm/llvm_runtime_executor.h 161 +8 -1
quadrants/codegen/llvm/llvm_compiled_data.h 94 +5 -1
quadrants/codegen/spirv/kernel_utils.h 213 +5 -1
quadrants/program/launch_context_builder.cpp 389 +5
quadrants/runtime/program_impls/llvm/llvm_program.h 157 +5
quadrants/codegen/spirv/detail/spirv_codegen.h 209 +4
quadrants/program/ndarray.cpp 175 +4
quadrants/program/snode_rw_accessors_bank.cpp 88 +4
quadrants/runtime/cpu/kernel_launcher.h 36 +4
quadrants/codegen/spirv/kernel_utils.cpp 109 +3
quadrants/runtime/amdgpu/kernel_launcher.cpp 257 +3
quadrants/runtime/cuda/kernel_launcher.cpp 269 +3
quadrants/program/program_impl.h 118 +2
quadrants/runtime/gfx/runtime.h 151 +2
quadrants/program/launch_context_builder.h 98 +1

Total: 31 file(s) changed, +2182 -350 code lines.

Full per-function report

@duburcqa

duburcqa commented May 5, 2026

Copy link
Copy Markdown
Contributor Author
env batch_size backend gjk_collision constraint_solver runtime_fps_main runtime_fps_620 runtime_fps_delta_pct
anymal_random 30000 cuda - - 9443191 9390292 -0.56
anymal_uniform 30000 cuda - - 12346008 12387632 +0.34
anymal_uniform_kinematic 0 cpu - - 1982 1948 -1.72
anymal_uniform_kinematic 30000 cuda - - 10631243 10352282 -2.62
anymal_zero 0 cpu - - 7267 7139 -1.76
anymal_zero 30000 cuda - - 19146598 19301149 +0.81
box_pyramid_3 4096 cuda - - 979160 988222 +0.93
box_pyramid_4 4096 cuda - - 384826 387764 +0.76
box_pyramid_5 4096 cuda - - 141026 137764 -2.31
box_pyramid_6 4096 cuda False - 58596 58888 +0.50
box_pyramid_6 4096 cuda True - 60147 59231 -1.52
dex_hand 4096 cuda - - 17240 17173 -0.39
duck_in_box_easy 30000 cuda False - 27101190 26767881 -1.23
duck_in_box_easy 30000 cuda True - 9526647 9501811 -0.26
duck_in_box_hard 0 cpu - - 5079 5121 +0.83
duck_in_box_hard 30000 cuda False - 10245189 10305593 +0.59
duck_in_box_hard 30000 cuda True - 3386673 3416781 +0.89
franka 30000 cuda - - 22146368 21472201 -3.04
franka_accessors 0 cpu - - 1136 1193 +5.02
franka_accessors 30000 cuda - - 15354421 15706391 +2.29
franka_free 30000 cuda - - 32056108 32810552 +2.35
franka_random 0 cpu - - 6249 6271 +0.35
franka_random 30000 cuda - CG 16548461 16839003 +1.76
franka_random 30000 cuda - Newton 16720811 16688250 -0.19
franka_random 30000 cuda False - 16509323 16401148 -0.66
franka_random 30000 cuda True - 11570179 11542843 -0.24
g1_fall 4096 cuda - Newton 907003 922819 +1.74
go2 4096 cuda False CG 3646422 3725625 +2.17
go2 4096 cuda False Newton 4624845 4583004 -0.90
go2 4096 cuda True - 3330016 3318628 -0.34
shadow_hand_cubes 0 cpu - - 41 41 +0.00
shadow_hand_cubes_sparse 0 cpu - - 65 66 +1.54

speed_test_620.txt
speed_test_main.txt

@duburcqa

duburcqa commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

All the unit tests are passing against Genesis-Embodied-AI/genesis-world#2742.

However, there is a bug in load_store elimination for static for-loops in eliminate_recomputable_ad_stack_pushes which breaks the unit test test_differentiable_push on genesis/main. It is unrelated to this PR. It was introduced by #621. I was not able to spot it because I ran the unit test against 2742 instead of main at that time... I suggest to fix this bug in a follow-up since it is unrelated?

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

@duburcqa

duburcqa commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

After applying #633, all the unit tests are passing on Genesis main
======= 642 passed, 3 skipped, 2 xfailed in 1086.07s (0:18:06) =======

@hughperkins

Copy link
Copy Markdown
Collaborator

checklist:

  • docs look good to me
  • agent based checks passed
  • genesis unit tests passing
  • genesis benchmarks ~neutral

=> ok to merge

@duburcqa duburcqa merged commit eef0712 into main May 5, 2026
56 checks passed
@duburcqa duburcqa deleted the duburcqa/perf_adstack_metadata_cache branch May 5, 2026 18:43
npoulad1 added a commit to ROCm/quadrants that referenced this pull request Jun 8, 2026
* [Misc] Warn user to disable caching when print_ir/QD_DUMP_IR enabled (Genesis-Embodied-AI#425)

Co-authored-by: v01dxyz <v01dxyz@v01d.xyz>

* [Build] Pin torch version to CUDA 12.8 for CUDA tests (Genesis-Embodied-AI#428)

* [Misc] Fixing up taichi-dev urls (Genesis-Embodied-AI#429)

* [Perf] Rename cuda_graph to gpu_graph across the codebase (Genesis-Embodied-AI#430)

* Misc: fix typo integeral -> integral (Genesis-Embodied-AI#434)

Co-authored-by: v01dxyz <v01dxyz@v01d.xyz>

* [Perf] CUDA graph 4: call from multiple locations (Genesis-Embodied-AI#420)

* [Bug] Fix fastcache not restoring graph_do_while_arg (Genesis-Embodied-AI#435)

* [Perf] Cache last-call result in perf_dispatch for single-compatible case (Genesis-Embodied-AI#438)

* Fix gpu_graph fallback on old Nvidia GPU. (Genesis-Embodied-AI#443)

* Fix shared memory offset not reset between CUDA kernels. (Genesis-Embodied-AI#442)

* [Misc] Allow disabling GPU graph via QD_GPU_GRAPH=0 env var (Genesis-Embodied-AI#439)

* [Misc] Add named top-level loops (Genesis-Embodied-AI#440)

* [Misc] Rename gpu_graph to graph (Genesis-Embodied-AI#446)

* [Misc] Add cross-platform shuffle (Genesis-Embodied-AI#447)

* [Bug] Fix graph_do_while on Windows: search for cudadevrt.lib (Genesis-Embodied-AI#456)

* [Bug] Also search default CUDA toolkit install location on Windows (Genesis-Embodied-AI#461)

* [SPIRV] Feature Parity Atomics & Shared Array (Genesis-Embodied-AI#432)

* [Misc] Change clang format to 120 characters (Genesis-Embodied-AI#463)

* [Misc] CUDA graph 5 Add fatbin (Genesis-Embodied-AI#464)

* [Bug] Reuse VkInstance across init/reset cycles (Genesis-Embodied-AI#465)

* [Perf] Tiles 1: _load, _store, _eye_ (Genesis-Embodied-AI#466)

* [Misc] Remove dead InternalFuncStmt type_check override (Genesis-Embodied-AI#471)

* [Perf] Tiles 2: add cholesky and ger (Genesis-Embodied-AI#472)

* [Perf] Tiles 2b: add triangular solve (Genesis-Embodied-AI#474)

* [Misc] Refactor: use _get_col/_set_col in tiles load/store/init (Genesis-Embodied-AI#475)

* [Build] Fix flaky test_clock_accuracy (Genesis-Embodied-AI#436)

* Fix AARCH64 emitting invalid asm in CUDA kernels. (Genesis-Embodied-AI#473)

Co-authored-by: Hugh Perkins <hughperkins@gmail.com>

* [AMDGPU] Enable HIP memory pool and surface pool-exhaustion errors. (Genesis-Embodied-AI#485)

* [AMDGPU] Scope hsaco tmp dir per-user to avoid collisions. (Genesis-Embodied-AI#484)

* [Perf] Tiles 3: Add slice syntax, qd.outer() and initial doc (Genesis-Embodied-AI#477)

* [AMDGPU] Fix gradient computation. (Genesis-Embodied-AI#486)

* Enable all backends that are supported in unit tests. (Genesis-Embodied-AI#488)

* Fix SPIRV ID overflow for large kernels due to autodiff. (Genesis-Embodied-AI#489)

* [Misc] Fix purity checker to allow accessing constants from quadrants modules (Genesis-Embodied-AI#487)

* [Misc] Increase tolerance for clock monotonic test (Genesis-Embodied-AI#492)

* [CI] Serialize api doc workflow (Genesis-Embodied-AI#494)

* [CI] Increase tolerance for clock test (Genesis-Embodied-AI#506)

* [CI] Increase clock test tolerance to 20% (Genesis-Embodied-AI#509)

* [Perf] Add tensor_type parametrization to tile16 tests (Genesis-Embodied-AI#504)

* [Perf] Tiles 4b: Migrate tiles16 tests to enable fastcache (Genesis-Embodied-AI#505)

* [Perf] Tiles 4c: add Tiles16x16 proxy (Genesis-Embodied-AI#507)

* [Perf] Tiles 4d: Consolidate slice error tests using parametrize (Genesis-Embodied-AI#508)

* [Perf] Tiles 4: add SharedArray slice support (Genesis-Embodied-AI#482)

* [Perf] Tiles 5: add Cholesky benchmark demo (Genesis-Embodied-AI#483)

* [Doc] Add user guide page for subgroup shuffle (Genesis-Embodied-AI#512)

* [Perf] Implement cross-platform shuffle_down (Genesis-Embodied-AI#510)

* [Perf] Add portable subgroup reduce_add and reduce_all_add (Genesis-Embodied-AI#511)

* [Perf] Add first warmup config to perf dispatch (Genesis-Embodied-AI#422)

* [AutoDiff] Autodiff 1: Add baseline adstack regression test for unary_collections (Genesis-Embodied-AI#500)

* [AutoDiff] Autodiff 2: Implement derivative for tan (Genesis-Embodied-AI#501)

* [AutoDiff] Autodiff 3: Recompute tanh/exp on the operand in the reverse pass (Genesis-Embodied-AI#502)

* [AutoDiff] Autodiff 4: Mark rsqrt as non-linear for adstack promotion (Genesis-Embodied-AI#503)

* [AutoDiff] Autodiff 5: Fix adjoint-alloca placement for GlobalLoads outside the current range-for (Genesis-Embodied-AI#496)

* [AutoDiff] Autodiff 6: Adstack regression tests (Genesis-Embodied-AI#491)

* [AutoDiff] Autodiff 7: Fix header size in AdStackAllocaStmt to match u64 runtime layout (Genesis-Embodied-AI#534)

* [AutoDiff] Autodiff 8: Surface LLVM adstack push/pop overflow as a Python exception (Genesis-Embodied-AI#535)

* [AutoDiff] Autodiff 9: Guard against LLVM worker-thread stack overflow from large per-task adstack budget (Genesis-Embodied-AI#495)

* [AutoDiff] Autodiff 10: Implement adstack for SPIR-V (Genesis-Embodied-AI#490)

* [AutoDiff] Autodiff 11: Latent adstack-adjacent fixes (AMDGPU hipFree, flush() keeps ctx_buffers_, always-preallocate) (Genesis-Embodied-AI#536)

* [Doc] Add AGENTS.md with instructions for AI agents (Genesis-Embodied-AI#541)

* [Bug] Abort kernel execution on assertion failure instead of segfaulting (Genesis-Embodied-AI#419)

* [Type] ndarray typing 1: Add eval_str=True to inspect.signature() calls (Genesis-Embodied-AI#411)

* [CI] Suppress reportPrivateImportUsage in torch-using files (Genesis-Embodied-AI#552)

* [Misc] QD_DUMP_IR dumps to files with the task_id added to the filename (Genesis-Embodied-AI#441)

* [Type] ndarray typing 2: Fix NDArray single-arg subscript crash (Genesis-Embodied-AI#412)

* [Test] Flush xdist channel before worker exit so test failure reports are visible (Genesis-Embodied-AI#555)

* [CI] Reduce test retries on CI from 3 to 1. (Genesis-Embodied-AI#554)

* [AutoDiff] Autodiff 12: Heap-backed adstack on LLVM backends (CPU/CUDA/AMDGPU) (Genesis-Embodied-AI#537)

* [AutoDiff] Autodiff 13: Heap-backed adstack on SPIR-V backends (Metal, Vulkan) (Genesis-Embodied-AI#493)

* [AutoDiff] Autodiff 14: Resolve bounded-inner-loop adstacks without default_ad_stack_size fallback (Genesis-Embodied-AI#539)

* [SPIRV] Vulkan SPIR-V correctness: atomic-view aliasing, PSB stride, narrow storage caps, u1 cast, per-init layer recheck (Genesis-Embodied-AI#513)

* [Build] Autodiff 15: Replace 2022 MoltenVK pin with LunarG Vulkan SDK fetch and sanitise MoltenVK cap advertisement (Genesis-Embodied-AI#551)

* [Test] Suppress stock pytest-timeout to avoid conflict with pytest_hardtle (Genesis-Embodied-AI#557)

* [Vulkan] Use SDK validation layer for debugPrintf instead of apt package (Genesis-Embodied-AI#562)

* [Test] Fix flaky perf_dispatch tests by increasing work amounts (Genesis-Embodied-AI#559)

* [Test] Add --maxfail CLI option to run_tests.py (default 20) (Genesis-Embodied-AI#558)

* [CI] Vulkan debug printf fix to address flaky tests (Genesis-Embodied-AI#563)

* [Docs] Add a new page to help for first time contributors (Genesis-Embodied-AI#426)

Authored-by: v01dxyz <v01dxyz@v01d.xyz>

* [AutoDiff] Autodiff 16: Resolve reverse-mode adstack depths per-launch via runtime-evaluated SizeExpr (Genesis-Embodied-AI#543)

* Fix: raise error if device memory allocation fails (Genesis-Embodied-AI#451) (Genesis-Embodied-AI#453)

Co-authored-by: v01dxyz <v01dxyz@v01d.xyz>
Co-authored-by: Hugh Perkins <hughperkins@gmail.com>

* [CI] Add CI job to check line wrapping of comments and docs (Genesis-Embodied-AI#564)

* [Misc] Add coverage report to PRs, including kernels (Genesis-Embodied-AI#470)

* [CI] CI wrap check feeds only diffs to agent (Genesis-Embodied-AI#567)

* Skip 'flaky' test on MacOS CI. (Genesis-Embodied-AI#573)

* [Test] Fix missing `import sys` in test_fail_device_memory_allocation (Genesis-Embodied-AI#574)

* [CI] Fix Vulkan debugPrintf flake with session-scoped warmup (Genesis-Embodied-AI#571)

* [AutoDiff] determine_ad_stack_size: replace whole-CFG Bellman-Ford with SCC + DAG DP (Genesis-Embodied-AI#575)

* [Test] Fix macOS OOM skip reason to describe actual root cause (Genesis-Embodied-AI#576)

* [Lang] whole_kernel_cse: 2.5x compile time speedup on large kernels (Genesis-Embodied-AI#577)

* [CI] Add CI check for unnecessarily deleted comments (Genesis-Embodied-AI#570)

* [CI] Migrate coverage report to github Check page (Genesis-Embodied-AI#566)

* [Lang] Skip IR verifier between passes unless debug=true (Genesis-Embodied-AI#579)

* [Lang] Inline AdStack ops on release LLVM codegen: dramatically reduces compile time for adstack-enabled reverse-mode kernels (Genesis-Embodied-AI#584)

* [CUDA] Honor offline_cache=False end-to-end so QD_OFFLINE_CACHE=0 actually gives a cold compile (Genesis-Embodied-AI#580)

* [Type] Tensor 24 (Genesis-Embodied-AI#561)

Co-authored-by: hugh <hugh@slurm-login-0.slurm-login.tenant-slurm.svc.cluster.local>

* [Lang] auto_diff host-walk reductions: dramatically faster front-end compile time on adstack-enabled reverse-mode kernels (Genesis-Embodied-AI#587)

* [AutoDiff] Speed up reverse-mode kernel launches on GPU backends (Genesis-Embodied-AI#578)

* [Vulkan] Move adstack-sizer scratch out of Function-scope memory to fix SPIR-V pipeline build failures (Genesis-Embodied-AI#588)

* [AutoDiff] Improve diagnosis of unsupported reverse-mode AD patterns (Genesis-Embodied-AI#590)

* [Bug] Fix: promote Ndarray to AnyArray in build_Name for flattened struct fields (Genesis-Embodied-AI#592)

* [SPIR-V] Shrink reverse-grad kernel MSL by ~50% (Genesis-Embodied-AI#591)

* [CI] Add CI check that PR changes have test coverage (Genesis-Embodied-AI#596)

* [Perf] Enable zero-copy in to_torch() and to_numpy() (Genesis-Embodied-AI#450)

* Add BufferView: safe sub-range ndarray access for kernels (Genesis-Embodied-AI#585)

Co-authored-by: alanray-tech <alanray-tech@users.noreply.github.com>
Co-authored-by: Hugh Perkins <hughperkins@gmail.com>

* [Doc] Add user-facing fastcache documentation (Genesis-Embodied-AI#597)

Co-authored-by: hugh <hugh@slurm-login-0.slurm-login.tenant-slurm.svc.cluster.local>

* [Misc] Upgrade to enable v1 dlpack so to_numpy(copy=False) writable (Genesis-Embodied-AI#598)

Co-authored-by: root <root@rtx-209-201.slurm-compute.tenant-slurm.svc.cluster.local>

* [AutoDiff] Cut reverse-mode adstack memory usage 10x on all backends (Genesis-Embodied-AI#599)

* [Misc] Add CI check for feature file factorization (Genesis-Embodied-AI#606)

* [Perf] Skip _recursive_set_args for all-Field frozen dataclass structs (Genesis-Embodied-AI#607)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [AutoDiff] SNode-arm bound-expr capture rejects fold-attack gate indices (Genesis-Embodied-AI#610)

* [Misc] Suppress field fastcache warning for qd.Tensor (Genesis-Embodied-AI#615)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [AutoDiff] Adstack heap: clip reducer count by per-task loop trip count (compile-time and SizeExpr-evaluated) (Genesis-Embodied-AI#611)

* [Misc] Forward copy= through qd.Tensor, add copy=None option (Genesis-Embodied-AI#616)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [Doc] Update README (Genesis-Embodied-AI#617)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [CI] Fix coverage report showing def lines as uncovered (Genesis-Embodied-AI#623)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [Perf] Generic launcher: persistent context, JIT-pointer reuse, Metal compute encoder, LLVM-GPU async memory ops (Part 1/2) (Genesis-Embodied-AI#619)

* [CI] Encode Python-first testing policy in coverage-check prompt (Genesis-Embodied-AI#622)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [CI] Add PR Line change report (Genesis-Embodied-AI#624)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [CI] Disable quadrants pytest plugin during quadrants internal coverage runs (Genesis-Embodied-AI#629)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [AutoDiff] Adstack load+store eliminations: EliminateRecomputableAdStackPushes pass + leaf extensions (Genesis-Embodied-AI#621)

* [CI] Simplify coverage PR comment to a single linked line (Genesis-Embodied-AI#630)

* [CUDA] Add AGX Thor, SM_110 (Genesis-Embodied-AI#631)

Co-authored-by: Johnny Nunez and Hugh Perkins

* [CI] Lines changed report: collapse PR comment to a single linked totals line (Genesis-Embodied-AI#632)

* [FEATURE] Support external Metal command queue via qd.init (Genesis-Embodied-AI#618)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [Perf] Cache adstack-sizer metadata per task across SPIR-V + LLVM-GPU; per-snode / DeviceAllocation invalidation (Part 2/2) (Genesis-Embodied-AI#620)

* [AutoDiff] Disable EliminateRecomputableAdStackPushes pending mutated-SNode chain-leaf fix (Genesis-Embodied-AI#633)

* [AutoDiff] Adstack chain-clone safety: mutated-SNode leaf reject + load_top consumer-aware guard (Genesis-Embodied-AI#634)

* [Docs] Add user-guide page for qd.simt.block.* primitives (Genesis-Embodied-AI#638)

* [Docs] Expand qd.simt.subgroup user-guide page to cover every op (Genesis-Embodied-AI#639)

* [Perf] Streams 1-4 (Genesis-Embodied-AI#410)

* [Docs] Add user-guide page for matrix decompositions and solvers (Genesis-Embodied-AI#643)

* [Bug] Revert "[Perf] Streams 1-4 (Genesis-Embodied-AI#410)" (Genesis-Embodied-AI#650)

* [Docs] Add user-guide page for atomics and bit operations (Genesis-Embodied-AI#640)

* [Docs] Add user-guide page for qd.simt.grid.* primitives (Genesis-Embodied-AI#641)

* [AutoDiff] Adstack max-reducer: parallel multi-axis MaxOverRange dispatch (Genesis-Embodied-AI#635)

* [AMDGPU] Fix amdgpu parallel rand init (Genesis-Embodied-AI#658)

* [Perf] Adstack: skip max-reducer recognizer on CPU + lift host-eval cap (Genesis-Embodied-AI#655)

* [Perf] Re-land Streams 1-4 with bug fixes (Genesis-Embodied-AI#653)

* [AMDGPU] Apply device_memory_GB=0.3 cap to AMDGPU tests (Genesis-Embodied-AI#659)

* [Perf] Per-launch host sync: drop wait_idle on SPIR-V, pin stream and drop stream_synchronize on CUDA/AMDGPU (Genesis-Embodied-AI#654)

* [AMDGPU] Unload hipModule_t in JITModuleAMDGPU destructor (Genesis-Embodied-AI#660)

* [AMDGPU] Trim default mempool on qd.reset() (Genesis-Embodied-AI#669)

* [AMDGPU] Hoist rand-state buffer to process lifetime (Genesis-Embodied-AI#668)

* [Streams] Use events for streams serialization on AMDGPU and CUDA (Genesis-Embodied-AI#667)

* [Perf] Adstack max-reducer: launch cache + zero-copy result map; content-stable registry_id (Genesis-Embodied-AI#671)

* [SPIR-V] dispatch_max_reducers: register each task with the real kernel name (Genesis-Embodied-AI#675)

* [AutoDiff] Debug-mode field/grad/dual: dtype, layout, and access-time invariants (Genesis-Embodied-AI#677)

* [Docs] Add user-guide page for qd.algorithms.* device-wide algorithms (Genesis-Embodied-AI#642)

Co-authored-by: alanray-tech <alan.ray@genesis-ai.company>

* [Docs] Doc for existing atomics: switch support table to per-backend columns (Genesis-Embodied-AI#657)

Co-authored-by: alanray-tech <alan.ray@genesis-ai.company>

* [GPU] Cross gpu atomics (Genesis-Embodied-AI#666)

Co-authored-by: alanray-tech <alan.ray@genesis-ai.company>

* [GPU] Make block operations portable cross-gpu (Genesis-Embodied-AI#664)

* [Perf] CPU LLVM adstack-cache: skip per-launch bump-writes + ndarray_shapes capture on forward-only handles (Genesis-Embodied-AI#685)

* [GPU] Cross-GPU for grid ops (Genesis-Embodied-AI#670)

* [Math] Make bitop operations portable cross-gpu (Genesis-Embodied-AI#662)

* [AMDGPU] Always use wave64, on both RDNA and CDNA (Genesis-Embodied-AI#687)

* [AMDGPU] Use syncscope("agent") for atomix xor to avoid CAS livelock (Genesis-Embodied-AI#672)

* [GPU] New bit ops for QIPC (Genesis-Embodied-AI#679)

* [GPU] Subgroup ops cross-gpu (Genesis-Embodied-AI#665)

* [Graph] Rename CUDA Graph to Graph in docs (Genesis-Embodied-AI#691)

* [SPIR-V] Fix FIFO-queue ordering when sharing command queue. (Genesis-Embodied-AI#694)

* [Atomics] New QIPC ops for atomics (Genesis-Embodied-AI#690)

* Pass dataclass sub-structs into qd.func (Genesis-Embodied-AI#698)

* [AMDGPU] HIP graph runtime support for @qd.kernel(graph=True) (Genesis-Embodied-AI#692)

* [CI] Add per-file timing report to Mac Metal test job (Genesis-Embodied-AI#695)

Co-authored-by: Cursor <cursoragent@cursor.com>

* [CI] Enable kernel disk cache during tests (Genesis-Embodied-AI#696)

* [Math] New QIPC ops for single-threaded linalg (Genesis-Embodied-AI#683)

* [BREAKING][GPU] New QIPC ops for subgroups (Genesis-Embodied-AI#676)

* [GPU] New QIPC ops for block (Genesis-Embodied-AI#684)

* [GPU] New device-level ops for QIPC (Genesis-Embodied-AI#693)

* [algorithms] PrefixSumExecutor: drop unused GRID_SZ local (Genesis-Embodied-AI#701)

* [block] sync(): fix unsupported-arch error message (Genesis-Embodied-AI#700)

* [volatile_load] add qd.volatile_load primitive (closes Genesis-Embodied-AI#648) (Genesis-Embodied-AI#702)

* [AutoDiff] Reject recycled identity_key in AdStackCache::register_adstack_sizing_info (Genesis-Embodied-AI#708)

* [Vulkan] Declare GroupNonUniform SPIR-V caps and enable shaderSubgroupExtendedTypes (Genesis-Embodied-AI#707)

* Fix duplicate HIP graph driver-function declarations after v1.0.0 merge

The amd-integration fork had cherry-picked the HIP graph driver functions
(graph_create / graph_destroy / graph_add_kernel_node / graph_instantiate /
graph_exec_destroy / graph_launch), and upstream v1.0.0 added the same set.
The per-file 3-way merge appended both copies into
amdgpu_driver_functions.inc.h, producing redeclaration errors that broke the
AMDGPU RHI/runtime compile. Drop the upstream duplicate block; the signatures
are identical to the fork's existing declarations.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix AMDGPU launcher coherence and num_instructions visibility after v1.0.0 merge

- kernel_launcher.cpp: the 3-way merge spliced upstream v1.0.0's launch_llvm_kernel
  rewrite (ephemeral arg/context buffers, explicit-stream path, AmdgpuDefaultStream
  PinGuard) onto the AMD fork's kernarg-by-value + persistent-scratch design,
  leaving references to undefined `ephemeral_context_ptr`. Restore the fork's
  coherent launch_llvm_kernel verbatim; it calls the (already merged) enhanced
  launch_offloaded_tasks, which keeps the max-reducer dispatch and stream-parallel
  groups adapted onto the AMD launch path.
- llvm_context.h: both the fork and upstream added `num_instructions`; the merge
  kept upstream's private placement, but the AMDGPU codegen force-inline heuristic
  calls it statically from outside the class. Move it back to the public section.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore async result D2H and hoist kernarg vectors in AMDGPU launcher

The v1.0.0 merge resolution regressed two amd-integration baseline
optimizations in launch_llvm_kernel / launch_offloaded_tasks:

  - The per-launch result-buffer copy was a blocking memcpy_device_to_host,
    forcing a host stall on every value-returning launch and serializing the
    GPU pipeline. Restore the async D2H (the caller synchronizes lazily when it
    needs the value); external-array transfers still stream_synchronize once
    before reading back.

  - launch_task constructed the kernarg std::vectors from initializer lists
    ({kernarg_payload} / {kernarg_size}) on every dispatch (heap alloc + free
    per launch). Hoist arg_ptrs/arg_sizes out of the per-task launch and reuse.

Co-authored-by: Cursor <cursoragent@cursor.com>

* amdgpu: default to LDS permlane64 emulation; drop host-x86 barrier asm on retarget

Two AMDGPU JIT-compile crashes surfaced after the v1.0.0 merge pulled in the QIPC subgroup
ops (Genesis-Embodied-AI#676), which made the rigid constraint solver's wave-cooperative reductions route through
`amdgpu_cross_half_shuffle_i32`. Both manifested as a SIGSEGV inside
`llvm::SIInstrInfo::getInstSizeInBytes` during `JITSessionAMDGPU::compile_module_to_hsaco`
(i.e. at first kernel launch), and reproduce on gfx942 / MI300X. Baseline 0.4.6 never emitted
these constructs, which is why it was unaffected.

1. Native `llvm.amdgcn.permlane64` lowering crashes the bundled LLVM 22.1.0 AMDGPU backend.
   Default `amdgpu_permlane64` to the existing LDS-roundtrip software emulation on every target
   (it produces identical results). Add `QD_AMDGPU_USE_NATIVE_PERMLANE64=1` to opt back into the
   native instruction once the backend bug is fixed; the old `QD_AMDGPU_FORCE_PERMLANE64_FALLBACK`
   is now the default and still honored. This is the actual crash fix.

2. The runtime module is compiled by the host x86_64 clang and only retargeted to amdgcn here, so
   `amdgpu_cross_half_shuffle_i32`'s `__asm__ volatile("" : "+v"(byte))` optimization barrier carries
   x86 flag clobbers (`~{dirflag},~{fpsr},~{flags}`) that are meaningless on AMDGPU. The IR verifies
   but the empty-body INLINEASM is invalid on the amdgcn target. Neutralize empty-body barrier asm
   during retarget (forward the tied value, then erase) so no stale host asm reaches codegen. On the
   wave64 targets we ship `ds_bpermute` already addresses the full wave, so the hint is a no-op.

Co-authored-by: Cursor <cursoragent@cursor.com>

* style: apply clang-format (v19.1.7) to AMDGPU fn_attrs and launcher sources

CI pre-commit's clang-format hook reformatted these files (long
declarations/lambda signatures collapsed onto single lines per the repo's
clang-format config). Apply the same formatting so the hook passes.

No functional changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(amdgpu): use CreateNeg for branchless i32 sgn instead of CreateSub(0, input)

clang-tidy (modernize-use-nullptr, -warnings-as-errors) flagged
`builder->CreateSub(0, input)` in the i32 sgn path: the literal `0` binds to
the `llvm::Value*` LHS parameter as a null pointer, not an integer zero.
Replace with `builder->CreateNeg(input)`, which emits `0 - input` with a proper
zero constant -- identical intended semantics, and clang-tidy clean.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Robert Dazi <14996868+v01dXYZ@users.noreply.github.com>
Co-authored-by: v01dxyz <v01dxyz@v01d.xyz>
Co-authored-by: Hugh Perkins <hughperkins@gmail.com>
Co-authored-by: Alexis DUBURCQ <alexis.duburcq@gmail.com>
Co-authored-by: hugh <hugh@slurm-login-0.slurm-login.tenant-slurm.svc.cluster.local>
Co-authored-by: alanray-tech <alan.ray@genesis-ai.company>
Co-authored-by: alanray-tech <alanray-tech@users.noreply.github.com>
Co-authored-by: root <root@rtx-209-201.slurm-compute.tenant-slurm.svc.cluster.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Johnny <johnnynuca14@gmail.com>
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