feat(ml-alpha): TFT GRN forward+backward kernels for multi-horizon heads (Phase 1.7a)
Per-horizon GRN structure (Lim et al. 2021 §3.3 adapted to scalar output):
eta_2[k, m] = GELU(W1[k, m, :] @ h + b1[k, m]) # [HIDDEN] → [HEAD_MID]
eta_1[k, m] = W2[k, m, :] @ eta_2[k, :] + b2[k, m] # [HEAD_MID] → [HEAD_MID]
gate_lin[k] = W_gate[k, :] @ eta_1[k, :] + b_gate[k] # → scalar
main[k] = W_main[k, :] @ eta_1[k, :] + b_main[k] # → scalar
skip[k] = W_skip[k, :] @ h + b_skip[k] # [HIDDEN] → scalar
logit[k] = skip[k] + sigmoid(gate_lin[k]) * main[k]
p[k] = sigmoid(logit[k])
Gated residual lets each per-horizon head learn "linear vs deeper-transform"
gating, matching the regime-conditional alpha pattern from
pearl_snapshot_alpha_is_regime_conditional (~20% of book states carry the
edge; spread-Q4 hits 75% acc, middle quintiles below chance).
Backward chain rule covers all 10 parameter tensors + the trunk gradient
(skip-path direct + main-path through W2→GELU→W1, lambda-scaled).
Single-writer discipline (no atomicAdd per feedback_no_atomicadd.md):
- Thread m owns row m of grad_w1 (col i in 0..HIDDEN), row m of grad_w2
(col m_in in 0..HEAD_MID), and column m of d_eta_2.
- Threads 0..4 own per-horizon scalar grads (skip/gate/main biases).
- Trunk grad_h tiles i over 2 strides of HEAD_MID for HIDDEN=128 coverage.
Shared mem: ~6.5KB (s_a1 + s_z2 + s_d_eta1 + s_d_eta2 + s_d_z1 + scalars),
well within 48KB limit.
Existing 2-layer MLP kernels (Tasks 1.3/1.4) stay in the cubin as
ablation baseline; the wired path becomes GRN once perception.rs lands.
build.rs cache-bust → v7.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>