Skip to content

[AutoDiff] Improve diagnosis of unsupported reverse-mode AD patterns#590

Merged
duburcqa merged 1 commit into
mainfrom
duburcqa/dynamic_range_grad_check_in_func
Apr 29, 2026
Merged

[AutoDiff] Improve diagnosis of unsupported reverse-mode AD patterns#590
duburcqa merged 1 commit into
mainfrom
duburcqa/dynamic_range_grad_check_in_func

Conversation

@duburcqa

@duburcqa duburcqa commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Improve diagnosis of unsupported reverse-mode AD patterns

Two diagnostic fixes for previously-silent classes of wrong-gradient bugs in reverse-mode autodiff. (1) An AST-level fix that propagates the caller's loop_depth across @qd.func boundaries so the existing dynamic-range guard fires on @qd.func-routed shapes the same way it already does on inline shapes. (2) A new IR-level guard in auto_diff.cpp that catches cross-iteration read-after-write on a needs_grad global field at the offload-level loop, a shape neither the AST guard nor the adstack handles.

TL;DR

The kernel below silently produced x.grad == 1 instead of the correct x.grad == n in both adstack settings:

@qd.func
def body():
    for i in range(n_iter[None]):
        if i == 0:
            out[i] = x[None]
        else:
            out[i] = out[i - 1] + x[None]

@qd.kernel
def fwd():
    body()

It now raises a clear RuntimeError from auto_diff.cpp pointing 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-static range(...) nested inside an outer for-loop in the kernel but routed through a @qd.func body - now also raises the same QuadrantsCompilationError the inline shape already raised.

Why

Two distinct silent-wrong-gradient classes that previously slipped through every guard.

Class 1 - @qd.func bypassed the existing AST guard

The upstream loop_depth > 0 check at ASTTransformer.build_For fires when a non-static range(...) is AST-nested inside another for-loop. The check uses ctx.loop_depth, which is local to a single AST transformer context and resets to zero every time the transformer enters a @qd.func body, so:

# Inline: AST loop_depth == 1 at the inner range -> existing check fires (without adstack).
@qd.kernel
def k():
    for i in x:
        for _ in range(n_iter):
            v = v * 0.95 + 0.01

# Routed through @qd.func: func ctx loop_depth resets to 0 -> existing check did NOT fire,
# kernel compiled and silently produced wrong gradients.
@qd.func
def inner(...):
    for _ in range(n_iter):
        v = v * 0.95 + 0.01

@qd.kernel
def k():
    for i in x:
        y[None] += inner(x[i])

This shape is pinned by the new wrap_inner_in_func=True parametrization on test_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_grad global field at the offload-level loop

