Skip to content

Commit b73198b

Browse files
feat(mac fused chat): --force-f-theta — f_θ actually RUNS each turn (even if recall-irrelevant on gemma-4)
User: f_θ must execute even if its output is discarded by the verifier attention. - build_restoration: --force-f-theta bypasses the S5 native-prefill short-circuit (the line that stopped f_θ running under --s5-exact-full-attn), so f_θ projects proposer hidden -> verifier K/V and injects it into the sliding layers. - _gen_turn now branches over all-mlx (proposer; f_θ bypassed) AND torch drafter+f_θ (proposer + f_θ runs) paths; reports f_theta_ran + f_theta_layers. - new preset mlx-kakeya-fused-chat-ftheta (torch path + --force-f-theta). On gemma-4 the restored sliding K/V are recall-irrelevant (exact layers carry recall) — f_θ still EXECUTES, exercising the full verifier/proposer/f_θ pipeline. Co-authored-by: FluffyAIcode <FluffyAIcode@users.noreply.github.com>
1 parent a6dd2e2 commit b73198b

3 files changed

Lines changed: 103 additions & 13 deletions

File tree

inference_engine/bridge/manifest.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,42 @@ def _harness_preset(
713713
},
714714
validate_reports=False,
715715
),
716+
Preset(
717+
name="mlx-kakeya-fused-chat-ftheta",
718+
description="Like mlx-kakeya-fused-chat-smoke but on the TORCH drafter "
719+
"+ f_θ path with --force-f-theta: f_θ restoration ACTUALLY "
720+
"RUNS each turn (projects proposer hidden → verifier K/V, "
721+
"injected into the sliding layers) even though on gemma-4 "
722+
"those K/V are recall-irrelevant (the exact layers carry "
723+
"recall). Verifies the FULL verifier/proposer/f_θ pipeline: "
724+
"report shows f_theta_ran=true + blocks>0. (No "
725+
"--all-mlx-drafter; torch bridge path is slower.)",
726+
command_templates=(
727+
(
728+
"python3", "scripts/research/k3_integrated_niah_eval_mac.py",
729+
"--verifier-path", "${ENV:KAKEYA_MAC_VERIFIER_PATH}",
730+
"--drafter-id", "${ENV:KAKEYA_MAC_DRAFTER_ID}",
731+
"--f-theta-dir", "${ENV:KAKEYA_MAC_FTHETA_DIR}",
732+
"--s5-exact-full-attn", "--fused-specdecode", "--force-f-theta",
733+
"--sink-size", "4", "--window-size", "64",
734+
"--block-size", "{block_size}",
735+
"--max-new-tokens", "{max_new_tokens}",
736+
"--prefill-chunk-size", "512",
737+
"--chat",
738+
"--chat-scripted",
739+
"What is the capital of France? Answer in one short sentence."
740+
"||Name three primary colors.",
741+
"--output",
742+
"results/research/k3_mac_bridge_mlx_kakeya_fused_chat_ftheta.json",
743+
),
744+
),
745+
timeout_minutes=90,
746+
params={
747+
"max_new_tokens": ("int:max_new_tokens", "32"),
748+
"block_size": ("int:block_size", "4"),
749+
},
750+
validate_reports=False,
751+
),
716752
)
717753
}
718754

scripts/research/k3_integrated_niah_eval_mac.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ def parse_args() -> argparse.Namespace:
180180
ap.add_argument("--chat-scripted", default=None,
181181
help="Non-interactive chat: '||'-separated user turns "
182182
"(for Mac-bridge verification); writes a transcript.")
183+
ap.add_argument("--force-f-theta", action="store_true",
184+
help="Run f_θ restoration even under --s5-exact-full-attn "
185+
"(bypass the S5 native-prefill short-circuit). On gemma-4 "
186+
"the restored sliding-layer K/V are recall-irrelevant "
187+
"(the exact layers carry recall), but f_θ EXECUTES and "
188+
"its output is injected — exercising the full verifier/"
189+
"proposer/f_θ pipeline. Requires the torch drafter+f_θ "
190+
"(do NOT combine with --all-mlx-drafter).")
183191
return ap.parse_args()
184192

185193

