Skip to content

Commit 5780f3f

Browse files
physicsrobclaude
andcommitted
compile_headless: one signature, one forward_compile call, schedule parity
New convention: compile_headless(graph, pos_encoding, *, ...) where graph is an output Node (node mode: natural-column gather) or an io dict (overlay mode: outputs land at input columns). The isinstance(first_arg, PosEncoding) sniffing, the io=/output_node= keywords, and the forked ~80-line _compile_headless_legacy are gone; the two modes share one forward_compile call (node mode passes overlays=None — identical to the old call) with thin pre/post branches. A PosEncoding first arg raises a dedicated TypeError naming the new order (PosEncoding IS a Node, so this check precedes the Node branch). Schedule parity: optimize and assume_zero_init now thread to forward_compile (defaults 0/False == forward_compile's own defaults, so existing behavior is unchanged) — the in-process debug backend can finally reproduce a production optimize=2 schedule. Also max_layers 100->400 (pure cap at optimize=0) and verbose True->False, matching the exporters. Call sites flipped to (io, pos) order: test_io_api.py (plus the three API-shape tests rewritten for the new convention), test_residual_nonzero_init.py, test_probe.py. Legacy (node, pos) sites — including probe_graph and doom's compiled_model — work unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4625edd commit 5780f3f

4 files changed

Lines changed: 161 additions & 207 deletions

File tree

tests/compile/forward/test_io_api.py

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
"""Tests for the new io API in compile_headless.
1+
"""Tests for the io-dict form of compile_headless.
22
3-
The io API enables overlaid I/O where output values land at input columns
3+
The io form enables overlaid I/O where output values land at input columns
44
via delta transfer, enabling autoregressive feedback where the transformer
55
output IS the next input.
66
"""
@@ -27,8 +27,8 @@ def test_io_identity():
2727
x = create_input(4)
2828

2929
module = compile_headless(
30+
{"x": (x, x)}, # output = input
3031
pos,
31-
io={"x": (x, x)}, # output = input
3232
d=64,
3333
verbose=False,
3434
)
@@ -45,8 +45,8 @@ def test_io_overlaid_single():
4545
y = multiply_const(x, 2.0) # y = 2*x
4646

4747
module = compile_headless(
48+
{"x": (x, y)}, # x -> y, overlaid
4849
pos,
49-
io={"x": (x, y)}, # x -> y, overlaid
5050
d=64,
5151
verbose=False,
5252
)
@@ -65,8 +65,8 @@ def test_io_overlaid_add_const():
6565
y = add_const(x, 10.0) # y = x + 10
6666

6767
module = compile_headless(
68+
{"x": (x, y)},
6869
pos,
69-
io={"x": (x, y)},
7070
d=64,
7171
verbose=False,
7272
)
@@ -88,11 +88,11 @@ def test_io_overlaid_multiple():
8888
out_b = add_const(b, 1.0)
8989

9090
module = compile_headless(
91-
pos,
92-
io={
91+
{
9392
"a": (a, out_a), # Overlaid
9493
"b": (b, out_b), # Overlaid
9594
},
95+
pos,
9696
d=64,
9797
verbose=False,
9898
)
@@ -115,11 +115,11 @@ def test_io_input_only_not_in_output():
115115
out_b = multiply_const(b, 2.0)
116116

117117
module = compile_headless(
118-
pos,
119-
io={
118+
{
120119
"a": (a, None), # Input-only: NOT in output
121120
"b": (b, out_b), # Overlaid
122121
},
122+
pos,
123123
d=64,
124124
verbose=False,
125125
)
@@ -142,11 +142,11 @@ def test_io_with_overflow():
142142
out_extra = create_literal_value(torch.tensor([99.0]))
143143

144144
module = compile_headless(
145-
pos,
146-
io={
145+
{
147146
"a": (a, out_a), # Overlaid at columns [0:2]
148147
"extra": (None, out_extra), # Overflow at column [2]
149148
},
149+
pos,
150150
d=64,
151151
verbose=False,
152152
)
@@ -166,8 +166,8 @@ def test_io_autoregressive():
166166
next_state = add_const(state, 1.0)
167167

168168
module = compile_headless(
169+
{"state": (state, next_state)},
169170
pos,
170-
io={"state": (state, next_state)},
171171
d=64,
172172
verbose=False,
173173
)
@@ -191,11 +191,11 @@ def test_io_uses_input_value():
191191
out_xy = add(x, y)
192192

193193
module = compile_headless(
194-
pos,
195-
io={
194+
{
196195
"x": (x, out_xy), # Overlaid: output x+y at x's columns
197196
"y": (y, None), # Input-only
198197
},
198+
pos,
199199
d=64,
200200
verbose=False,
201201
)
@@ -221,15 +221,15 @@ def test_io_width_mismatch_error():
221221
out_a = create_literal_value(torch.zeros(2)) # Wrong width
222222

223223
with pytest.raises(ValueError, match="width"):
224-
compile_headless(pos, io={"a": (a, out_a)}, d=64, verbose=False)
224+
compile_headless({"a": (a, out_a)}, pos, d=64, verbose=False)
225225

226226

227227
def test_io_empty_tuple_error():
228228
"""Each io entry must have at least one node."""
229229
pos = create_pos_encoding()
230230

231231
with pytest.raises(ValueError, match="both input and output as None"):
232-
compile_headless(pos, io={"a": (None, None)}, d=64, verbose=False)
232+
compile_headless({"a": (None, None)}, pos, d=64, verbose=False)
233233

234234

235235
def test_io_no_output_error():
@@ -238,21 +238,21 @@ def test_io_no_output_error():
238238
x = create_input(4)
239239

240240
with pytest.raises(ValueError, match="at least one output"):
241-
compile_headless(pos, io={"x": (x, None)}, d=64, verbose=False)
241+
compile_headless({"x": (x, None)}, pos, d=64, verbose=False)
242242

243243

244244
# ---------------------------------------------------------------------------
245-
# Legacy API compatibility tests
245+
# Calling-convention tests
246246
# ---------------------------------------------------------------------------
247247

248248

249-
def test_legacy_api_still_works():
250-
"""Legacy output_node API should still work."""
249+
def test_node_mode():
250+
"""A bare output Node compiles in node mode (natural-column gather)."""
251251
pos = create_pos_encoding()
252-
x = create_input("x", 4) # Legacy: named input
252+
x = create_input("x", 4) # node mode: named input
253253
y = multiply_const(x, 2.0)
254254

255-
module = compile_headless(pos, output_node=y, d=64, verbose=False)
255+
module = compile_headless(y, pos, d=64, verbose=False)
256256

257257
inp = torch.tensor([[1.0, 2.0, 3.0, 4.0]])
258258
out = module(inp)
@@ -261,22 +261,23 @@ def test_legacy_api_still_works():
261261
assert torch.allclose(out, expected, atol=0.1)
262262

263263

264-
def test_cannot_specify_both_io_and_output_node():
265-
"""Cannot specify both io and output_node."""
264+
def test_output_node_keyword_rejected():
265+
"""The removed output_node= keyword raises TypeError."""
266266
pos = create_pos_encoding()
267267
x = create_input(4)
268268
y = multiply_const(x, 2.0)
269269

270-
with pytest.raises(ValueError, match="Cannot specify both"):
271-
compile_headless(pos, io={"x": (x, y)}, output_node=y, d=64, verbose=False)
270+
with pytest.raises(TypeError):
271+
compile_headless({"x": (x, y)}, pos, output_node=y, d=64, verbose=False)
272272

273273

274-
def test_must_specify_io_or_output_node():
275-
"""Must specify either io or output_node."""
274+
def test_pos_encoding_first_rejected_with_hint():
275+
"""The old (pos_encoding, io) order raises a TypeError naming the fix."""
276276
pos = create_pos_encoding()
277+
x = create_input(4)
277278

278-
with pytest.raises(ValueError, match="Either io or output_node"):
279-
compile_headless(pos, d=64, verbose=False)
279+
with pytest.raises(TypeError, match="graph, pos_encoding"):
280+
compile_headless(pos, {"x": (x, x)}, d=64, verbose=False)
280281

281282

282283
# ---------------------------------------------------------------------------
@@ -292,8 +293,8 @@ def test_anonymous_input_with_io():
292293
assert x.name == "" # Verify it's anonymous
293294

294295
module = compile_headless(
296+
{"my_input": (x, x)}, # Name comes from io key
295297
pos,
296-
io={"my_input": (x, x)}, # Name comes from io key
297298
d=64,
298299
verbose=False,
299300
)

tests/compile/forward/test_residual_nonzero_init.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_nonzero_init_intermediate_col(monkeypatch):
7373
x = create_input(4)
7474
y = multiply_const(x, 2.0)
7575

76-
module = compile_headless(pos, io={"x": (x, y)}, d=64, verbose=False)
76+
module = compile_headless({"x": (x, y)}, pos, d=64, verbose=False)
7777
inp = torch.tensor([[1.0, 2.0, 3.0, 4.0]])
7878
expected = torch.tensor([[2.0, 4.0, 6.0, 8.0]])
7979

@@ -100,8 +100,8 @@ def test_nonzero_init_overflow_output(monkeypatch):
100100
y = add_const(x, 10.0)
101101

102102
module = compile_headless(
103+
{"a": (x, None), "b": (None, y)},
103104
pos,
104-
io={"a": (x, None), "b": (None, y)},
105105
d=64,
106106
verbose=False,
107107
)
@@ -134,7 +134,7 @@ def test_nonzero_init_mlp_path(monkeypatch):
134134
x = create_input(4)
135135
y = relu_add(x, multiply_const(x, -1.0)) # ReLU(x) + ReLU(-x) = |x|
136136

137-
module = compile_headless(pos, io={"x": (x, y)}, d=128, verbose=False)
137+
module = compile_headless({"x": (x, y)}, pos, d=128, verbose=False)
138138
inp = torch.tensor([[1.0, -2.0, 3.0, -4.0]])
139139
expected = torch.tensor([[1.0, 2.0, 3.0, 4.0]])
140140

tests/debug/test_probe.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def test_probe_residual_reads_intermediate_node():
7373
y = multiply_const(x, 3.0)
7474
z = add_const(y, 1.0)
7575
module = compile_headless(
76+
{"x": (x, z)},
7677
pos,
77-
io={"x": (x, z)},
7878
d=64,
7979
verbose=False,
8080
)
@@ -115,8 +115,8 @@ def test_probe_attention_captures_softmax_weights():
115115
output_matrix=torch.randn(4, 4),
116116
)
117117
module = compile_headless(
118+
{"x": (x, attn)},
118119
pos,
119-
io={"x": (x, attn)},
120120
d=256,
121121
d_head=16,
122122
verbose=False,
@@ -161,8 +161,8 @@ def test_probe_layer_diff_drift_and_sentinel():
161161
x = create_input("x", 2)
162162
y = multiply_const(x, 3.0)
163163
module = compile_headless(
164+
{"x": (x, y)},
164165
pos,
165-
io={"x": (x, y)},
166166
d=64,
167167
verbose=False,
168168
)

0 commit comments

Comments
 (0)