Skip to content

Commit 1bb242a

Browse files
author
Codex
committed
Path C: scale fused suffix VJP cotangents
1 parent d8607e9 commit 1bb242a

2 files changed

Lines changed: 49 additions & 6 deletions

File tree

cppmega_mlx/training/path_c_fused_suffix.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,6 @@ def fused_suffix_vjp(
175175
loss_cotan = cotangents[0]
176176
else:
177177
loss_cotan = cotangents
178-
del loss_cotan # The fused backward already ran inside forward with
179-
# ``d_loss/d_loss = 1``; trainer-side loss scaling is handled by the
180-
# caller (``CompiledPretrainingStep`` keeps loss unscaled). If a
181-
# future caller scales the loss, the bank cotangents would need to
182-
# scale accordingly; we surface the assumption explicitly via the
183-
# runtime contract.
184178
hidden_entry_primal = primals[0]
185179
target_ids_primal = primals[1]
186180
target_mask_primal = primals[2]
@@ -206,6 +200,7 @@ def fused_suffix_vjp(
206200
hidden_entry_cotan = mx.reshape(
207201
hidden_entry_cotan, hidden_entry_primal.shape
208202
)
203+
hidden_entry_cotan = hidden_entry_cotan * loss_cotan
209204

210205
target_ids_cotan = _zeros_like_with_cotangent_dtype(
211206
target_ids_primal
@@ -223,6 +218,7 @@ def fused_suffix_vjp(
223218
!= tuple(int(dim) for dim in tuple(primal.shape))
224219
):
225220
cotan = mx.reshape(cotan, primal.shape)
221+
cotan = cotan * loss_cotan
226222
param_cotans.append(cotan)
227223

228224
return (

tests/test_path_c_fused_suffix_custom_function.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,53 @@ def loss_fn(hidden_entry, param_a, param_b):
190190
assert grads[2].tolist() == [5.0, 6.0, 7.0, 8.0]
191191

192192

193+
def test_fused_suffix_vjp_scales_bank_view_cotangents_by_loss_cotangent() -> None:
194+
artifact = _RecordingArtifact(sentinel_loss=3.0, sentinel_ntokens=11.0)
195+
bank_owner = _FakeBankOwner()
196+
abi_map = _make_abi_map()
197+
in_region_aliases = {
198+
"p_a": {
199+
"logical_name": "param_a", "logical_grad_name": "param_a_grad",
200+
"bank": "f32", "dtype": "float32",
201+
"offset": 60, "size": 4, "logical_shape": (4,),
202+
},
203+
"p_b": {
204+
"logical_name": "param_b", "logical_grad_name": "param_b_grad",
205+
"bank": "f32", "dtype": "float32",
206+
"offset": 64, "size": 4, "logical_shape": (4,),
207+
},
208+
}
209+
f = build_fused_suffix_custom_function(
210+
artifact=artifact, bank_owner=bank_owner, abi_map=abi_map,
211+
hidden_entry_logical_name="hidden_entry",
212+
target_ids_logical_name="target_ids",
213+
target_mask_logical_name="target_mask",
214+
loss_logical_name="loss",
215+
ntokens_logical_name="ntokens",
216+
in_region_parameter_bank_aliases=in_region_aliases,
217+
parameter_order=("p_a", "p_b"),
218+
)
219+
220+
hidden_entry = mx.full((1, 3, 4), 1.0, dtype=mx.float32)
221+
target_ids = mx.zeros((3,), dtype=mx.float32)
222+
target_mask = mx.ones((3,), dtype=mx.float32)
223+
param_a = mx.zeros((4,), dtype=mx.float32)
224+
param_b = mx.zeros((4,), dtype=mx.float32)
225+
226+
def scaled_loss_fn(hidden_entry, param_a, param_b):
227+
loss, _ntokens = f(hidden_entry, target_ids, target_mask, param_a, param_b)
228+
return loss * mx.array(3.0, dtype=mx.float32)
229+
230+
grad_fn = mx.value_and_grad(scaled_loss_fn, argnums=(0, 1, 2))
231+
val, grads = grad_fn(hidden_entry, param_a, param_b)
232+
mx.eval(val, *grads)
233+
234+
assert float(val) == 9.0
235+
assert grads[0].tolist() == [[[21.0]*4]*3]
236+
assert grads[1].tolist() == [3.0, 6.0, 9.0, 12.0]
237+
assert grads[2].tolist() == [15.0, 18.0, 21.0, 24.0]
238+
239+
193240
def test_fused_suffix_rejects_wrong_primal_count() -> None:
194241
artifact = _RecordingArtifact(0.0, 0.0)
195242
bank_owner = _FakeBankOwner()

0 commit comments

Comments
 (0)