Skip to content

Commit 54e103a

Browse files
committed
[Fixup] Elim-pushes: detect prologue init push by position, not by zero value, to preserve real x = 0.0 body stores
1 parent bd597b1 commit 54e103a

15 files changed

Lines changed: 477 additions & 486 deletions

quadrants/transforms/auto_diff/auto_diff_common.h

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,11 @@ class NonLinearOps {
6161
};
6262

6363
// ----------------------------------------------------------------------------
64-
// Recomputable chain analyzer + cloner: decide whether a forward SSA value can
65-
// be reconstructed in the reverse-pass scope from already-stack-backed allocas,
66-
// kernel args, constants, and loop indices, and clone the DAG at a target
67-
// insertion point. Cross-stage shared infrastructure: used by
68-
// `EliminateRecomputableAdStackPushes` (forward_state_spill stage) to drop
69-
// pushes whose values are recomputable, and by `BackupSSA::generic_visit`
70-
// (post_adjoint_cleanup stage) to clone such chains in place of cross-block
71-
// SSA reads.
64+
// Recomputable chain analyzer + cloner: decide whether a forward SSA value can be reconstructed in the reverse-pass
65+
// scope from already-stack-backed allocas, kernel args, constants, and loop indices, and clone the DAG at a target
66+
// insertion point. Cross-stage shared infrastructure: used by `EliminateRecomputableAdStackPushes`
67+
// (forward_state_spill stage) to drop pushes whose values are recomputable, and by `BackupSSA::generic_visit`
68+
// (post_adjoint_cleanup stage) to clone such chains in place of cross-block SSA reads.
7269
// ----------------------------------------------------------------------------
7370

