Spaces:
Sleeping
Sleeping
| """Dataset and sequence encoding for block infilling. | |
| The masked target is a contiguous, line-aligned BLOCK of the program body (~half | |
| of it), not a single short line. This stresses global structure (def-use across | |
| the gap, table/segment coherence) — the regime where the thesis expects the | |
| diffusion substrate to matter. Both models see the SAME block: | |
| diffusion (in-place, exact): [bos] prefix <block> suffix [eos] [pad...] | |
| the block positions are the denoise region; context is always visible. No gap | |
| padding — the block occupies exactly its real length. | |
| AR-FIM (causal): [bos][pre] prefix [suf] suffix [mid] block [eos] [pad...] | |
| loss only on the block+eos span, matching diffusion's region-only loss. | |
| `prefix + block + suffix == source` exactly (char-offset slicing), so a correct | |
| fill reconstructs the program and verification by execution is well-defined. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import numpy as np | |
| import torch | |
| from torch.utils.data import Dataset | |
| from .config import TaskConfig | |
| from .tokenizer import Tokenizer | |
| def load_records(path: str) -> list[dict]: | |
| with open(path) as f: | |
| return [json.loads(line) for line in f if line.strip()] | |
| def make_block(source: str, frac: float, rng: np.random.RandomState): | |
| """Slice the source into (prefix, block, suffix) where block is a contiguous, | |
| line-aligned run of body lines totalling ~frac of the body. Returns None if | |
| there is no usable body. prefix+block+suffix == source exactly.""" | |
| nl = source.find("\n") | |
| if nl < 0: | |
| return None | |
| body_start = nl + 1 | |
| stripped = source.rstrip("\n") | |
| end_pos = stripped.rfind("\nend") | |
| if end_pos < 0: | |
| return None | |
| body_end = end_pos + 1 # position of the final 'end' | |
| body = source[body_start:body_end] | |
| if not body.strip(): | |
| return None | |
| # Line-start offsets within the body (each body line keeps its trailing \n). | |
| starts = [0] + [i + 1 for i, ch in enumerate(body) if ch == "\n" and i + 1 < len(body)] | |
| n = len(starts) | |
| target = max(1, int(len(body) * frac)) | |
| s = rng.randint(0, n) | |
| acc = 0 | |
| e = s | |
| while e < n and acc < target: | |
| seg_end = starts[e + 1] if e + 1 < n else len(body) | |
| acc += seg_end - starts[e] | |
| e += 1 | |
| block_start = body_start + starts[s] | |
| block_end = body_start + (starts[e] if e < n else len(body)) | |
| prefix = source[:block_start] | |
| block = source[block_start:block_end] | |
| suffix = source[block_end:] | |
| if not block.strip(): | |
| return None | |
| return prefix, block, suffix | |
| def encode_diffusion(tok: Tokenizer, prefix, block, suffix, cfg: TaskConfig): | |
| """(ids, region_mask, block_id, attn_mask) of length T, or None if it doesn't | |
| fit. block_id is the generation-block index for region positions, -1 elsewhere | |
| (used by the block-causal mask).""" | |
| pre = tok.encode(prefix) | |
| mid = tok.encode(block) | |
| suf = tok.encode(suffix) | |
| body = [tok.bos_id] + pre + mid + suf + [tok.eos_id] | |
| if len(body) > cfg.seq_len: | |
| return None | |
| ids = np.full(cfg.seq_len, tok.pad_id, dtype=np.int64) | |
| ids[: len(body)] = body | |
| region = np.zeros(cfg.seq_len, dtype=bool) | |
| start = 1 + len(pre) | |
| region[start : start + len(mid)] = True | |
| block_id = np.full(cfg.seq_len, -1, dtype=np.int64) | |
| for r in range(len(mid)): | |
| block_id[start + r] = r // cfg.block_len | |
| attn = ids != tok.pad_id | |
| return ids, region, block_id, attn | |
| def encode_ar(tok: Tokenizer, prefix, block, suffix, cfg: TaskConfig): | |
| """(ids, loss_mask) of length T, or None if it doesn't fit.""" | |
| pre = tok.encode(prefix) | |
| mid = tok.encode(block) | |
| suf = tok.encode(suffix) | |
| head = [tok.bos_id, tok.pre_id] + pre + [tok.suf_id] + suf + [tok.mid_id] | |
| tail = mid + [tok.eos_id] | |
| body = head + tail | |
| if len(body) > cfg.seq_len: | |
| return None | |
| ids = np.full(cfg.seq_len, tok.pad_id, dtype=np.int64) | |
| ids[: len(body)] = body | |
| loss_mask = np.zeros(cfg.seq_len, dtype=bool) | |
| loss_mask[len(head) : len(head) + len(tail)] = True | |
| return ids, loss_mask | |
| def block_for_eval(source: str, frac: float, idx: int): | |
| """Deterministic block for a record (seeded by index), so diffusion and AR are | |
| evaluated on the IDENTICAL masked block.""" | |
| return make_block(source, frac, np.random.RandomState(idx + 1)) | |
| # ---- token-level (lua mode): everything in token-id space ---- | |
| # Whitespace is dropped, so blocks are contiguous lexeme runs and reconstruction | |
| # is tok.decode(prefix + block + suffix) (space-joined, executes identically). | |
| def make_block_lua(source: str, frac: float, rng: np.random.RandomState, tok: Tokenizer): | |
| """Split the lexeme-id list into (pre, block, suf) id lists, block ~frac of the | |
| body (tokens between the signature's ')' and the final 'end'). Returns None if | |
| no usable body.""" | |
| ids = tok.encode(source) | |
| rp = tok.stoi.get(")") | |
| endt = tok.stoi.get("end") | |
| if rp is None or endt is None or rp not in ids or endt not in ids: | |
| return None | |
| h = ids.index(rp) # end of the parameter list | |
| be = max(i for i, t in enumerate(ids) if t == endt) # final 'end' | |
| b0, b1 = h + 1, be | |
| if b1 <= b0: | |
| return None | |
| n = b1 - b0 | |
| target = max(1, int(n * frac)) | |
| s = rng.randint(0, n) | |
| e = min(s + target, n) | |
| a, c = b0 + s, b0 + e | |
| return ids[:a], ids[a:c], ids[c:] | |
| def _ids_canvas(tok, pre, blk, suf, cfg, ar): | |
| if ar: | |
| head = [tok.bos_id, tok.pre_id] + pre + [tok.suf_id] + suf + [tok.mid_id] | |
| tail = blk + [tok.eos_id] | |
| body = head + tail | |
| if len(body) > cfg.seq_len: | |
| return None | |
| arr = np.full(cfg.seq_len, tok.pad_id, dtype=np.int64) | |
| arr[: len(body)] = body | |
| loss = np.zeros(cfg.seq_len, dtype=bool) | |
| loss[len(head): len(head) + len(tail)] = True | |
| return arr, loss | |
| body = [tok.bos_id] + pre + blk + suf + [tok.eos_id] | |
| if len(body) > cfg.seq_len: | |
| return None | |
| arr = np.full(cfg.seq_len, tok.pad_id, dtype=np.int64) | |
| arr[: len(body)] = body | |
| region = np.zeros(cfg.seq_len, dtype=bool) | |
| start = 1 + len(pre) | |
| region[start: start + len(blk)] = True | |
| bid = np.full(cfg.seq_len, -1, dtype=np.int64) | |
| for r in range(len(blk)): | |
| bid[start + r] = r // cfg.block_len | |
| attn = arr != tok.pad_id | |
| return arr, region, bid, attn | |
| class InfillDataset(Dataset): | |
| """Block-infilling dataset. Each record gets a deterministic block (seeded by | |
| index) so runs are reproducible and the two modes train on matching blocks.""" | |
| def __init__(self, records: list[dict], tok: Tokenizer, cfg: TaskConfig, mode: str): | |
| assert mode in ("diffusion", "ar") | |
| self.tok = tok | |
| self.cfg = cfg | |
| self.mode = mode | |
| self.items = [] | |
| self.skipped = 0 | |
| ar = mode == "ar" | |
| lua = tok.mode == "lua" | |
| for i, rec in enumerate(records): | |
| # Per-record block fraction sampled in [frac_lo, frac_hi] so one model | |
| # handles any masking ratio; eval then sweeps the ratio for free. | |
| rng = np.random.RandomState(i + 1) | |
| frac = cfg.frac_lo + (cfg.frac_hi - cfg.frac_lo) * rng.rand() | |
| if lua: | |
| blk = make_block_lua(rec["source"], frac, rng, tok) | |
| e = None if blk is None else _ids_canvas(tok, blk[0], blk[1], blk[2], cfg, ar) | |
| else: | |
| blk = make_block(rec["source"], frac, rng) | |
| e = None if blk is None else ( | |
| encode_ar(tok, *blk, cfg) if ar else encode_diffusion(tok, *blk, cfg)) | |
| if blk is None or e is None: | |
| self.skipped += 1 | |
| else: | |
| self.items.append(e) | |
| def __len__(self): | |
| return len(self.items) | |
| def __getitem__(self, idx): | |
| if self.mode == "diffusion": | |
| ids, region, block_id, attn = self.items[idx] | |
| return (torch.from_numpy(ids), torch.from_numpy(region), | |
| torch.from_numpy(block_id), torch.from_numpy(attn)) | |
| ids, loss_mask = self.items[idx] | |
| return torch.from_numpy(ids), torch.from_numpy(loss_mask) | |