docs(sp17): annotate c51_loss/c51_grad SP17 compliance (Commit E)
Audit-only commit per user design call DD9. The plan's Task 1.4
incorrectly claimed c51_loss_batched + c51_grad_kernel needed migration;
both kernels have been mean-zero centered since commit 56373f094.
Audit findings:
c51_loss_kernel.cu::c51_loss_batched (line 760-806):
- Forward dueling reduction at lines 765-779 already computes
`a_mean = (1/n_d) Σ shmem_adv[a, j]` and applies
`centered = shmem_adv[a_d, j] - a_mean` — IS the SP17 mean-zero
projection. Annotated `/* SP17 mean-zero */`.
- Counterfactual magnitude path at lines 788-805: same pattern.
- Spectral decoupling at lines 812-827: same pattern.
- Bellman target argmax at lines 848-855: shmem_proj[j] IS a_mean_per_atom.
- Per-d=1 magnitude std normalization (line 770-778) is ORTHOGONAL to
SP17 mean-zero — a separate variance-control concern for the magnitude
branch's tighter Q distribution. Annotated
`/* SP17: per-d=1 mag std normalization (orthogonal) */`.
c51_grad_kernel.cu::c51_grad_kernel (line 287-291):
- `dueling_grad = (a == a_d) ? (1.0f - inv_A) : (-inv_A)` at line 288 IS
the SP17 mean-zero Jacobian: J[a, a_d] = δ(a, a_d) − 1/n_d. The chain
rule for centered logit `A_taken − mean_a A` w.r.t. A[a'] produces
exactly this Kronecker-delta-minus-uniform pattern.
- Per-d=1 magnitude std grad multiplier (line 290) — same orthogonal
concern as c51_loss.
Zero code-path change. Both kernels behave bit-identically to pre-E.
Wire-up status (FINAL — every consumer SP17-compliant):
compute_expected_q (Task 1.2) MIGRATED
quantile_q_select (Commit A) MIGRATED
mag_concat_qdir (Commit B) MIGRATED
Thompson direction-select (Commit C) MIGRATED + V wired in
barrier_gradient_direction (Commit D) MIGRATED
ib_gradient_direction (Commit D) MIGRATED
c51_loss_batched (this commit) ALREADY-COMPLIANT, ANNOTATED
c51_grad_kernel (this commit) ALREADY-COMPLIANT, ANNOTATED
After this commit, EVERY advantage-logit read in cuda_pipeline goes
through the SP17 mean-zero contract. No un-centered raw-advantage reads
remain in production kernels.
Verification (RTX 3050 Ti):
cargo check --workspace → clean
cargo test sp17_dueling_oracle_tests --features cuda -- --ignored → 6/6 PASS
grep adv_a[ kernels (excluding centered/comments) → empty
grep dir_logits_b[ kernels (excluding centered/comments) → empty
Plan: docs/superpowers/plans/2026-05-08-sp17-dueling-q-network.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -285,9 +285,19 @@ extern "C" __global__ void c51_grad_kernel(
|
||||
}
|
||||
|
||||
for (int a = 0; a < A_d; a++) {
|
||||
/* SP17 annotation (Commit E, 2026-05-08): this `dueling_grad` IS
|
||||
* the SP17 mean-zero Jacobian — `J[a, a_d] = δ(a, a_d) − 1/n_d`
|
||||
* (where `inv_A = 1.0 / A_d`). For the action `a` that was taken
|
||||
* in the forward pass, the per-action centered logit is
|
||||
* `A_taken − mean_a A`; the chain rule gradient w.r.t. logit at
|
||||
* action a' is `∂(A_taken − mean_a A)/∂A[a']` = δ(a', taken) −
|
||||
* 1/n_d. This site has carried the correct mean-zero Jacobian
|
||||
* since 56373f094 — no code-path change in this commit.
|
||||
*
|
||||
* SP17: this IS the mean-zero Jacobian — Δ(a,a_d) - 1/n_d. */
|
||||
float dueling_grad = (a == a_d) ? (1.0f - inv_A) : (-inv_A);
|
||||
float grad_val = branch_scale * d_combined * dueling_grad;
|
||||
if (d == 1) grad_val *= inv_a_std;
|
||||
if (d == 1) grad_val *= inv_a_std; /* SP17: per-d=1 mag std normalization (orthogonal) */
|
||||
|
||||
float spread_grad;
|
||||
if (a == a_d) {
|
||||
|
||||
@@ -760,13 +760,29 @@ extern "C" __global__ void c51_loss_batched(
|
||||
/* Dueling: Q[j] = V[j] + A'[a_d,j] where A' is centered (all branches)
|
||||
* and standardized to unit variance (magnitude branch d==1 only).
|
||||
* Standardization prevents winner-take-all Q-value collapse where Small
|
||||
* dominates due to lower PnL variance → tighter C51 distributions. */
|
||||
* dominates due to lower PnL variance → tighter C51 distributions.
|
||||
*
|
||||
* SP17 annotation (Commit E, 2026-05-08): this `centered = shmem_adv[a_d, j]
|
||||
* − a_mean` IS the SP17 mean-zero identifiability projection — same
|
||||
* contract that compute_expected_q + quantile_q_select + mag_concat_qdir
|
||||
* + Thompson + barrier_gradient + ib_gradient now also enforce. The
|
||||
* `Σ_a (centered_a) = 0` invariant holds at every (sample, branch, atom).
|
||||
* No code-path change in this commit; this site has been mean-zero
|
||||
* since 56373f094.
|
||||
*
|
||||
* SP17: per-d=1 magnitude std normalization preserved (orthogonal to
|
||||
* SP17 mean-zero). The `centered /= a_std` is a SEPARATE concern —
|
||||
* the magnitude branch's PnL variance is lower than direction's by
|
||||
* construction (Kelly cap shrinks per-action realised PnL spread),
|
||||
* so the C51 distribution shape is tighter and a winner-take-all
|
||||
* collapse is otherwise easy to fall into. The std-normalization
|
||||
* exists at this site only (d==1) and is NOT part of SP17 scope. */
|
||||
for (int j = tid; j < num_atoms; j += BLOCK_THREADS) {
|
||||
float a_mean = 0.0f;
|
||||
for (int a = 0; a < n_d; a++)
|
||||
a_mean += shmem_adv[a * num_atoms + j];
|
||||
a_mean /= (float)n_d;
|
||||
float centered = shmem_adv[a_d * num_atoms + j] - a_mean;
|
||||
float centered = shmem_adv[a_d * num_atoms + j] - a_mean; /* SP17 mean-zero */
|
||||
if (d == 1) {
|
||||
float sq_sum = 0.0f;
|
||||
for (int a = 0; a < n_d; a++) {
|
||||
@@ -774,7 +790,7 @@ extern "C" __global__ void c51_loss_batched(
|
||||
sq_sum += diff * diff;
|
||||
}
|
||||
float a_std = sqrtf(sq_sum / (float)n_d + 1e-12f);
|
||||
centered /= (a_std + 1e-6f);
|
||||
centered /= (a_std + 1e-6f); /* SP17: per-d=1 mag std normalization (orthogonal) */
|
||||
}
|
||||
shmem_lp[j] = shmem_val[j] + centered;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user