Reverse-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.cpp to 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):

  • AdStackAllocaJudger only promotes cross-iteration dependencies on local AllocaStmts. Cross-iter through GlobalStoreStmt / GlobalLoadStmt on a has_adjoint() SNode is never promoted.
  • MakeAdjoint's reverse pass for the offload-level loop body emits per-iteration adjoints; the cross-iter chain out[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:

  1. Cannot use non static range in Backwards mode (raised as QuadrantsCompilationError) - the upstream AST guard at ASTTransformer.build_For. Now also fires for @qd.func-routed shapes thanks to the caller-loop_depth propagation, so inline and func-routed forms of the diagnostic behave identically.
  2. Cross-iteration read-after-write on the needs_gradglobal field at the offload-level loop body is not supported in reverse-mode autodiff: ... (raised as RuntimeError from QD_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_negative accepts 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_depth propagation

Three Python files - _func_base.py, ast_transformer_utils.py, call_transformer.py - thread the caller's ctx.loop_depth across @qd.func invocations:

  • ASTTransformerGlobalContext carries a caller_loop_depth: int field (the snapshot site).
  • CallTransformer.build_Call writes ctx.loop_depth into the global context's caller_loop_depth before invoking the callable, and restores the previous value in a finally (so chained calls and exceptions both restore correctly).
  • _func_base.py seeds the freshly-created ASTTransformerFuncContext with ctx.loop_depth = global_context.caller_loop_depth for non-kernel contexts, so a non-static range(...) inside the func body sees the same loop_depth > 0 the caller is already inside. Kernel contexts always begin at depth 0.

The upstream loop_depth > 0 check at ASTTransformer.build_For is otherwise untouched.

2. IR-level: OffloadLevelGlobalCrossIterRAWChecker in auto_diff.cpp

A BasicStmtVisitor gated on autodiff_mode == kReverse, run before RegulateTensorTypedStatements so the diagnostic still points at the user-shaped IR (post-frontend-lowering, but before any AD rewrites).

Walks the body of every direct-child RangeForStmt of the kernel root once. Inside that body collects, per has_adjoint() SNode:

  • every GlobalStoreStmt destination GlobalPtrStmt (also drilling through MatrixPtrStmt to its origin),
  • every GlobalLoadStmt source GlobalPtrStmt (same).

inside_nested_ is set on entry into any nested RangeForStmt / StructForStmt / MeshForStmt / WhileStmt body (the four overrides go through a shared walk_nested_loop_body helper) 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 is has_adjoint():

  • skip if the store index does not transitively reference a LoopIndexStmt (the store writes the same slot every iteration; nothing iterates),
  • skip if the load index does not transitively reference a LoopIndexStmt (the load reads from a disjoint iteration slice the loop never writes to),
  • otherwise compare the indices per-axis via indices_have_no_cross_iter_dependency:
    • axes where neither index references LoopIndexStmt are iter-independent and ignored regardless of value (allows sibling-component access on a constant axis like out[i, 0] vs out[i, 1]),
    • axes where both indices reference LoopIndexStmt must be the same SSA Stmt* (allows in-place out[i] = out[i] + x; flags out[i] = out[i-1] + x because the load axis is BinaryOpStmt(LoopIndexStmt, Sub, ConstStmt) and the store axis is the bare LoopIndexStmt),
    • axes where exactly one references LoopIndexStmt are conservatively flagged (one iter-dependent, one constant on the same axis - cannot prove statically whether the constant aliases an iteration).

StructFor offloads are out of scope for run - their loop variable is an SNode index produced by the runtime, not a user-controlled integer arithmeticed against itself, so the out[i-1] shape this guard targets does not arise.

Tests

Existing test patches

  • tests/python/test_adstack.py::test_adstack_basic_gradient_negative - new wrap_inner_in_func parametrization axis pins the @qd.func-bypass class. The pytest.raises accepts both qd.QuadrantsCompilationError (from the upstream AST guard) and RuntimeError (from QD_ERROR via auto_diff.cpp) since either may surface depending on the shape.

New tests in tests/python/test_adstack.py

A single _run_reverse_global_cross_iter(has_outer_for, in_func, load_kind, expect_raise) factory using the range(1) if qd.static(has_outer_for) else qd.static(range(1)) inline-if pattern (already used by _run_unary_loop_carried in this file) covers eight reverse-mode shapes that read and write the same needs_grad global field from inside a non-static range:

has_outer_for in_func load_kind Shape
False False cross_iter non-static range at the kernel top; cross-iter inline
False True cross_iter non-static range at the top of a @qd.func body called from kernel
True False cross_iter non-static range nested under outer range(1); cross-iter inline
True True cross_iter non-static range nested under outer range(1); cross-iter via @qd.func
False False constant_outside_iter_range kernel-top range; load reads out[n] (slot strictly outside the iteration range)
False True constant_outside_iter_range as above, body in @qd.func
True False constant_outside_iter_range nested under outer range(1); load reads out[n]
True True constant_outside_iter_range as above, body in @qd.func

Two parametrized test functions (_with_adstack and _without_adstack) drive all eight shapes. The cross_iter variants pin the offload-level guard's positive cases; the constant_outside_iter_range variants pin the references_loop_index gate 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_gradient factory plus test_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] * 2 where 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 any needs_grad field; 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 on out reference the same LoopIndexStmt so the per-axis rule treats the indices as matching.

Side-effect audit

Concern Where checked Verdict
qd.ndrange / qd.grouped / mesh-for nested cases the upstream AST guard at ASTTransformer.build_For only fires on node.iter.func.id == "range", never on the other lowering paths, and the new IR-level checker does not regress this Existing behavior preserved
AdStackAllocaJudger semantics not touched Existing behavior unchanged; only invoked from ReplaceLocalVarWithStacks as before
Adstack-on path OffloadLevelGlobalCrossIterRAWChecker runs before RegulateTensorTypedStatements and the IB / adstack flow is unchanged No new check on the adstack-on path's per-iteration adjoint emission
Independent-iteration kernel-top range-fors OffloadLevelGlobalCrossIterRAWChecker only fires on same-SNode (store, load) pairs that BOTH transitively reference LoopIndexStmt and differ structurally per axis out[i] = x[None] * 2 (no load on out) compiles unchanged in both settings
In-place accumulation out[i] = out[i] + x per-axis rule: store and load reference the same LoopIndexStmt on every axis Compiles unchanged; pinned by test_reverse_kernel_top_in_place_accum_with_adstack
Multi-axis sibling-component access out[i, 0] = out[i, 1] * 2 per-axis rule: axes where neither index references LoopIndexStmt are ignored Compiles unchanged; pinned by test_reverse_sibling_component_correct_gradient_with_adstack
Loads on disjoint iteration slices out[i] = out[external_const] the load index does not reference LoopIndexStmt and is skipped Compiles unchanged; pinned by the load_kind=constant_outside_iter_range variants
Forward-mode AD both fixes gated on autodiff_mode == kReverse No effect
Non-AD kernels both fixes gated on autodiff_mode == kReverse No effect
Nested mesh-for / while bodies walk_nested_loop_body overrides cover MeshForStmt and WhileStmt so loads / stores inside them are excluded from the offload-level pairing Symmetric with RangeForStmt / StructForStmt

@hughperkins

Copy link
Copy Markdown
Collaborator

INteresting. Lets make sure to run genesis unit tests and benchmarks please. For loops and ast transformer is clearly used all over the place.

@duburcqa duburcqa marked this pull request as ready for review April 29, 2026 12:13

@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: 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".

Comment thread quadrants/transforms/auto_diff.cpp Outdated
Comment thread quadrants/transforms/auto_diff.cpp
@duburcqa duburcqa changed the title AD: diagnose cross-iteration RAW on a needs_grad global field at the offload-level loop, and move the dynamic-range guard from the Python AST transformer to auto_diff.cpp Improve diagnosis of unsupported reverse-mode AD patterns Apr 29, 2026
@duburcqa duburcqa changed the title Improve diagnosis of unsupported reverse-mode AD patterns [AutoDiff] Improve diagnosis of unsupported reverse-mode AD patterns Apr 29, 2026
Comment thread quadrants/transforms/auto_diff.cpp
Comment thread quadrants/transforms/auto_diff.cpp
Comment thread python/quadrants/lang/ast/ast_transformer.py Outdated
…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
@duburcqa duburcqa force-pushed the duburcqa/dynamic_range_grad_check_in_func branch from 69df56e to 887264c Compare April 29, 2026 12:51
@duburcqa

Copy link
Copy Markdown
Contributor Author

Lets make sure to run genesis unit tests and benchmarks please.

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) =======================================================================================================