@@ -367,7 +375,8 @@ def build_restoration(prompt_ids: List[int], *, prefill_native_s5: bool = False)
367375
restored bank for those layers lets mlx_lm store their own post-RoPE
368376
cache directly and avoids the extra clean verifier forward.
369377
"""
370-
if prefill_native_s5 and args.s5_exact_full_attn and not args.identity_restore:
378+
if (prefill_native_s5 and args.s5_exact_full_attn
379+
and not args.identity_restore and not args.force_f_theta):
371380
return {}, {}, len(prompt_ids)
372381
if drafter is None or f_theta is None or fcfg is None:
373382
raise RuntimeError("drafter/f_theta are required for this restoration mode")
@@ -709,12 +718,16 @@ def _run_fused_chat() -> Tuple[List[str], List[float], List[int]]:
709718
accepts (reports blocks / mean_accept_len). On gemma-4 f_θ restoration
710719
is bypassed via S5 native exact-layer prefill (the free lunch);
711720
f_θ is load-bearing on full-attention models."""
712-
if not (args.force_fused_specdecode and mlx_drafter is not None
713-
and args.cuda_trim):
721+
_allmlx_ok = mlx_drafter is not None and args.cuda_trim
722+
_torch_ftheta_ok = drafter is not None and f_theta is not None
723+
if not (args.force_fused_specdecode and (_allmlx_ok or _torch_ftheta_ok)):
714724
raise SystemExit(
715-
"--chat needs the FULL fused engine flags: --fused-specdecode "
716-
"--force-fused-specdecode --all-mlx-drafter --s5-exact-full-attn "
717-
"--cuda-trim")
725+
"--chat needs the FULL fused engine. Either:\n"
726+
" (a) --fused-specdecode --all-mlx-drafter --s5-exact-full-attn "
727+
"--cuda-trim (verifier + proposer; f_θ bypassed on gemma-4 "
728+
"via S5), or\n"
729+
" (b) --fused-specdecode --force-f-theta (torch DFlash drafter "
730+
"+ f_θ that ACTUALLY RUNS; do NOT pass --all-mlx-drafter).")
718731
# Stop at gemma's natural turn end: <end_of_turn> + eos
719732
# (convert_tokens_to_ids is the reliable special-token lookup).
720733
chat_eos = set(end_ids)
@@ -741,23 +754,45 @@ def _encode_chat(history: List[Dict[str, str]]) -> List[int]:
741754

