vignesh1234 commited on
Commit
bcfa15f
·
verified ·
1 Parent(s): 7b41231

remove __pycache__ and a decoder backup file that were uploaded by mistake

Browse files
maram_inference/maram/__pycache__/__init__.cpython-312.pyc DELETED
Binary file (151 Bytes)
 
maram_inference/maram/__pycache__/load.cpython-312.pyc DELETED
Binary file (1.27 kB)
 
maram_inference/maram/data/__pycache__/__init__.cpython-312.pyc DELETED
Binary file (156 Bytes)
 
maram_inference/maram/data/__pycache__/label.cpython-312.pyc DELETED
Binary file (11.6 kB)
 
maram_inference/maram/decode/__pycache__/__init__.cpython-312.pyc DELETED
Binary file (158 Bytes)
 
maram_inference/maram/decode/__pycache__/decode.cpython-312.pyc DELETED
Binary file (44.6 kB)
 
maram_inference/maram/decode/decode.py.pre_events_20261031T0701 DELETED
@@ -1,472 +0,0 @@
1
- """MARAM decoding: greedy1 | bestofK | native, sharing the prompt KV cache.
2
-
3
- The physical sequence grows "as generated": each step appends one token per stream slot (or <pad> for a
4
- finished/dead slot), block_idx = current block, and a <chk> query after every N content tokens. Attention
5
- for the new queries is computed over the whole physical cache with the SAME mask_mod as training
6
- (`maram.model.mask.build_mask`) evaluated on the concatenated (cache + new) metadata, materialised densely
7
- (queries are few, so this is exact and cheap).
8
-
9
- native: every N tokens a <chk> forward; score_k = sigmoid(correct) - 0.1*sigmoid(remaining);
10
- kill the lowest-scored unfinished stream; fork the highest-scored unfinished stream's KV entries into the
11
- killed slot (copies of its K/V with the new slot's seg id) with header <s_k><strat:z>, z = a strategy the
12
- killed stream did not have (preferring one the parent does not have either). --no_respawn = prune only.
13
- """
14
- from __future__ import annotations
15
- import math, re
16
- from dataclasses import dataclass, field
17
- from typing import List, Optional
18
- import numpy as np, torch, torch.nn.functional as F
19
-
20
- from maram.model.mask import build_mask, PAD_SEG
21
- from maram.model.model import MaramModel, ModelConfig
22
- from maram.tokenizer.maram_tokenizer import (PAD, PROMPT, CHK, END, STREAM_IDS, STRAT_IDS, STRAT_NAMES, FIRST_MERGE_ID)
23
-
24
- ANSWER_RE = re.compile(r"Answer\s*:\s*(.+)", re.I)
25
-
26
-
27
- class LayerKV:
28
- def __init__(self, B, H, S, hd, device, dtype):
29
- self.k = torch.zeros(B, H, S, hd, device=device, dtype=dtype)
30
- self.v = torch.zeros(B, H, S, hd, device=device, dtype=dtype)
31
- self.len = 0
32
-
33
- def update(self, k_new, v_new):
34
- n = k_new.shape[2]
35
- self.k[:, :, self.len:self.len + n] = k_new
36
- self.v[:, :, self.len:self.len + n] = v_new
37
- return self.k[:, :, :self.len + n], self.v[:, :, :self.len + n]
38
-
39
-
40
- class KVCache:
41
- def __init__(self, model: MaramModel, B: int, S: int, device, dtype):
42
- cfg = model.cfg
43
- self.layers = [LayerKV(B, cfg.n_heads, S, cfg.head_dim, device, dtype) for _ in range(cfg.n_layers)]
44
- self.seg = torch.full((B, S), PAD_SEG, dtype=torch.long, device=device)
45
- self.pos = torch.zeros((B, S), dtype=torch.long, device=device)
46
- self.block = torch.zeros((B, S), dtype=torch.long, device=device)
47
- self.B, self.S_alloc, self.len, self.device = B, S, 0, device
48
-
49
- def commit(self, n, seg, pos, block):
50
- self.seg[:, self.len:self.len + n] = seg
51
- self.pos[:, self.len:self.len + n] = pos
52
- self.block[:, self.len:self.len + n] = block
53
- self.len += n
54
- for l in self.layers:
55
- l.len = self.len
56
-
57
- def expand_rows(self, reps: int):
58
- """Repeat every batch row `reps` times (share a prefilled prompt across samples)."""
59
- for l in self.layers:
60
- l.k = l.k.repeat_interleave(reps, dim=0); l.v = l.v.repeat_interleave(reps, dim=0)
61
- self.seg = self.seg.repeat_interleave(reps, dim=0); self.pos = self.pos.repeat_interleave(reps, dim=0)
62
- self.block = self.block.repeat_interleave(reps, dim=0); self.B *= reps
63
-
64
- def invalidate(self, row, seg_id):
65
- """Mask every cache entry of (row, seg): a killed stream disappears for all later queries."""
66
- m = self.seg[row, :self.len] == seg_id
67
- self.seg[row, :self.len][m] = PAD_SEG
68
- return int(m.sum())
69
-
70
- def fork(self, rows, src_seg, dst_seg):
71
- """For each (row, src, dst): append copies of all cache entries with seg==src relabelled seg=dst."""
72
- if not rows:
73
- return 0
74
- counts = []
75
- idxs = []
76
- for r, s, d in zip(rows, src_seg, dst_seg):
77
- ix = torch.nonzero(self.seg[r, :self.len] == s).flatten()
78
- idxs.append(ix); counts.append(len(ix))
79
- m = max(counts)
80
- if self.len + m > self.S_alloc:
81
- raise RuntimeError("KV cache too small for fork")
82
- for r, s, d, ix in zip(rows, src_seg, dst_seg, idxs):
83
- n = len(ix)
84
- if n == 0:
85
- continue
86
- for l in self.layers:
87
- l.k[r, :, self.len:self.len + n] = l.k[r, :, ix]
88
- l.v[r, :, self.len:self.len + n] = l.v[r, :, ix]
89
- self.seg[r, self.len:self.len + n] = d
90
- self.pos[r, self.len:self.len + n] = self.pos[r, ix]
91
- self.block[r, self.len:self.len + n] = self.block[r, ix]
92
- self.len += m
93
- for l in self.layers:
94
- l.len = self.len
95
- return m
96
-
97
-
98
- @dataclass
99
- class Stream:
100
- slot: int # 0-based -> seg = slot+1, header STREAM_IDS[slot]
101
- strat: int
102
- tokens: List[int] = field(default_factory=list) # content tokens incl <end>
103
- n_fed: int = 0 # tokens fed so far (headers + content) -> next pos = P + n_fed
104
- finished: bool = False
105
- dead: bool = False
106
- score: float = float("nan")
107
- p_correct: float = float("nan")
108
- p_remain: float = float("nan")
109
- last_h: Optional[torch.Tensor] = None
110
- pending: Optional[int] = None # token to feed next (sampled last step)
111
- parent: Optional[int] = None
112
- born_block: int = 0
113
- generated: int = 0 # sampled tokens (counted for cost), incl <end>
114
- gen: int = 0 # occupant index of this slot (0 = original, +1 per respawn)
115
- temp: Optional[float] = None # sampling temperature of this stream (None = run-level `temperature`)
116
-
117
-
118
- class Decoder:
119
- def __init__(self, model: MaramModel, tokenizer, device="cuda", dtype=torch.bfloat16, judge_mode="full"):
120
- self.model = model.eval().to(device)
121
- self.tk = tokenizer
122
- self.device = device; self.dtype = dtype
123
- self.cfg = model.cfg
124
- self.judge_mode = judge_mode
125
-
126
- # ------------------------------------------------------------------ core forward over the cache
127
- @torch.no_grad()
128
- def _forward(self, cache: KVCache, tokens, seg, pos, block):
129
- """tokens/seg/pos/block: [B, n] new queries. Appends them to the cache. Returns hidden [B,n,d]."""
130
- B, n = tokens.shape
131
- S = cache.len
132
- seg_all = torch.cat([cache.seg[:, :S], seg], 1); pos_all = torch.cat([cache.pos[:, :S], pos], 1)
133
- blk_all = torch.cat([cache.block[:, :S], block], 1)
134
- mm = build_mask(seg_all, pos_all, blk_all, self.cfg.K, self.cfg.N)
135
- b = torch.arange(B, device=self.device)[:, None, None]
136
- q = (S + torch.arange(n, device=self.device))[None, :, None]
137
- kv = torch.arange(S + n, device=self.device)[None, None, :]
138
- dense = mm(b, 0, q, kv) # [B, n, S+n]
139
- with torch.autocast(self.device if self.device == "cuda" else "cpu", dtype=self.dtype, enabled=self.dtype != torch.float32):
140
- h = self.model.hidden_states(tokens, pos, None, dense, cache.layers)
141
- cache.commit(n, seg, pos, block)
142
- return h
143
-
144
- def _logits(self, h):
145
- return self.model.lm_head(h).float()
146
-
147
- def _judge(self, h_chk, h_last):
148
- with torch.autocast(self.device if self.device == "cuda" else "cpu", dtype=self.dtype, enabled=self.dtype != torch.float32):
149
- return self.model.judge(h_chk, h_last, self.judge_mode).float()
150
-
151
- def _sample(self, logits, temperature):
152
- if temperature <= 0:
153
- return logits.argmax(-1)
154
- return torch.multinomial(torch.softmax(logits / temperature, -1), 1).squeeze(-1)
155
-
156
- # ------------------------------------------------------------------ prompt prefill (shared)
157
- def prefill(self, prompts: List[List[int]], K_slots: int, max_new: int, extra: int = 0):
158
- """prompts: list of token id lists (<=256). Builds a cache sized for the run and prefills the prompt
159
- region (left-aligned, pad masked). Returns cache and P per row."""
160
- B = len(prompts)
161
- Pm = max(len(p) for p in prompts) + 1
162
- S = Pm + K_slots * (max_new + 2 + 2 * 8) + 2 * 8 + extra
163
- cache = KVCache(self.model, B, S, self.device, self.dtype)
164
- toks = torch.full((B, Pm), PAD, dtype=torch.long, device=self.device)
165
- seg = torch.full((B, Pm), PAD_SEG, dtype=torch.long, device=self.device)
166
- pos = torch.zeros((B, Pm), dtype=torch.long, device=self.device)
167
- P = torch.zeros(B, dtype=torch.long, device=self.device)
168
- for i, p in enumerate(prompts):
169
- toks[i, 0] = PROMPT; toks[i, 1:1 + len(p)] = torch.tensor(p, device=self.device)
170
- seg[i, :1 + len(p)] = 0; pos[i, :1 + len(p)] = torch.arange(1 + len(p), device=self.device)
171
- P[i] = 1 + len(p)
172
- self._forward(cache, toks, seg, pos, torch.zeros_like(pos))
173
- return cache, P
174
-
175
- # ------------------------------------------------------------------ generic multi-stream loop
176
- @torch.no_grad()
177
- def run_streams(self, prompts, strats: List[List[int]], temperature: float, max_new: int = 512,
178
- use_judge: bool = False, respawn: bool = True, N: Optional[int] = None, sample_seed: int = 1337,
179
- slot_temps: Optional[List[float]] = None, strat_pool: Optional[List[int]] = None,
180
- fork_temp: Optional[float] = None, row_temps: Optional[List[List[float]]] = None,
181
- prune: str = "always", tau: float = 0.0, delta: float = 0.0, immune_slots: Optional[List[int]] = None,
182
- dump_tokens: bool = False):
183
- """strats[i] = strategy token per slot for problem i (len K_slots). Returns per-problem dict.
184
- prune (use_judge only): "always" = at every <chk> with >=2 unfinished streams kill the lowest-scored one (the
185
- original rule); "gated" = kill it only if its p_correct < tau and the best unfinished stream's p_correct exceeds
186
- it by >= delta, otherwise do nothing at that checkpoint; "none" = never kill (every stream runs to completion and
187
- the judge only picks at the final <chk>). immune_slots: slots that are never killed (pruning happens among the
188
- other slots only; the fork source may still be an immune slot). dump_tokens: keep every stream's token ids
189
- (live and killed) in the records.
190
- slot_temps: optional per-slot sampling temperature (overrides `temperature`; a forked child inherits its
191
- slot's temperature unless fork_temp is given). row_temps[i][k]: per-problem, per-slot temperature (overrides
192
- slot_temps). fork_temp: temperature of every respawned child. strat_pool: strategies a forked child may take
193
- (default all three)."""
194
- K = len(strats[0]); N = N or self.cfg.N
195
- pool = list(strat_pool) if strat_pool else list(STRAT_IDS)
196
- assert prune in ("always", "gated", "none"), prune
197
- immune = set(immune_slots or [])
198
- gate_skips = [0] * len(prompts) # checkpoints where the gate blocked a kill
199
- if slot_temps is not None:
200
- assert len(slot_temps) == K, (slot_temps, K)
201
- per_stream_temps = slot_temps is not None or row_temps is not None or fork_temp is not None
202
- def init_temp(i, k):
203
- if row_temps is not None: return row_temps[i][k]
204
- if slot_temps is not None: return slot_temps[k]
205
- return temperature
206
- assert K <= len(STREAM_IDS)
207
- B = len(prompts)
208
- g = torch.Generator(device=self.device); g.manual_seed(sample_seed)
209
- nb = math.ceil(max_new / N)
210
- cache, P = self.prefill(prompts, K, max_new, extra=((nb - 1) * (max_new + nb + 2) if use_judge and respawn else 0))
211
- Pl = P.tolist() # per-step token/seg/pos tensors are built on the CPU and moved once (avoids per-element CUDA launches)
212
- streams = [[Stream(slot=k, strat=strats[i][k], temp=init_temp(i, k)) for k in range(K)] for i in range(B)]
213
- chk_count = [0] * B; forks = [0] * B; kills = [0] * B; dead_generated = [0] * B
214
- judge_log = [[] for _ in range(B)]; dead_recs = [[] for _ in range(B)]
215
- dev = self.device
216
- n_blocks = nb
217
- for b in range(n_blocks):
218
- # ---- headers for this block
219
- for hdr_step in range(2 if b == 0 else 1):
220
- toks = torch.full((B, K), PAD, dtype=torch.long)
221
- seg = torch.full((B, K), PAD_SEG, dtype=torch.long)
222
- pos = torch.zeros((B, K), dtype=torch.long)
223
- for i in range(B):
224
- for k, s in enumerate(streams[i]):
225
- if s.finished or s.dead:
226
- continue
227
- if b == 0 or s.born_block == b:
228
- tid = STREAM_IDS[k] if hdr_step == 0 else s.strat
229
- else:
230
- if hdr_step == 1: continue
231
- tid = STREAM_IDS[k]
232
- toks[i, k] = tid; seg[i, k] = k + 1; pos[i, k] = Pl[i] + s.n_fed; s.n_fed += 1
233
- if (seg != PAD_SEG).any():
234
- toks, seg, pos = toks.to(dev), seg.to(dev), pos.to(dev)
235
- h = self._forward(cache, toks, seg, pos, torch.full_like(pos, b))
236
- for i in range(B):
237
- for k, s in enumerate(streams[i]):
238
- if seg[i, k] != PAD_SEG:
239
- s.last_h = h[i, k]
240
- # for streams born this block (forks) the two headers were fed above when hdr_step covered them
241
- # ---- N content tokens
242
- for t in range(N):
243
- if b * N + t >= max_new:
244
- break
245
- # sample next token for every live stream from the logits at its last fed position
246
- toks = torch.full((B, K), PAD, dtype=torch.long)
247
- seg = torch.full((B, K), PAD_SEG, dtype=torch.long)
248
- pos = torch.zeros((B, K), dtype=torch.long)
249
- live = [(i, k) for i in range(B) for k, s in enumerate(streams[i]) if not (s.finished or s.dead)]
250
- if not live:
251
- break
252
- hs = torch.stack([streams[i][k].last_h for i, k in live])
253
- logits = self._logits(hs)
254
- # never sample control tokens: <pad> <prompt> <s1..s4> <strat:*> <chk> (256..265) and <right>/<wrong>
255
- # (267, 268). Byte tokens 0..255 (every single character incl. digits) and <end> (266) stay allowed.
256
- logits[:, PAD:END] = -float("inf")
257
- logits[:, END + 1:FIRST_MERGE_ID] = -float("inf")
258
- if per_stream_temps:
259
- tt = torch.tensor([streams[i][k].temp for i, k in live], device=logits.device, dtype=logits.dtype)[:, None]
260
- samp = torch.multinomial(torch.softmax(logits / tt.clamp_min(1e-6), -1), 1, generator=g).squeeze(-1)
261
- nxt = torch.where(tt[:, 0] <= 0, logits.argmax(-1), samp)
262
- elif temperature <= 0:
263
- nxt = logits.argmax(-1)
264
- else:
265
- nxt = torch.multinomial(torch.softmax(logits / temperature, -1), 1, generator=g).squeeze(-1)
266
- for (i, k), tid in zip(live, nxt.tolist()):
267
- s = streams[i][k]
268
- if len(s.tokens) + 1 >= max_new and tid != END:
269
- tid = END # forced finish at the budget
270
- s.tokens.append(tid); s.generated += 1
271
- toks[i, k] = tid; seg[i, k] = k + 1; pos[i, k] = Pl[i] + s.n_fed; s.n_fed += 1
272
- toks, seg, pos = toks.to(dev), seg.to(dev), pos.to(dev)
273
- h = self._forward(cache, toks, seg, pos, torch.full_like(pos, b))
274
- for (i, k) in live:
275
- s = streams[i][k]
276
- s.last_h = h[i, k]
277
- if s.tokens[-1] == END:
278
- s.finished = True
279
- # ---- checkpoint
280
- if use_judge:
281
- toks = torch.full((B, 1), CHK, dtype=torch.long, device=dev)
282
- seg = torch.full((B, 1), K + 1, dtype=torch.long, device=dev)
283
- pos = (P + (b + 1) * N)[:, None]
284
- h = self._forward(cache, toks, seg, pos, torch.full_like(pos, b))
285
- fork_rows, fork_src, fork_dst = [], [], [] # one batched fork per checkpoint (shared cache length)
286
- for i in range(B):
287
- chk_count[i] += 1
288
- cand = [s for s in streams[i] if not s.dead and s.last_h is not None]
289
- if not cand:
290
- continue
291
- hl = torch.stack([s.last_h for s in cand])
292
- hc = h[i, 0][None].expand(len(cand), -1)
293
- j = self._judge(hc, hl)
294
- pc = torch.sigmoid(j[:, 0]).tolist(); pr = torch.sigmoid(j[:, 1]).tolist()
295
- for s, a, r in zip(cand, pc, pr):
296
- s.p_correct, s.p_remain = a, r
297
- s.score = a - 0.1 * r
298
- judge_log[i].append({"block": b, "slot": s.slot, "gen": s.gen, "strat": STRAT_NAMES[s.strat], "p_correct": a,
299
- "p_remain": r, "finished": s.finished, "len": len(s.tokens)})
300
- last_block = (b + 1) * N >= max_new
301
- unfinished = [s for s in cand if not s.finished]
302
- killable = [s for s in unfinished if s.slot not in immune]
303
- if len(unfinished) >= 2 and killable and not last_block and prune != "none":
304
- worst = min(killable, key=lambda s: s.score)
305
- best = max(unfinished, key=lambda s: s.score)
306
- allowed = worst is not best
307
- if allowed and prune == "gated":
308
- allowed = worst.p_correct < tau and (best.p_correct - worst.p_correct) >= delta
309
- if not allowed:
310
- gate_skips[i] += 1
311
- if allowed:
312
- worst.dead = True; kills[i] += 1; dead_generated[i] += worst.generated
313
- cache.invalidate(i, worst.slot + 1) # killed stream vanishes from the cache
314
- dead_recs[i].append({"slot": worst.slot, "gen": worst.gen, "strat": STRAT_NAMES[worst.strat],
315
- "text": self.tk.decode(worst.tokens), "len": len(worst.tokens),
316
- "generated_own": worst.generated, "killed_block": b, "parent": worst.parent,
317
- "p_correct": worst.p_correct, "p_remain": worst.p_remain,
318
- **({"tokens": list(worst.tokens)} if dump_tokens else {})})
319
- if respawn:
320
- fork_rows.append(i); fork_src.append(best.slot + 1); fork_dst.append(worst.slot + 1)
321
- forks[i] += 1
322
- taken = {s.strat for s in streams[i] if not s.dead}
323
- bi_ = pool.index(best.strat) if best.strat in pool else -1
324
- order = pool[bi_ + 1:] + pool[:bi_ + 1]
325
- choices = ([z for z in order if z != worst.strat and z != best.strat]
326
- or [z for z in order if z != worst.strat] or order)
327
- child = Stream(slot=worst.slot, strat=choices[0], tokens=list(best.tokens), n_fed=best.n_fed,
328
- last_h=best.last_h, parent=best.slot, born_block=b + 1, gen=worst.gen + 1,
329
- temp=fork_temp if fork_temp is not None else init_temp(i, worst.slot))
330
- streams[i][worst.slot] = child
331
- if fork_rows:
332
- cache.fork(fork_rows, fork_src, fork_dst)
333
- if all(s.finished or s.dead for i in range(B) for s in streams[i]):
334
- break
335
- # ---- collect
336
- out = []
337
- for i in range(B):
338
- recs = []
339
- for s in streams[i]:
340
- recs.append({"slot": s.slot, "gen": s.gen, "strat": STRAT_NAMES[s.strat], "text": self.tk.decode(s.tokens), "finished": s.finished,
341
- "dead": s.dead, "score": s.score, "p_correct": s.p_correct, "p_remain": s.p_remain, "len": len(s.tokens),
342
- "parent": s.parent, "generated_own": s.generated, "temp": s.temp,
343
- **({"tokens": list(s.tokens)} if dump_tokens else {})})
344
- out.append({"streams": recs, "dead_streams": dead_recs[i], "chk_forwards": chk_count[i], "kills": kills[i], "forks": forks[i],
345
- "judge_log": judge_log[i], "cache_len": cache.len, "_dead_generated": dead_generated[i],
346
- "gate_skips": gate_skips[i]})
347
- return out, streams
348
-
349
- # ------------------------------------------------------------------ public modes
350
- def _account(self, result, live_streams):
351
- gen = sum(s.generated for s in live_streams) + result.get("_dead_generated", 0)
352
- result["tokens_generated"] = gen + result["chk_forwards"]
353
- result["tokens_sampled"] = gen
354
- return result
355
-
356
- @torch.no_grad()
357
- def greedy1(self, prompts, strat=STRAT_IDS[0], max_new=512):
358
- res, streams = self.run_streams(prompts, [[strat]] * len(prompts), temperature=0.0, max_new=max_new, use_judge=False)
359
- out = []
360
- for i, r in enumerate(res):
361
- r = self._account(r, streams[i]); r["answer"] = extract_answer(r["streams"][0]["text"]); out.append(r)
362
- return out
363
-
364
- @torch.no_grad()
365
- def bestofK(self, prompts, K=3, temperature=0.7, max_new=512, seed=1337,
366
- sample_strats: Optional[List[int]] = None, sample_temps: Optional[List[float]] = None):
367
- """K independent sampled streams (strategies round-robin, or sample_strats[j] per sample; temperature, or
368
- sample_temps[j] per sample, 0 = greedy), majority vote on the final answer.
369
- Implemented as K single-stream rows sharing nothing but the code path (prompt prefill is cheap)."""
370
- rows = [p for p in prompts for _ in range(K)]
371
- strats = [[sample_strats[j] if sample_strats else STRAT_IDS[j % 3]] for _ in prompts for j in range(K)]
372
- row_temps = [[sample_temps[j]] for _ in prompts for j in range(K)] if sample_temps else None
373
- res, streams = self.run_streams(rows, strats, temperature=temperature, max_new=max_new, use_judge=False, sample_seed=seed,
374
- row_temps=row_temps)
375
- out = []
376
- for i in range(len(prompts)):
377
- grp = res[i * K:(i + 1) * K]; gs = streams[i * K:(i + 1) * K]
378
- answers = [extract_answer(g["streams"][0]["text"]) for g in grp]
379
- ans, ans_str = majority_vote(answers) # value vote (expressions evaluated); string vote kept for reference
380
- tg = sum(g["streams"][0]["generated_own"] for g in grp)
381
- out.append({"answer": ans, "answer_stringvote": ans_str, "answers": answers, "tokens_generated": tg, "tokens_sampled": tg,
382
- "chk_forwards": 0, "streams": [g["streams"][0] for g in grp], "kills": 0, "forks": 0})
383
- return out
384
-
385
- @torch.no_grad()
386
- def native(self, prompts, K=3, N=None, temperature=0.7, max_new=512, respawn=True, seed=1337,
387
- slot_strats: Optional[List[int]] = None, slot_temps: Optional[List[float]] = None, strat_pool: Optional[List[int]] = None,
388
- fork_temp: Optional[float] = None, prune: str = "always", tau: float = 0.0, delta: float = 0.0,
389
- immune_slots: Optional[List[int]] = None, dump_tokens: bool = False):
390
- """slot_strats: strategy token per slot (default round-robin over all three); slot_temps / strat_pool / fork_temp /
391
- prune / tau / delta / immune_slots / dump_tokens as in run_streams."""
392
- base = list(slot_strats) if slot_strats else [STRAT_IDS[j % 3] for j in range(K)]
393
- strats = [list(base) for _ in prompts]
394
- self._all_streams = []
395
- res, streams = self._run_native(prompts, strats, temperature, max_new, respawn, N, seed, slot_temps, strat_pool, fork_temp,
396
- prune, tau, delta, immune_slots, dump_tokens)
397
- return res
398
-
399
- def _run_native(self, prompts, strats, temperature, max_new, respawn, N, seed, slot_temps=None, strat_pool=None, fork_temp=None,
400
- prune="always", tau=0.0, delta=0.0, immune_slots=None, dump_tokens=False):
401
- # run_streams replaces killed streams in place; we need the killed ones' generated counts, so wrap Stream creation
402
- res, streams = self.run_streams(prompts, strats, temperature=temperature, max_new=max_new, use_judge=True,
403
- respawn=respawn, N=N, sample_seed=seed, slot_temps=slot_temps, strat_pool=strat_pool,
404
- fork_temp=fork_temp, prune=prune, tau=tau, delta=delta, immune_slots=immune_slots,
405
- dump_tokens=dump_tokens)
406
- out = []
407
- for i, r in enumerate(res):
408
- # generated by killed streams is recorded in judge_log lens at kill time; recompute from stream history
409
- gen_live = sum(s.generated for s in streams[i])
410
- gen_dead = r.get("_dead_generated", 0)
411
- r["tokens_sampled"] = gen_live + gen_dead
412
- r["tokens_generated"] = r["tokens_sampled"] + r["chk_forwards"]
413
- fin = [s for s in r["streams"] if s["finished"] and not s["dead"]]
414
- pick = max(fin, key=lambda s: (s["score"] if s["score"] == s["score"] else -1e9)) if fin else \
415
- max([s for s in r["streams"] if not s["dead"]], key=lambda s: (s["score"] if s["score"] == s["score"] else -1e9))
416
- r["answer"] = extract_answer(pick["text"]); r["picked_slot"] = pick["slot"]
417
- # value vote over the surviving streams' final answers (same samples as the judge pick): isolates the judge's
418
- # selection value from the pruning; identical to best-of-K's vote when prune == "none"
419
- live_answers = [extract_answer(s["text"]) for s in r["streams"] if not s["dead"]]
420
- r["answer_vote"], r["answer_stringvote"] = majority_vote(live_answers); r["answers"] = live_answers
421
- r["anchor_killed"] = any(d["slot"] == 0 and d.get("gen", 0) == 0 for d in r["dead_streams"])
422
- out.append(r)
423
- return out, streams
424
-
425
-
426
- def extract_answer(text: str):
427
- ms = ANSWER_RE.findall(text or "")
428
- return ms[-1].strip() if ms else None
429
-
430
-
431
- def answer_key(ans):
432
- """Canonical vote key for majority voting. An arithmetic expression (Countdown-style answer) is keyed by its
433
- evaluated value — computed from the expression alone, without the gold target and without the uses-each-number-
434
- once check — so distinct valid expressions for the same target agree. A number is keyed by its normalised
435
- value; anything else by its stripped string. None stays None."""
436
- from fractions import Fraction
437
- from maram.data.label import normalize_expr, _fractionize, norm_number
438
- if ans is None:
439
- return None
440
- e = normalize_expr(ans)
441
- if e and re.fullmatch(r"[\d\s\+\-\*/\(\)]+", e) and re.search(r"[\+\-\*/]", e) and re.search(r"\d", e):
442
- try:
443
- v = eval(_fractionize(e), {"Fraction": Fraction, "__builtins__": {}})
444
- return f"v:{v}"
445
- except Exception:
446
- pass
447
- n = norm_number(ans)
448
- return f"n:{n}" if n is not None else f"s:{ans.strip()}"
449
-
450
-
451
- def majority_vote(answers):
452
- """Returns (winning raw answer, string-vote answer). Ties: first seen."""
453
- keys = [answer_key(a) for a in answers]
454
- counts = {}
455
- for k in keys:
456
- if k is not None:
457
- counts[k] = counts.get(k, 0) + 1
458
- win = max(counts, key=counts.get) if counts else None
459
- ans = next((a for a, k in zip(answers, keys) if k == win), None) if win is not None else None
460
- sc = {}
461
- for a in answers:
462
- if a is not None:
463
- sc[a.strip()] = sc.get(a.strip(), 0) + 1
464
- return ans, (max(sc, key=sc.get) if sc else None)
465
-
466
-
467
- def load_model(ckpt_path, device="cuda", judge_mode="full"):
468
- sd = torch.load(ckpt_path, map_location="cpu", weights_only=False)
469
- cfg = ModelConfig.from_dict(sd["model_cfg"])
470
- cfg.judge_mode = judge_mode
471
- m = MaramModel(cfg); m.load_state_dict(sd["model"])
472
- return m.to(device), sd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
maram_inference/maram/model/__pycache__/__init__.cpython-312.pyc DELETED
Binary file (157 Bytes)
 
maram_inference/maram/model/__pycache__/judge.cpython-312.pyc DELETED
Binary file (2.64 kB)
 
maram_inference/maram/model/__pycache__/mask.cpython-312.pyc DELETED
Binary file (3.84 kB)
 
maram_inference/maram/model/__pycache__/model.cpython-312.pyc DELETED
Binary file (15.4 kB)
 
maram_inference/maram/tokenizer/__pycache__/__init__.cpython-312.pyc DELETED
Binary file (161 Bytes)
 
maram_inference/maram/tokenizer/__pycache__/maram_tokenizer.cpython-312.pyc DELETED
Binary file (4.29 kB)