@@ -31,18 +31,48 @@ def main() -> int:
3131 ap .add_argument ("--haystack-lines" , type = int , default = 60 )
3232 ap .add_argument ("--max-new-tokens" , type = int , default = 24 )
3333 ap .add_argument ("--prefill-chunk" , type = int , default = 512 )
34+ ap .add_argument ("--kakeya-cache" , action = "store_true" ,
35+ help = "Build the batched cache from Kakeya's concat-based "
36+ "SinkWindowKVCache (avoids mlx_lm's in-place "
37+ "buffer-assignment decode path that breaks at batch>1). "
38+ "S5: full-attn layers keep all, sliding bounded." )
39+ ap .add_argument ("--window" , type = int , default = 64 ,
40+ help = "sliding-layer window when --kakeya-cache (S5)." )
41+ ap .add_argument ("--sink" , type = int , default = 4 )
42+ ap .add_argument ("--full-window" , type = int , default = 100000 ,
43+ help = "full-attn-layer window when --kakeya-cache "
44+ "(large = keep all, exact recall)." )
3445 ap .add_argument ("--output" , default = None )
3546 args = ap .parse_args ()
3647
3748 import mlx .core as mx
3849 import mlx_lm
3950 sys .path .insert (0 , "sdks/python" )
4051 from inference_engine .v04 import make_niah_dataset
52+ from inference_engine .backends .mlx .cache import SinkWindowKVCache
53+ from inference_engine .backends .mlx .cross_model_dlm_verifier import (
54+ resolve_mlx_text_model , mlx_full_attention_layer_indices ,
55+ )
4156
4257 print (f"[mlx-mt] loading { args .verifier_path } " , flush = True )
4358 model , tok = mlx_lm .load (args .verifier_path )
4459 N = args .sessions
4560
61+ text_model = resolve_mlx_text_model (model )
62+ full_idx = set (mlx_full_attention_layer_indices (text_model ))
63+
64+ def new_cache ():
65+ if not args .kakeya_cache :
66+ return model .make_cache ()
67+ # S5 hybrid via concat-based caches: full-attn layers keep all (large
68+ # window = exact recall), sliding layers bounded to sink+window.
69+ return [
70+ SinkWindowKVCache (sink_size = args .sink ,
71+ window_size = (args .full_window if li in full_idx
72+ else args .window ))
73+ for li in range (len (text_model .layers ))
74+ ]
75+
4676 def encode (text ):
4777 # Match the working Mac NIAH harness: neutral filler + a direct-answer
4878 # instruction, and append Gemma-4's content-channel marker so short
@@ -79,7 +109,7 @@ def recall(toks, ans):
79109
80110 def prefill_batched (ids_2d ):
81111 """Chunked batched prefill -> (cache, last_logits[N,V])."""
82- cache = model . make_cache ()
112+ cache = new_cache ()
83113 chunk = args .prefill_chunk
84114 T = len (ids_2d [0 ])
85115 last = None
@@ -134,6 +164,17 @@ def decode_batched(cache, logits, max_tokens):
134164 serial_tps = round ((N * args .max_new_tokens ) / ser_decode_s , 3 ) if ser_decode_s else 0.0
135165 serial_recall = sum (recall (g_s [i ], answers [i ]) for i in range (N )) / N
136166
167+ # Diagnostic: per-row batched-vs-serialized first token + recall, to
168+ # localize whether batched PREFILL diverges from serialized (batch-1).
169+ print ("[mlx-mt][diag] row | serial_tok0 | batched_tok0 | match | "
170+ "serial_recall | batched_recall" , flush = True )
171+ for i in range (N ):
172+ s0 = g_s [i ][0 ] if g_s [i ] else None
173+ b0 = g_b [i ][0 ] if g_b [i ] else None
174+ print (f"[mlx-mt][diag] { i :2d} | { s0 } | { b0 } | { s0 == b0 } | "
175+ f"{ recall (g_s [i ], answers [i ])} | { recall (g_b [i ], answers [i ])} " ,
176+ flush = True )
177+
137178 speedup = round (batched_tps / serial_tps , 2 ) if serial_tps else None
138179 report = {
139180 "kind" : "mlx_batched_multitenant" ,
0 commit comments