742755
def _gen_turn(pid: List[int]) -> Dict[str, Any]:
743756
rk, rv, tsrc = build_restoration(pid, prefill_native_s5=True)
757+
# f_θ ran iff build_restoration produced restored banks via the
758+
# torch drafter+f_θ (under --force-f-theta the S5 short-circuit is
759+
# bypassed → rk holds f_θ-projected sliding-layer K/V).
760+
f_theta_ran = bool(rk) and (drafter is not None and f_theta is not None)
744761
T = len(pid)
745762
evicted = compute_evicted_positions(
746763
T, args.sink_size, args.window_size)
747-
aux_prompt = capture_aux_hidden(
764+
aux_prompt_mx = capture_aux_hidden(
748765
mlx_model, pid, aux_layer_ids, embed_scale=embed_scale)
766+
aux_prompt = (aux_prompt_mx if bridge is None
767+
else [bridge(a) for a in aux_prompt_mx])
749768
adapter.prefill(
750769
pid, restored_k_per_layer=_pad(rk, tsrc, T),
751770
restored_v_per_layer=_pad(rv, tsrc, T),
752771
evicted_positions=evicted,
753-
prefill_chunk_size=args.prefill_chunk_size, full_kv=True)
772+
prefill_chunk_size=args.prefill_chunk_size, full_kv=args.cuda_trim)
754773
t0 = time.perf_counter()
755-
res = fused_specdecode_generate_mlx_trim(
756-
adapter, active_drafter, aux_prompt=aux_prompt,
757-
embed_fn=embed_fn, lm_head_fn=lm_head_fn,
758-
gen_tokens=args.max_new_tokens, block_size=args.block_size,
759-
eos_ids=chat_eos, single_fused=args.single_fused)
774+
if mlx_drafter is not None and args.cuda_trim:
775+
res = fused_specdecode_generate_mlx_trim(
776+
adapter, active_drafter, aux_prompt=aux_prompt,
777+
embed_fn=embed_fn, lm_head_fn=lm_head_fn,
778+
gen_tokens=args.max_new_tokens, block_size=args.block_size,
779+
eos_ids=chat_eos, single_fused=args.single_fused)
780+
elif mlx_drafter is not None:
781+
res = fused_specdecode_generate_mlx(
782+
adapter, active_drafter, aux_prompt=aux_prompt,
783+
embed_fn=embed_fn, lm_head_fn=lm_head_fn,
784+
gen_tokens=args.max_new_tokens, block_size=args.block_size,
785+
eos_ids=chat_eos)
786+
else:
787+
res = fused_specdecode_generate(
788+
adapter, active_drafter, aux_prompt=aux_prompt,
789+
embed_fn=embed_fn, lm_head_fn=lm_head_fn,
790+
gen_tokens=args.max_new_tokens, block_size=args.block_size,
791+
eos_ids=chat_eos, argmax_fn=argmax_fn, arange_fn=arange_fn,
792+
cat_aux_fn=cat_aux_fn, allow_greedy_fallback=False)
760793
res["decode_s"] = round(time.perf_counter() - t0, 3)
794+
res["f_theta_ran"] = f_theta_ran
795+
res["f_theta_layers"] = sorted(rk.keys()) if rk else []
761796
try:
762797
txt = tokenizer.decode(res["tokens"], skip_special_tokens=True)
763798
except TypeError:
@@ -789,11 +824,15 @@ def _gen_turn(pid: List[int]) -> Dict[str, Any]:
789824
"user": u, "text": res["text"],
790825
"tokens": res["decode_tokens"], "blocks": res["blocks"],
791826
"mean_accept_len": res["mean_accept_len"],
827+
"f_theta_ran": res["f_theta_ran"],
828+
"f_theta_layers": res["f_theta_layers"],
792829
"decode_s": res["decode_s"], "decode_tps": round(tps, 2),
793830
"resident_kv_bytes": res["resident_kv_bytes"]})
794831
print(f"[chat] USER {u!r}", file=sys.stderr, flush=True)
795832
print(f"[chat] GEMMA-4 {res['text'][:200]!r} (blocks="
796833
f"{res['blocks']}, accept_len={res['mean_accept_len']}, "
834+
f"f_theta_ran={res['f_theta_ran']} "
835+
f"layers={res['f_theta_layers']}, "
797836
f"{round(tps,2)} tok/s, kv={res['resident_kv_bytes']/1e6:.1f}MB)",
798837
file=sys.stderr, flush=True)
799838
report = {

tests/inference_engine/bridge/test_manifest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def test_allowlist_contains_exactly_the_documented_presets():
8181
"mlx-batched-pad-decode",
8282
"mlx-env-probe",
8383
"mlx-kakeya-chat-smoke",
84+
"mlx-kakeya-fused-chat-ftheta",
8485
"mlx-kakeya-fused-chat-smoke",
8586
"mlx-multitenant-pressure",
8687
"mlx-upgrade",
@@ -149,6 +150,20 @@ def test_mlx_kakeya_chat_smoke_preset_resolves():
149150
assert not [t for t in argv if t.startswith("{") and t.endswith("}")]
150151

151152

153+
def test_mlx_kakeya_fused_chat_ftheta_preset_runs_f_theta_path():
154+
request = parse_manifest(_manifest(
155+
preset="mlx-kakeya-fused-chat-ftheta",
156+
params={"max_new_tokens": "32", "block_size": "4"}))
157+
(argv,) = build_commands(request, HARNESS_ENV)
158+
assert argv[1].endswith("k3_integrated_niah_eval_mac.py")
159+
# torch drafter + f_θ path: --force-f-theta, and NOT --all-mlx-drafter
160+
assert "--force-f-theta" in argv
161+
assert "--fused-specdecode" in argv
162+
assert "--all-mlx-drafter" not in argv
163+
assert HARNESS_ENV["KAKEYA_MAC_FTHETA_DIR"] in argv
164+
assert HARNESS_ENV["KAKEYA_MAC_DRAFTER_ID"] in argv
165+
166+
152167
def test_mlx_kakeya_fused_chat_smoke_preset_resolves():
153168
request = parse_manifest(_manifest(
154169
preset="mlx-kakeya-fused-chat-smoke",

0 commit comments

Comments
 (0)