Skip to content

Commit 367cf22

Browse files
committed
more
1 parent e1503bd commit 367cf22

2 files changed

Lines changed: 133 additions & 8 deletions

File tree

users/zeyer/experiments/exp2025_07_07_in_grads/jobs/chunk_segmentation.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,17 @@ class ChunkSegmentationFromModelBatchedJob(Job):
377377
in bf16 it may differ by matmul non-determinism (set ``model_dtype`` in the model config).
378378
"""
379379

380-
# Bumped to 2 when sort_by_length / batched_logprobs were added: the flags are part of the hash
381-
# (no __sis_hash_exclude__), so every value combination gets its own hash.
380+
# Bumped to 2 when sort_by_length / batched_logprobs were added:
381+
# those flags are part of the hash, so every value combination gets its own hash.
382382
__sis_version__ = 2
383+
# Algorithm variants added later.
384+
# The excluded value for each is the one that REPRODUCES the behavior from before the param existed
385+
# (no beam / no bias / summed word scores),
386+
# so a job left at the defaults hashes like a pre-variant job and the finished sweeps stay reused;
387+
# a non-default value still changes the hash.
388+
# Polarity matters:
389+
# excluding the *new* value instead would make a variant job collide with the old hash and not rerun.
390+
__sis_hash_exclude__ = {"word_start_beam": None, "exit_bias": 0.0, "length_norm": False}
383391

384392
def __init__(
385393
self,
@@ -395,6 +403,9 @@ def __init__(
395403
max_batch_size: int = 8,
396404
sort_by_length: bool = True,
397405
batched_logprobs: bool = True,
406+
word_start_beam: Optional[float] = None,
407+
exit_bias: float = 0.0,
408+
length_norm: bool = False,
398409
):
399410
super().__init__()
400411
self.dataset_dir = dataset_dir
@@ -411,6 +422,19 @@ def __init__(
411422
# instead of one per word (the DP's real bottleneck). Both default on (verified equivalent).
412423
self.sort_by_length = sort_by_length
413424
self.batched_logprobs = batched_logprobs
425+
# word_start_beam: keep every previous-chunk exit within this log-prob margin of the best,
426+
# and start from the earliest survivor.
427+
# None = plain argmax (the original heuristic);
428+
# a wider beam prunes less and approaches word_start_heuristic=False (exact).
429+
# One knob interpolating the two, since the argmax end measurably beats the exact end.
430+
# exit_bias: added to EVERY exit log-prob,
431+
# unlike empty_exit_penalty which only applies to a chunk exiting with zero words --
432+
# a global words-per-chunk knob (cf. word insertion penalty).
433+
# length_norm: score a word by its per-token MEAN log-prob instead of the sum,
434+
# so emitting a multi-token word is not systematically dearer than the single exit token.
435+
self.word_start_beam = word_start_beam
436+
self.exit_bias = exit_bias
437+
self.length_norm = length_norm
414438

415439
self.rqmt = {"time": 40, "cpu": 2, "gpu": 1, "mem": 125}
416440
self.out_hdf = self.output_path("out.hdf")
@@ -537,9 +561,15 @@ def _chunk_bounds(audio, samplerate):
537561
if cci == 0 or not self.word_start_heuristic:
538562
prev_idx, cws = 0, 0
539563
else:
540-
prev_idx = int(
541-
torch.stack([n.accum_exit_log_prob for n in s["array"][cci - 1]]).argmax().item()
542-
)
564+
exits = torch.stack([n.accum_exit_log_prob for n in s["array"][cci - 1]])
565+
if self.word_start_beam is None:
566+
prev_idx = int(exits.argmax().item())
567+
else:
568+
# Beam prune:
569+
# keep every exit within the margin of the best, start from the earliest survivor.
570+
# Wider beam -> prunes less -> approaches the exact DP.
571+
keep = (exits >= exits.max() - self.word_start_beam).nonzero()
572+
prev_idx = int(keep.min().item())
543573
cws = s["array"][cci - 1][prev_idx].word_idx
544574
cwe = len(s["words"])
545575
if cws >= cwe:
@@ -557,9 +587,10 @@ def _chunk_bounds(audio, samplerate):
557587
if not active:
558588
break
559589

560-
# One flushed line per chunk step, so the log keeps updating and a slow run is
561-
# distinguishable from a real hang: small chunk sizes mean ~1000+ chunks per seq, and
562-
# the DP is otherwise silent for a whole group (only group start / seq end print).
590+
# One flushed line per chunk step, so the log keeps updating
591+
# and a slow run is distinguishable from a real hang:
592+
# small chunk sizes mean ~1000+ chunks per seq,
593+
# and the DP is otherwise silent for a whole group (only group start / seq end print).
563594
print(
564595
f"** chunk step: {len(active)} active seqs,"
565596
f" chunk {[st[i]['cci'] for i in active]} of {[len(st[i]['cse']) for i in active]},"
@@ -606,9 +637,13 @@ def _chunk_bounds(audio, samplerate):
606637
0.0,
607638
)
608639
wlp = wlp.sum()
640+
if self.length_norm:
641+
wlp = wlp / max(1, int(t1 - t0))
609642
else:
610643
wlp = None
611644
exit_lp = log_probs[0, 0, model.assistant_end_token_id]
645+
if self.exit_bias:
646+
exit_lp = exit_lp + self.exit_bias
612647
if w == 0:
613648
exit_lp = exit_lp + self.empty_exit_penalty
614649
prev_left, prev_below = None, None

users/zeyer/experiments/exp2026_05_22_chunk_align.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ def py():
221221
# the -5 default fights that): does removing the penalty recover acc 0.07 / 0.02?
222222
(1.0, 0.5, [0.0, -5.0]),
223223
(0.5, 0.0, [0.0, -5.0]),
224+
# optimal overlap at cs1 once eep is set right (eep=0): ov0.5 gave 0.5530, so map the rest.
225+
# eep=-5 for cs1-ov0 is the plain cs1-ov0 job above (0.6318).
226+
(1.0, 0.0, [0.0]),
227+
(1.0, 0.25, [0.0]),
228+
(1.0, 0.75, [0.0]),
224229
]:
225230
for _eep in _eeps:
226231
_seg_e = ChunkSegmentationFromModelBatchedJob(
@@ -280,6 +285,91 @@ def py():
280285
reg(f"{_x_name}-accuracy.txt", _metric_x.out_accuracy)
281286
reg(f"{_x_name}-chunk_idx_mae.txt", _metric_x.out_chunk_idx_mae)
282287

288+
# --- algorithm variants (batched job only) ---
289+
290+
# 1) word_start_beam: one knob interpolating the argmax heuristic (beam=None, the old default)
291+
# and the exact DP (word_start_heuristic=False).
292+
# cs1-ov0 is where the two endpoints differ most (0.6318 argmax vs 0.2473 exact), so map the curve there:
293+
# wider beam = less pruning = closer to exact, which we expect to get WORSE
294+
# (search-error effect, cf. the NMT beam-search curse).
295+
for _beam in [0.0, 1.0, 2.0, 5.0, 10.0, 20.0]:
296+
_seg_bm = ChunkSegmentationFromModelBatchedJob(
297+
dataset_dir=dl_ds_buckeye.out_hub_cache_dir,
298+
dataset_key="val",
299+
model_config=_cfg_hp,
300+
chunk_size_secs=1.0,
301+
chunk_overlap_secs=0.0,
302+
max_batch_size=8,
303+
word_start_beam=_beam,
304+
)
305+
_bm_name = f"chunk-align/phi4mm-buckeye-val-cs1-ov0-beam{_beam:g}"
306+
_seg_bm.add_alias(_bm_name)
307+
reg(f"{_bm_name}.hdf", _seg_bm.out_hdf)
308+
_m_bm = CalcChunkAssignmentMetricsJob(
309+
chunk_seg_hdf=_seg_bm.out_hdf,
310+
dataset_dir=dl_ds_buckeye.out_hub_cache_dir,
311+
dataset_key="val",
312+
dataset_offset_factors=_DATASET_OFFSET_FACTORS["buckeye"],
313+
)
314+
_m_bm.add_alias(f"{_bm_name}-metric")
315+
reg(f"{_bm_name}-accuracy.txt", _m_bm.out_accuracy)
316+
reg(f"{_bm_name}-chunk_idx_mae.txt", _m_bm.out_chunk_idx_mae)
317+
318+
# 2) exit_bias: a global words-per-chunk knob (added to EVERY exit),
319+
# vs empty_exit_penalty which only hits chunks exiting with zero words,
320+
# and which we showed is a pure heuristic artifact.
321+
# Swept with eep=0 so the bias is the only exit knob.
322+
# bias=0 is the existing cs2-ov0-eep0 (0.8535).
323+
for _bias in [-2.0, -1.0, 1.0, 2.0]:
324+
_seg_eb = ChunkSegmentationFromModelBatchedJob(
325+
dataset_dir=dl_ds_buckeye.out_hub_cache_dir,
326+
dataset_key="val",
327+
model_config=_cfg_hp,
328+
chunk_size_secs=2.0,
329+
chunk_overlap_secs=0.0,
330+
empty_exit_penalty=0.0,
331+
max_batch_size=8,
332+
exit_bias=_bias,
333+
)
334+
_eb_name = f"chunk-align/phi4mm-buckeye-val-cs2-ov0-eep0-bias{_bias:g}"
335+
_seg_eb.add_alias(_eb_name)
336+
reg(f"{_eb_name}.hdf", _seg_eb.out_hdf)
337+
_m_eb = CalcChunkAssignmentMetricsJob(
338+
chunk_seg_hdf=_seg_eb.out_hdf,
339+
dataset_dir=dl_ds_buckeye.out_hub_cache_dir,
340+
dataset_key="val",
341+
dataset_offset_factors=_DATASET_OFFSET_FACTORS["buckeye"],
342+
)
343+
_m_eb.add_alias(f"{_eb_name}-metric")
344+
reg(f"{_eb_name}-accuracy.txt", _m_eb.out_accuracy)
345+
reg(f"{_eb_name}-chunk_idx_mae.txt", _m_eb.out_chunk_idx_mae)
346+
347+
# 3) length_norm: score a word by its per-token MEAN log-prob instead of the sum,
348+
# so a multi-token word is not systematically dearer to emit than the single exit token.
349+
# Compare against the plain cs*-ov* jobs (cs2-ov0 0.8747, cs10-ov2.5 0.9856, cs1-ov0 0.6318).
350+
for _cs, _ov in [(2.0, 0.0), (10.0, 2.5), (1.0, 0.0)]:
351+
_seg_ln = ChunkSegmentationFromModelBatchedJob(
352+
dataset_dir=dl_ds_buckeye.out_hub_cache_dir,
353+
dataset_key="val",
354+
model_config=_cfg_hp,
355+
chunk_size_secs=_cs,
356+
chunk_overlap_secs=_ov,
357+
max_batch_size=8,
358+
length_norm=True,
359+
)
360+
_ln_name = f"chunk-align/phi4mm-buckeye-val-cs{_cs:.0f}-ov{_ov:g}-lnorm"
361+
_seg_ln.add_alias(_ln_name)
362+
reg(f"{_ln_name}.hdf", _seg_ln.out_hdf)
363+
_m_ln = CalcChunkAssignmentMetricsJob(
364+
chunk_seg_hdf=_seg_ln.out_hdf,
365+
dataset_dir=dl_ds_buckeye.out_hub_cache_dir,
366+
dataset_key="val",
367+
dataset_offset_factors=_DATASET_OFFSET_FACTORS["buckeye"],
368+
)
369+
_m_ln.add_alias(f"{_ln_name}-metric")
370+
reg(f"{_ln_name}-accuracy.txt", _m_ln.out_accuracy)
371+
reg(f"{_ln_name}-chunk_idx_mae.txt", _m_ln.out_chunk_idx_mae)
372+
283373
# fp32 batched (default fast path) at cs30, to check the fast path (esp. batched_logprobs) is
284374
# bit-exact vs the fp32 single-seq reference below. The bf16 sweep diverges more for small
285375
# chunks, so this isolates real logic differences from bf16 numerical noise.

0 commit comments

Comments
 (0)