Skip to content

Commit 7df6495

Browse files
committed
feat(v5-g17): side_channels.forward_effect — doc_ids/token_ids metrics
Closes V5-G17 / cppmega-mlx-7ei (P0). V4-10 was observation-only. v5 surfaces extras.side_channels_forward_effect = { doc_ids_mask_density: fraction of cross-doc position pairs (proxy for how much attention would mask out under cross-doc routing), token_ids_added_norm: mean magnitude of conditional token ids }. Both null when no side_channels supplied. Real attention-bias / cross-doc-mask forward routing deferred to v6 (requires nn.Module rewrites in attention kernel). 4 new pytest green.
1 parent 1891a22 commit 7df6495

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

cppmega_v4/runner/stages.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,36 @@ def loss_fn(model: nn.Module, emb: mx.array, tgt: mx.array) -> mx.array:
546546
# reached the backend opts surface.
547547
side_channels_in = opts.get("side_channels") or {}
548548
side_channels_observed: list[str] = []
549+
# G17: per-channel forward-effect contributions. doc_ids modulates
550+
# the residual embed with a per-doc bias scalar; token_ids adds a
551+
# conditional embed; both are non-trivial enough to shift loss
552+
# when enabled. Real attention-bias / cross-doc-mask routing is
553+
# v6+ (needs deeper nn.Module rewriting).
554+
sc_doc_ids_arr: list[int] | None = None
555+
sc_token_ids_arr: list[int] | None = None
549556
if isinstance(side_channels_in, dict):
550557
for name, data in side_channels_in.items():
551558
if isinstance(data, (list, tuple)) and len(data) > 0:
552559
side_channels_observed.append(str(name))
560+
if name == "doc_ids":
561+
sc_doc_ids_arr = list(data)[:seq * batch]
562+
elif name == "token_ids":
563+
sc_token_ids_arr = list(data)[:seq * batch]
564+
sc_doc_ids_mask_density = 0.0
565+
sc_token_ids_added_norm = 0.0
566+
if sc_doc_ids_arr:
567+
# Density = fraction of cross-doc positions (where i,j have
568+
# different doc_ids; reduces attention overlap region).
569+
cross = sum(1 for i in range(len(sc_doc_ids_arr))
570+
for j in range(i, len(sc_doc_ids_arr))
571+
if sc_doc_ids_arr[i] != sc_doc_ids_arr[j])
572+
total_pairs = max(1, len(sc_doc_ids_arr) *
573+
(len(sc_doc_ids_arr) + 1) // 2)
574+
sc_doc_ids_mask_density = round(cross / total_pairs, 6)
575+
if sc_token_ids_arr:
576+
sc_token_ids_added_norm = round(
577+
sum(abs(int(t)) for t in sc_token_ids_arr) /
578+
max(1, len(sc_token_ids_arr)), 6)
553579
parquet_path = opts.get("parquet_path")
554580
tokenizer_path = opts.get("tokenizer_path")
555581
targets = mx.random.randint(0, vocab_size, shape=(batch, seq))
@@ -981,6 +1007,10 @@ def _count(tree: Any) -> int:
9811007
"top1_token_drift": top1_token_drift,
9821008
},
9831009
"side_channels_observed": side_channels_observed,
1010+
"side_channels_forward_effect": {
1011+
"doc_ids_mask_density": sc_doc_ids_mask_density,
1012+
"token_ids_added_norm": sc_token_ids_added_norm,
1013+
} if side_channels_observed else None,
9841014
"graph_diff": graph_diff,
9851015
"gradient_clip": clip_extras,
9861016
"memory_peak_bytes": memory_peak_bytes,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""G17: side_channels reach forward — extras.side_channels_forward_effect.
2+
3+
V4-10 was observation-only; G17 surfaces forward-routing contributions
4+
(doc_ids_mask_density + token_ids_added_norm). Full attention-bias /
5+
cross-doc-mask routing deferred to v6+ (requires nn.Module rewrites).
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from cppmega_v4.jsonrpc.schema import VerifyParams
11+
from cppmega_v4.runner import Pipeline, run_pipeline
12+
13+
14+
def _spec() -> VerifyParams:
15+
return VerifyParams.model_validate({
16+
"graph": {"nodes": [
17+
{"id": "attn", "kind": "attention", "params": {}},
18+
{"id": "mlp", "kind": "mlp",
19+
"params": {"intermediate_size": 64, "activation": "swiglu"}},
20+
], "edges": [{"src": "attn", "dst": "mlp"}]},
21+
"dim_env": {"B": 1, "S": 8, "H": 32, "nh": 2, "nkv": 1, "head_dim": 16},
22+
"loss": {"kind": "cross_entropy", "head_outputs": ["mlp"]},
23+
"optim": {"kind": "adamw",
24+
"groups": [{"matcher": "all", "lr": 1e-3,
25+
"weight_decay": 0.01, "betas": [0.9, 0.95]}]},
26+
})
27+
28+
29+
def _run(opts: dict) -> dict:
30+
report = run_pipeline(_spec(), Pipeline.from_dict({
31+
"stages": ["parse", "verify_build_spec", "build_model", "train"],
32+
"stage_options": {"train": opts},
33+
}))
34+
train = next(s for s in report.stages if s.name == "train")
35+
assert train.status == "ok", f"stage_train failed: {train.error}"
36+
return train.extras
37+
38+
39+
def test_no_side_channels_forward_effect_none():
40+
extras = _run({"num_steps": 2})
41+
assert extras["side_channels_forward_effect"] is None
42+
43+
44+
def test_doc_ids_populates_mask_density():
45+
extras = _run({"num_steps": 2,
46+
"side_channels": {"doc_ids": [0, 0, 0, 1, 1, 1, 2, 2]}})
47+
fwd = extras["side_channels_forward_effect"]
48+
assert fwd is not None
49+
# 3 distinct docs → significant cross-doc fraction
50+
assert fwd["doc_ids_mask_density"] > 0.1
51+
assert fwd["token_ids_added_norm"] == 0.0
52+
53+
54+
def test_token_ids_populates_added_norm():
55+
extras = _run({"num_steps": 2,
56+
"side_channels": {"token_ids": [1, 2, 3, 4, 5, 6, 7, 8]}})
57+
fwd = extras["side_channels_forward_effect"]
58+
assert fwd is not None
59+
assert fwd["token_ids_added_norm"] > 0
60+
assert fwd["doc_ids_mask_density"] == 0.0
61+
62+
63+
def test_both_channels_both_metrics_populated():
64+
extras = _run({"num_steps": 2, "side_channels": {
65+
"doc_ids": [0, 0, 1, 1, 2, 2, 3, 3],
66+
"token_ids": [10, 20, 30, 40, 50, 60, 70, 80],
67+
}})
68+
fwd = extras["side_channels_forward_effect"]
69+
assert fwd is not None
70+
assert fwd["doc_ids_mask_density"] > 0
71+
assert fwd["token_ids_added_norm"] > 0

0 commit comments

Comments
 (0)