7471
// Returns true iff `stmt`'s transitive operand DAG terminates at recomputable leaves via side-effect-free
@@ -107,9 +104,7 @@ class RecomputableChainAnalyzer {
107104
// Recomputable leaves: ConstStmt, ArgLoadStmt, AdStackLoadTopStmt, AdStackAllocaStmt. LoopIndexStmt is
108105
// intentionally excluded - cloning a LoopIndexStmt copies the reference to the forward RangeForStmt,
109106
// but the cloned consumer lives inside the reverse RangeForStmt (a separate stmt with its own loop
110-
// index), so the cloned read points at undefined state and silently double-accumulates gradients
111-
// (`test_adstack_sum_linear`). A future MakeAdjoint coordination pass that tracks
112-
// forward-RangeFor-to-reverse-RangeFor mapping could lift this restriction.
107+
// index), so the cloned read points at undefined state and silently double-accumulates gradients.
113108
//
114109
// AdStackLoadTopStmt as a leaf is correct under the dominance + control-flow-consumer + self-load
115110
// guards in `EliminateRecomputableAdStackPushes::run_one_pass`. The reasoning:
@@ -146,11 +141,9 @@ class RecomputableChainAnalyzer {
146141
// before reverse runs, so the reverse re-read sees the kernel's final post-write state. If a global is
147142
// mutated by the forward and then re-read by the reverse clone, the values can differ.
148143
//
149-
// Most rigid-step kernel chain leaves are reads of input parameters (mass, inertia, joint params,
150-
// morphology) that the kernel does NOT write. For those, the re-read returns the same value.
151-
// Conservative analysis: a future safety check could prove the SNode is read-only in the kernel; until
152-
// then this path relies on the test suite's gradient-correctness asserts to surface any mutated-global
153-
// miscompilation.
144+
// Chain leaves that pass this branch are typically reads of kernel-input parameters not written by the
145+
// kernel; for those, the re-read returns the same value as the forward read. The pass does not statically
146+
// verify SNode read-only-ness, and mutated-global cases would silently produce wrong gradients.
154147
bool is_interior = stmt->is<UnaryOpStmt>() || stmt->is<BinaryOpStmt>() || stmt->is<TernaryOpStmt>() ||
155148
stmt->is<MatrixPtrStmt>() || stmt->is<GlobalPtrStmt>() || stmt->is<ExternalPtrStmt>() ||
156149
stmt->is<GlobalLoadStmt>();
@@ -214,9 +207,8 @@ class RecomputableChainCloner {
214207
};
215208

216209
// ----------------------------------------------------------------------------
217-
// ADTransform: shared base for reverse-mode (MakeAdjoint) and forward-mode
218-
// (MakeDual) IR builders. All methods are inline so derived classes in
219-
// separate translation units can use them without ODR concerns.
210+
// ADTransform: shared base for reverse-mode (MakeAdjoint) and forward-mode (MakeDual) IR builders. Methods are inline
211+
// so derived classes in separate translation units can use them without ODR concerns.
220212
// ----------------------------------------------------------------------------
221213
class ADTransform : public IRVisitor {
222214
protected:

quadrants/transforms/auto_diff/eliminate_recomputable_pushes.cpp

Lines changed: 99 additions & 96 deletions
Large diffs are not rendered by default.

quadrants/transforms/auto_diff/forward_state_spill.cpp

Lines changed: 70 additions & 78 deletions
Large diffs are not rendered by default.

quadrants/transforms/auto_diff/forward_state_spill.h

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,23 @@ namespace quadrants::lang {
44

55
class Block;
66

7-
// Stage: Forward-state spill. Pre-MakeAdjoint passes that prepare the forward
8-
// IR so the reverse pass can re-read the per-iteration values it needs.
7+
// Stage: Forward-state spill. Pre-MakeAdjoint passes that prepare the forward IR so the reverse pass can re-read the
8+
// per-iteration values it needs.
99

10-
// Hoist forward-pass SSA defs that the reverse pass will re-read (operands of
11-
// non-linear ops, indices of GlobalPtr / ExternalPtr, range-for bounds, if
12-
// conds) into AllocaStmt + LocalStore + LocalLoad. Demand-driven: defs no
13-
// reverse formula reads stay in pure SSA form.
10+
// Hoist forward-pass SSA defs that the reverse pass will re-read (operands of non-linear ops, indices of GlobalPtr /
11+
// ExternalPtr, range-for bounds, if conds) into AllocaStmt + LocalStore + LocalLoad. Demand-driven: defs no reverse
12+
// formula reads stay in pure SSA form.
1413
void promote_ssa_to_local_var(Block *block);
1514

16-
// Replace each AllocaStmt the reverse pass needs to read across iterations
17-
// with an AdStackAllocaStmt + AdStackPushStmt / AdStackLoadTopStmt. The
18-
// AllocaJudger (file-private) decides which allocas need the promotion.
15+
// Replace each AllocaStmt the reverse pass needs to read across iterations with an AdStackAllocaStmt +
16+
// AdStackPushStmt / AdStackLoadTopStmt. The AllocaJudger (file-private) decides which allocas need the promotion.
1917
void replace_local_var_with_stacks(Block *block, int ad_stack_size);
2018

21-
// Drop AdStackAllocaStmts whose pushed value is recomputable from already-
22-
// stack-backed allocas + kernel args + constants + loop indices. Trades
23-
// forward-pass adstack memory traffic for cloned arithmetic in the reverse
24-
// scope, which `BackupSSA::generic_visit` synthesizes on demand via the
25-
// shared RecomputableChainCloner. Must run after `replace_local_var_with_stacks`
26-
// (so the analyzer sees the AdStack shape) and before `make_adjoint` (so the
27-
// reverse pass is generated against the cleaned forward IR).
19+
// Drop AdStackAllocaStmts whose pushed value is recomputable from already-stack-backed allocas + kernel args +
20+
// constants + loop indices. Trades forward-pass adstack memory traffic for cloned arithmetic in the reverse scope,
21+
// which `BackupSSA::generic_visit` synthesizes on demand via the shared RecomputableChainCloner. Must run after
22+
// `replace_local_var_with_stacks` (so the analyzer sees the AdStack shape) and before `make_adjoint` (so the reverse
23+
// pass is generated against the cleaned forward IR).
2824
void eliminate_recomputable_ad_stack_pushes(Block *ib);
2925

3026
} // namespace quadrants::lang

0 commit comments

Comments
 (0)