Spaces:
Sleeping
Sleeping
File size: 8,161 Bytes
3afc977 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | """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)
|