@duburcqa

Copy link
Copy Markdown
Contributor Author

Comparing Quadrants main vs this PR on Genesis main:

env batch_size backend gjk_collision constraint_solver runtime_fps_588 runtime_fps_590 runtime_fps_delta_pct
anymal_random 30000 cuda - - 9288483 9314890 +0.28
anymal_uniform 30000 cuda - - 12277746 12361607 +0.68
anymal_uniform_kinematic 0 cpu - - 2000 2028 +1.40
anymal_uniform_kinematic 30000 cuda - - 10382797 10266957 -1.12
anymal_zero 0 cpu - - 7186 7073 -1.57
anymal_zero 30000 cuda - - 18857814 19096316 +1.26
box_pyramid_3 4096 cuda - - 978981 968342 -1.09
box_pyramid_4 4096 cuda - - 391086 395824 +1.21
box_pyramid_5 4096 cuda - - 139273 141715 +1.75
box_pyramid_6 4096 cuda False - 58248 59547 +2.23
box_pyramid_6 4096 cuda True - 59916 61918 +3.34
dex_hand 4096 cuda - - 17364 17081 -1.63
duck_in_box_easy 30000 cuda False - 26593384 26469341 -0.47
duck_in_box_easy 30000 cuda True - 9530599 9681964 +1.59
duck_in_box_hard 0 cpu - - 5144 5204 +1.17
duck_in_box_hard 30000 cuda False - 10162732 10077402 -0.84
duck_in_box_hard 30000 cuda True - 3368778 3493847 +3.71
franka 30000 cuda - - 21630563 21764102 +0.62
franka_accessors 0 cpu - - 1203 1182 -1.75
franka_accessors 30000 cuda - - 15764165 15853954 +0.57
franka_free 30000 cuda - - 32238005 32626193 +1.20
franka_random 0 cpu - - 6298 6301 +0.05
franka_random 30000 cuda - CG 16696014 16793645 +0.58
franka_random 30000 cuda - Newton 16450966 16535074 +0.51
franka_random 30000 cuda False - 16345136 16786850 +2.70
franka_random 30000 cuda True - 11410466 11327811 -0.72
g1_fall 4096 cuda - Newton 922187 920318 -0.20
go2 4096 cuda False CG 3673338 3668476 -0.13
go2 4096 cuda False Newton 4464312 4386696 -1.74
go2 4096 cuda True - 3243521 3230236 -0.41
shadow_hand_cubes 0 cpu - - 41 41 +0.00
shadow_hand_cubes_sparse 0 cpu - - 64 66 +3.12

speed_test_590.txt

@hughperkins

Copy link
Copy Markdown
Collaborator

checklist:

  • genesis benchmarks neutral
  • genesis unit test failures explained by the purpose of this pr

=> ok to merge

@github-actions

Copy link
Copy Markdown

Coverage Report (887264c2c)

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

Full annotated report

@duburcqa duburcqa enabled auto-merge (squash) April 29, 2026 14:31
@duburcqa duburcqa disabled auto-merge April 29, 2026 14:32
@duburcqa duburcqa merged commit f8ed8ce into main Apr 29, 2026
60 of 62 checks passed
@duburcqa duburcqa deleted the duburcqa/dynamic_range_grad_check_in_func branch April 29, 2026 14:32
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants