hypernet-sp-distill / mt_mlx.py
baya1116's picture
Super-squash branch 'main' using huggingface_hub
b5989f0
Raw
History Blame Contribute Delete
3.63 kB
"""Multi-turn bounded SP-evict chat in MLX (the 'long-CoT rally' the project wanted).
Persistent KV across turns but bounded: each chunk trims cache to the system prompt, prepends
32 SP (compressing the distant conversation) + recent raw window. User msg tokens are force-fed,
then the assistant samples its reply. Tests whether reasoning is carried across turns under bounded memory."""
import sys, os, time
import numpy as np
import mlx.core as mx
from mlx_lm.models.cache import make_prompt_cache
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import sp_mlx
import sys as _s
RW = int(_s.argv[1]) if len(_s.argv)>1 else 128
C, MAXD, TEMP, MAX_RESP = 64, 4096, 0.6, 500
M = sp_mlx.get()
model, tok, pooler, H, eos, embT, mdtype = (M["model"], M["tok"], M["pooler"], M["H"], M["eos"], M["embT"], M["mdtype"])
def emb_ids(ids):
return mx.zeros((1, 0, H), dtype=mdtype) if len(ids) == 0 else embT(mx.array([ids]))
def evict(kept):
if not MAXD or len(kept) <= MAXD:
return kept
_, mass = pooler.forward_with_mass(emb_ids(kept).astype(mx.float32)); mx.eval(mass)
m = np.array(mass)[0]; idx = np.sort(np.argsort(m)[-MAXD:])
return [kept[i] for i in idx]
SYSTEM = "You are a helpful math tutor. Reason briefly step by step, then give the final answer."
cache = make_prompt_cache(model)
sys_ids = tok.encode(SYSTEM)
model(mx.array([sys_ids]), cache=cache); MQ = cache[0].offset
gen, kept, absorbed = [], [], 0
print(f"SYSTEM seeded MQ={MQ}\n", flush=True)
def turn(user_msg):
global gen, kept, absorbed
feed = tok.encode(("<|end▁of▁sentence|>" if gen else "") + f"<|User|>{user_msg}<|Assistant|>", add_special_tokens=False)
fi, sampled, start, done = 0, 0, len(gen), False
t0 = time.time()
while not done:
c0 = len(gen); R = min(c0, RW); nd_end = c0 - R
if nd_end > absorbed:
kept.extend(gen[absorbed:nd_end]); absorbed = nd_end; kept = evict(kept)
sp = pooler.forward(emb_ids(kept).astype(mx.float32)).astype(mdtype)
parts = [sp] + ([emb_ids(gen[c0 - R:c0])] if R > 0 else [])
block = mx.concatenate(parts, axis=1)
for c in cache:
c.trim(c.offset - MQ)
logits = model(mx.zeros((1, block.shape[1]), dtype=mx.int32), cache=cache, input_embeddings=block)
last = logits[:, -1, :]
for _ in range(C):
if fi < len(feed):
t = feed[fi]; fi += 1
elif sampled < MAX_RESP:
t = int(mx.random.categorical(last * (1.0 / TEMP)).item()); sampled += 1
if t == eos:
done = True; break
else:
done = True; break
gen.append(t)
logits = model(mx.zeros((1, 1), dtype=mx.int32), cache=cache, input_embeddings=emb_ids([t]))
last = logits[:, -1, :]
if done:
break
reply = tok.decode(gen[start + len(feed):])
return reply, time.time() - t0
CONV = [
"I have 3 apples and then I buy 5 more. How many apples do I have?",
"If I now give away 2 apples, how many do I have left?",
"Now I double the number of apples I have. How many is that?",
"I eat 4 of them. How many remain?",
"Finally, I split the remaining apples equally between 2 friends. How many does each friend get?",
]
for i, u in enumerate(CONV, 1):
r, dt = turn(u)
print(f"--- Turn {i} total_ctx={len(gen)} survivors={len(kept)} {dt:.1f}s ---", flush=True)
print(f"USER: {u}", flush=True)
print(f"ASSISTANT: {r.strip()[:500]}", flush=True)
print("", flush=True)
print("MT_MLX_DONE", flush=True)