[AutoDiff] Improve diagnosis of unsupported reverse-mode AD patterns#590
Conversation
|
INteresting. Lets make sure to run genesis unit tests and benchmarks please. For loops and ast transformer is clearly used all over the place. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 772b3b20fc
ℹ️ 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".
…agate caller loop_depth across @qd.func boundaries so the upstream AST loop_depth > 0 guard fires on @qd.func-routed nested ranges (closing the silent-wrong-gradient hole on test_adstack_basic_gradient_negative[wrap_inner_in_func=True]); add OffloadLevelGlobalCrossIterRAWChecker in auto_diff.cpp to raise on cross-iteration read-after-write through a needs_grad global field at the offload-level loop body, with a per-axis indices_have_no_cross_iter_dependency rule plus MeshForStmt / WhileStmt overrides so multi-axis sibling-component access (out[i, 0] = out[i, 1]) and loads on a disjoint iteration slice (out[i] = out[external_const]) and nested mesh / while bodies are not flagged as cross-iter; tests cover the matrix via parametrized factories on (has_outer_for, in_func, load_kind) plus a separate sibling-component factory
69df56e to
887264c
Compare
I'm afraid the unit tests are failing. Exactly as it should: > loss.backward()
tests/test_grad.py:102:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
genesis/grad/tensor.py:78: in backward
self.scene._backward()
genesis/engine/scene.py:1450: in _backward
self._step_grad()
genesis/engine/scene.py:1043: in _step_grad
self._sim._step_grad()
genesis/engine/simulator.py:304: in _step_grad
self.sub_step_grad(self.cur_substep_local)
genesis/engine/simulator.py:328: in sub_step_grad
self._coupler.couple_grad(f)
genesis/engine/couplers/legacy_coupler.py:1010: in couple_grad
self.mpm_grid_op.grad(
../quadrants/python/quadrants/lang/_quadrants_callable.py:126: in grad
return self.quadrants_callable._adjoint(self.instance, *args, **kwargs)
../quadrants/python/quadrants/lang/kernel.py:699: in __call__
key = self.ensure_compiled(*py_args) # type: ignore[arg-type]
../quadrants/python/quadrants/lang/kernel.py:635: in ensure_compiled
self.materialize(key=key, py_args=py_args, arg_features=arg_features)
../quadrants/python/quadrants/lang/kernel.py:429: in materialize
quadrants_kernel = impl.get_runtime().prog.create_kernel(
../quadrants/python/quadrants/lang/kernel.py:233: in __call__
transform_tree(tree, ctx)
../quadrants/python/quadrants/lang/ast/transform.py:8: in transform_tree
ASTTransformer()(ctx, tree)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <quadrants.lang.ast.ast_transformer.ASTTransformer object at 0x7c99cc4cab90>
ctx = <quadrants.lang.ast.ast_transformer_utils.ASTTransformerFuncContext object at 0x7c99cc4c9db0>
node = <ast.Module object at 0x7c99dafa2fb0>
def __call__(self, ctx: "ASTTransformerFuncContext", node: ast.AST):
method_name = "build_" + node.__class__.__name__
method = getattr(self, method_name, None)
try:
if method is None:
error_msg = f'Unsupported node "{node.__class__.__name__}"'
raise QuadrantsSyntaxError(error_msg)
info = ctx.get_pos_info(node) if isinstance(node, (ast.stmt, ast.expr)) else ""
with impl.get_runtime().src_info_guard(info):
res = method(ctx, node)
if not hasattr(node, "violates_pure"):
# assume False until proven otherwise
node.violates_pure = False
node.violates_pure_reason = None
return res
except Exception as e:
stack_trace = traceback.format_exc()
if impl.get_runtime().print_full_traceback:
raise e
if ctx.raised or not isinstance(node, (ast.stmt, ast.expr)):
> raise e.with_traceback(None)
E quadrants.lang.exception.QuadrantsCompilationError: quadrants stack trace:
E ===
E Traceback (most recent call last):
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer_utils.py", line 40, in __call__
E res = method(ctx, node)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer.py", line 480, in build_Call
E return CallTransformer.build_Call(ctx, node, build_stmt, build_stmts)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformers/call_transformer.py", line 377, in build_Call
E node.ptr = func(*py_args, **py_kwargs)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/_quadrants_callable.py", line 111, in __call__
E return self.wrapper(self.instance, *args, **kwargs)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/func.py", line 89, in __call__
E ret = transform_tree(tree, ctx)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/transform.py", line 8, in transform_tree
E ASTTransformer()(ctx, tree)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer_utils.py", line 51, in __call__
E raise e.with_traceback(None)
E quadrants.lang.exception.QuadrantsCompilationError: quadrants stack trace:
E ===
E Traceback (most recent call last):
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer_utils.py", line 40, in __call__
E res = method(ctx, node)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer.py", line 1258, in build_For
E raise exception.QuadrantsCompilationError("Cannot use non static range in Backwards mode")
E quadrants.lang.exception.QuadrantsCompilationError: Cannot use non static range in Backwards mode
E
E ===
E
E Your code:
E File "/mnt/home/duburcqa/workspace/src/genesis/genesis/engine/couplers/legacy_coupler.py", line 149, in _func_collide_with_rigid:
E for i_g in range(self.rigid_solver.n_geoms):
E ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Cannot use non static range in Backwards mode
E
E
E ===
E
E Your code:
E File "/mnt/home/duburcqa/workspace/src/genesis/genesis/engine/couplers/legacy_coupler.py", line 391, in mpm_grid_op:
E vel_mpm = self._func_collide_with_rigid(
E ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E f,
E ^^
E pos,
E ^^^^
E vel_mpm,
E ^^^^^^^^
E mass_mpm,
E ^^^^^^^^^
E i_b,
E ^^^^
E geoms_state=geoms_state,
E ^^^^^^^^^^^^^^^^^^^^^^^^
E geoms_info=geoms_info,
E ^^^^^^^^^^^^^^^^^^^^^^
E links_state=links_state,
E ^^^^^^^^^^^^^^^^^^^^^^^^
E rigid_global_info=rigid_global_info,
E ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E sdf_info=sdf_info,
E ^^^^^^^^^^^^^^^^^^
E collider_static_config=collider_static_config,
E ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E )
E ^
E quadrants stack trace:
E ===
E Traceback (most recent call last):
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer_utils.py", line 40, in __call__
E res = method(ctx, node)
E File "/mnt/home/duburcqa/workspace/src/quadrants/python/quadrants/lang/ast/ast_transformer.py", line 1258, in build_For
E raise exception.QuadrantsCompilationError("Cannot use non static range in Backwards mode")
E quadrants.lang.exception.QuadrantsCompilationError: Cannot use non static range in Backwards mode
E
E ===
E
E Your code:
E File "/mnt/home/duburcqa/workspace/src/genesis/genesis/engine/couplers/legacy_coupler.py", line 149, in _func_collide_with_rigid:
E for i_g in range(self.rigid_solver.n_geoms):
E ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Cannot use non static range in Backwards mode
../quadrants/python/quadrants/lang/ast/ast_transformer_utils.py:51: QuadrantsCompilationError
======================================================================================================= 2 failed, 645 passed, 3 skipped, 2 xfailed in 1109.45s (0:18:29) ======================================================================================================= |
|
Comparing Quadrants main vs this PR on Genesis main:
|
|
checklist:
=> ok to merge |
Coverage Report (
|
| File | Coverage | Missing |
|---|---|---|
🟢 python/quadrants/lang/_func_base.py |
100% | |
🟢 python/quadrants/lang/ast/ast_transformer_utils.py |
100% | |
🔴 python/quadrants/lang/ast/ast_transformers/call_transformer.py |
68% | 387-393,395 |
🟢 tests/python/test_adstack.py |
100% |
Diff coverage: 95% · Overall: 73% · 177 lines, 8 missing
* [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>
Improve diagnosis of unsupported reverse-mode AD patterns
TL;DR
The kernel below silently produced
x.grad == 1instead of the correctx.grad == nin both adstack settings:It now raises a clear
RuntimeErrorfromauto_diff.cpppointing at the two valid workarounds (wrap the call in an outer for-loop, or restructure to remove the cross-iteration RAW). The companion@qd.func-bypass class - a non-staticrange(...)nested inside an outer for-loop in the kernel but routed through a@qd.funcbody - now also raises the sameQuadrantsCompilationErrorthe inline shape already raised.Why
Two distinct silent-wrong-gradient classes that previously slipped through every guard.
Class 1 -
@qd.funcbypassed the existing AST guardThe upstream
loop_depth > 0check atASTTransformer.build_Forfires when a non-staticrange(...)is AST-nested inside another for-loop. The check usesctx.loop_depth, which is local to a single AST transformer context and resets to zero every time the transformer enters a@qd.funcbody, so:This shape is pinned by the new
wrap_inner_in_func=Trueparametrization ontest_adstack_basic_gradient_negative; before this PR it would have passed with no diagnostic and a wrong adjoint.Class 2 - cross-iteration RAW on a
needs_gradglobal field at the offload-level loopReverse-mode AD relies on either (a) the adstack to replay loop-carried local primals, or (b) the kernel-internal IB analysis at
quadrants/transforms/auto_diff.cppto chain adjoints through AST-nested loops. Neither mechanism applies when the cross-iteration dependency is on a global field at the offload-level loop (i.e., the kernel's outermost loop, not nested under another loop):AdStackAllocaJudgeronly promotes cross-iteration dependencies on localAllocaStmts. Cross-iter throughGlobalStoreStmt/GlobalLoadStmton ahas_adjoint()SNode is never promoted.MakeAdjoint's reverse pass for the offload-level loop body emits per-iteration adjoints; the cross-iter chainout[i-1] -> out[i]is not threaded into that pass.So a kernel that writes
out[i] = out[i-1] + x[None]at the offload-level loop body silently produced wrong gradients in both adstack settings, with no diagnostic. The TL;DR repro is the minimal expression of this shape.Surface API
No new user-visible API. Two diagnostics fire at compile time when the kernel hits an unsupported reverse-mode shape:
Cannot use non static range in Backwards mode(raised asQuadrantsCompilationError) - the upstream AST guard atASTTransformer.build_For. Now also fires for@qd.func-routed shapes thanks to the caller-loop_depthpropagation, so inline and func-routed forms of the diagnostic behave identically.Cross-iteration read-after-write on theneeds_gradglobal fieldat the offload-level loop body is not supported in reverse-mode autodiff: ...(raised asRuntimeErrorfromQD_ERROR) - new IR-level diagnostic. Fires in both adstack settings because adstack does not handle this shape regardless of the flag. Multi-axis sibling-component access (out[i, 0] = out[i, 1] * 2) and in-place accumulation (out[i] = out[i] + x) are exempted via the per-axis rule described below.test_adstack_basic_gradient_negativeaccepts both exception types since the same shape is now reachable through either guard depending on which fires first; everything else is unchanged.Mechanism
1. AST-level: caller-
loop_depthpropagationThree Python files -
_func_base.py,ast_transformer_utils.py,call_transformer.py- thread the caller'sctx.loop_depthacross@qd.funcinvocations:ASTTransformerGlobalContextcarries acaller_loop_depth: intfield (the snapshot site).CallTransformer.build_Callwritesctx.loop_depthinto the global context'scaller_loop_depthbefore invoking the callable, and restores the previous value in afinally(so chained calls and exceptions both restore correctly)._func_base.pyseeds the freshly-createdASTTransformerFuncContextwithctx.loop_depth = global_context.caller_loop_depthfor non-kernel contexts, so a non-staticrange(...)inside the func body sees the sameloop_depth > 0the caller is already inside. Kernel contexts always begin at depth 0.The upstream
loop_depth > 0check atASTTransformer.build_Foris otherwise untouched.2. IR-level:
OffloadLevelGlobalCrossIterRAWCheckerinauto_diff.cppA
BasicStmtVisitorgated onautodiff_mode == kReverse, run beforeRegulateTensorTypedStatementsso the diagnostic still points at the user-shaped IR (post-frontend-lowering, but before any AD rewrites).Walks the body of every direct-child
RangeForStmtof the kernel root once. Inside that body collects, perhas_adjoint()SNode:GlobalStoreStmtdestinationGlobalPtrStmt(also drilling throughMatrixPtrStmtto its origin),GlobalLoadStmtsourceGlobalPtrStmt(same).inside_nested_is set on entry into any nestedRangeForStmt/StructForStmt/MeshForStmt/WhileStmtbody (the four overrides go through a sharedwalk_nested_loop_bodyhelper) so loads / stores inside nested loops are excluded - the AD pass's IB analysis handles cross-iter through global fields correctly when the cross-iter range is AST-nested under another loop, and the adstack handles cross-iter through local allocas.After the walk, for each
(snode, store, load)triple where the SNode ishas_adjoint():LoopIndexStmt(the store writes the same slot every iteration; nothing iterates),LoopIndexStmt(the load reads from a disjoint iteration slice the loop never writes to),indices_have_no_cross_iter_dependency:LoopIndexStmtare iter-independent and ignored regardless of value (allows sibling-component access on a constant axis likeout[i, 0]vsout[i, 1]),LoopIndexStmtmust be the same SSAStmt*(allows in-placeout[i] = out[i] + x; flagsout[i] = out[i-1] + xbecause the load axis isBinaryOpStmt(LoopIndexStmt, Sub, ConstStmt)and the store axis is the bareLoopIndexStmt),LoopIndexStmtare conservatively flagged (one iter-dependent, one constant on the same axis - cannot prove statically whether the constant aliases an iteration).StructForoffloads are out of scope forrun- their loop variable is an SNode index produced by the runtime, not a user-controlled integer arithmeticed against itself, so theout[i-1]shape this guard targets does not arise.Tests
Existing test patches
tests/python/test_adstack.py::test_adstack_basic_gradient_negative- newwrap_inner_in_funcparametrization axis pins the @qd.func-bypass class. Thepytest.raisesaccepts bothqd.QuadrantsCompilationError(from the upstream AST guard) andRuntimeError(fromQD_ERRORviaauto_diff.cpp) since either may surface depending on the shape.New tests in
tests/python/test_adstack.pyA single
_run_reverse_global_cross_iter(has_outer_for, in_func, load_kind, expect_raise)factory using therange(1) if qd.static(has_outer_for) else qd.static(range(1))inline-if pattern (already used by_run_unary_loop_carriedin this file) covers eight reverse-mode shapes that read and write the sameneeds_gradglobal field from inside a non-static range:has_outer_forin_funcload_kindFalseFalsecross_iterFalseTruecross_iter@qd.funcbody called from kernelTrueFalsecross_iterrange(1); cross-iter inlineTrueTruecross_iterrange(1); cross-iter via@qd.funcFalseFalseconstant_outside_iter_rangeout[n](slot strictly outside the iteration range)FalseTrueconstant_outside_iter_range@qd.funcTrueFalseconstant_outside_iter_rangerange(1); load readsout[n]TrueTrueconstant_outside_iter_range@qd.funcTwo parametrized test functions (
_with_adstackand_without_adstack) drive all eight shapes. Thecross_itervariants pin the offload-level guard's positive cases; theconstant_outside_iter_rangevariants pin thereferences_loop_indexgate so loads on a disjoint iteration slice do not trigger a false positive (with adstack on, the constant-outside variants compile cleanly across all four(has_outer_for, in_func)combinations, regardless of nesting).A separate
_run_reverse_sibling_component_correct_gradientfactory plustest_reverse_sibling_component_correct_gradient_with_adstack(parametrized over the same(has_outer_for, in_func)axes) pins the multi-axis sibling-component case:out[i, 1] = x; out[i, 0] = out[i, 1] * 2where the per-axis rule must allow the differing constant on axis 1.Three sanity tests pin behavior the new checker must not break:
test_reverse_kernel_top_indep_iters_{with,without}_adstack- kernel-top range with no cross-iter RAW on anyneeds_gradfield; gradient must be analytical in both adstack settings.test_reverse_kernel_top_in_place_accum_with_adstack-out[i] = out[i] + x[None]; store and load onoutreference the sameLoopIndexStmtso the per-axis rule treats the indices as matching.Side-effect audit
qd.ndrange/qd.grouped/ mesh-for nested casesASTTransformer.build_Foronly fires onnode.iter.func.id == "range", never on the other lowering paths, and the new IR-level checker does not regress thisAdStackAllocaJudgersemanticsReplaceLocalVarWithStacksas beforeOffloadLevelGlobalCrossIterRAWCheckerruns beforeRegulateTensorTypedStatementsand the IB / adstack flow is unchangedOffloadLevelGlobalCrossIterRAWCheckeronly fires on same-SNode(store, load)pairs that BOTH transitively referenceLoopIndexStmtand differ structurally per axisout[i] = x[None] * 2(no load onout) compiles unchanged in both settingsout[i] = out[i] + xLoopIndexStmton every axistest_reverse_kernel_top_in_place_accum_with_adstackout[i, 0] = out[i, 1] * 2LoopIndexStmtare ignoredtest_reverse_sibling_component_correct_gradient_with_adstackout[i] = out[external_const]LoopIndexStmtand is skippedload_kind=constant_outside_iter_rangevariantsautodiff_mode == kReverseautodiff_mode == kReversewalk_nested_loop_bodyoverrides coverMeshForStmtandWhileStmtso loads / stores inside them are excluded from the offload-level pairingRangeForStmt/StructForStmt