Skip to content

Commit e5eb966

Browse files
justinchubyCopilot
andauthored
Improve arch diff dtype reporting and Gemma4 test coverage (#273)
## Changes ### Graph diff improvements (`_graph_diff.py`) - **Interface change details**: Show dtype and shape specifics instead of generic 'input[N] changed'. Example: `input[3]: dtype BOOL → FLOAT; shape [?, 1, ?, ?] → [?, ?]` - **Initializer dtype distribution**: Show per-dtype count changes instead of just total count. Example: `FLOAT16: 42 → 38, FLOAT: 0 → 4` — makes it easy to spot when a PR changes the compute precision of weights. ### Test config improvements (`_test_configs.py`) - Add `sliding_window=8` to both `gemma4_text` and `gemma4` multimodal test configs. This exercises the sliding window mask generation path in L1 tests, which was previously untested by the parametrized suite. ### Testing - 2724 tests pass, 0 failures - All 15 Gemma4 tests pass - All 27 graph diff tests pass Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2183a84 commit e5eb966

2 files changed

Lines changed: 35 additions & 3 deletions

File tree

src/mobius/_graph_diff.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@ def canonicalize_graph(graph: ir.Graph) -> dict:
191191
# =====================================================================
192192

193193

194+
def _describe_port_diff(base_port: dict, head_port: dict) -> str:
195+
"""Describe what changed between two I/O port dicts (dtype, shape)."""
196+
parts: list[str] = []
197+
bd, hd = base_port.get("dtype", "?"), head_port.get("dtype", "?")
198+
if bd != hd:
199+
parts.append(f"dtype {bd}{hd}")
200+
bs, hs = base_port.get("shape", []), head_port.get("shape", [])
201+
if bs != hs:
202+
parts.append(f"shape {bs}{hs}")
203+
return "; ".join(parts) or "changed"
204+
205+
194206
def diff_graphs(base: dict, head: dict) -> list[dict[str, Any]]:
195207
"""Compare two canonical graph representations.
196208
@@ -217,10 +229,12 @@ def diff_graphs(base: dict, head: dict) -> list[dict[str, Any]]:
217229
# Check for dtype/shape changes on matched ports
218230
for idx, (bv, hv) in enumerate(zip(b_in, h_in)):
219231
if bv != hv:
220-
details_parts.append(f"input[{idx}] changed")
232+
diffs = _describe_port_diff(bv, hv)
233+
details_parts.append(f"input[{idx}]: {diffs}")
221234
for idx, (bv, hv) in enumerate(zip(b_out, h_out)):
222235
if bv != hv:
223-
details_parts.append(f"output[{idx}] changed")
236+
diffs = _describe_port_diff(bv, hv)
237+
details_parts.append(f"output[{idx}]: {diffs}")
224238
changes.append(
225239
{
226240
"type": "interface_change",
@@ -232,10 +246,26 @@ def diff_graphs(base: dict, head: dict) -> list[dict[str, Any]]:
232246
b_inits = base.get("initializers", [])
233247
h_inits = head.get("initializers", [])
234248
if b_inits != h_inits:
249+
details_parts_init: list[str] = []
250+
if len(b_inits) != len(h_inits):
251+
details_parts_init.append(f"count {len(b_inits)}{len(h_inits)}")
252+
# Summarize dtype distribution changes
253+
from collections import Counter
254+
255+
b_dtypes = Counter(i["dtype"] for i in b_inits)
256+
h_dtypes = Counter(i["dtype"] for i in h_inits)
257+
if b_dtypes != h_dtypes:
258+
dtype_parts = []
259+
for dt in sorted(set(b_dtypes) | set(h_dtypes)):
260+
bc, hc = b_dtypes.get(dt, 0), h_dtypes.get(dt, 0)
261+
if bc != hc:
262+
dtype_parts.append(f"{dt}: {bc}{hc}")
263+
if dtype_parts:
264+
details_parts_init.append("dtype distribution: " + ", ".join(dtype_parts))
235265
changes.append(
236266
{
237267
"type": "initializer_change",
238-
"details": (f"initializer count {len(b_inits)}{len(h_inits)}"),
268+
"details": "; ".join(details_parts_init) or "initializers changed",
239269
}
240270
)
241271

tests/_test_configs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ def _base_config(config_cls=None, **overrides) -> ArchitectureConfig:
326326
"rope_local_base_freq": 10_000.0,
327327
# 2-layer test: 1 sliding + 1 full (must match TINY_LAYERS=2)
328328
"layer_types": ["sliding_attention", "full_attention"],
329+
"sliding_window": 8,
329330
"global_head_dim": TINY_HEAD_DIM,
330331
"global_rope_theta": 10_000.0,
331332
"final_logit_softcapping": 30.0,
@@ -2055,6 +2056,7 @@ def _base_config(config_cls=None, **overrides) -> ArchitectureConfig:
20552056
"attn_qk_norm": True,
20562057
"rope_local_base_freq": 10_000.0,
20572058
"layer_types": ["sliding_attention", "full_attention"],
2059+
"sliding_window": 8,
20582060
"global_head_dim": TINY_HEAD_DIM,
20592061
"global_rope_theta": 10_000.0,
20602062
"global_partial_rotary_factor": 0.25,

0 commit comments

Comments
 (0)