nemo_curator.stages.audio.alm.pretrain.planning
nemo_curator.stages.audio.alm.pretrain.planning
Segment-level planning: overlap drop, greedy packing, repetition filter.
These three stages all operate on a task’s segments / _snippet_plan
in memory, before the extractor reads any audio. The pure helpers
(filter_empty_segments, find_overlapping_indices, plan_snippets,
relativize_segments, n-gram counters, color highlighting) are unit-
testable without Ray / soundfile / torch.
Module Contents
Classes
Functions
Data
API
Bases: ProcessingStage[AudioTask, AudioTask]
Drop empty segments and overlapping segment pairs.
First filters segments that have neither text nor words. Then drops
every segment that overlaps any other surviving segment, where
“overlap” means intersection ≥ min_overlap_sec OR one fully
contains the other. Both members of an overlapping pair are
discarded — this version keeps no overlap-resolution heuristic.
Per-original counters are stamped onto task._metadata under the
pretrain_long_form key so the final aggregator can build a
per-original metrics breakdown.
Bases: ProcessingStage[AudioTask, AudioTask]
Compute snippet cut boundaries for one input audio.
Pure planning — no audio I/O. Produces a list of snippet specs
each holding start, end (absolute seconds in the source
audio) and the contained segments. The plan is stored under
task.data["_snippet_plan"] for the downstream extractor to act
on. Drop counts (too_long, too_short, no_text) are
written to task._metadata['pretrain_long_form'].
Bases: ProcessingStage[AudioTask, AudioTask]
Drop planned snippets whose text shows suspicious n-gram repetition.
Whisper-style ASR sometimes degenerates into repeating the same short phrase for many seconds; the resulting transcript looks fine locally but contains the same n-gram of token ids dozens of times. Such snippets are unsuitable for pretraining.
For every planned snippet (read from task.data["_snippet_plan"])
we join the segment text fields with the same formula the
extractor uses, tokenize with the configured HuggingFace fast
tokenizer, count n-gram frequencies over the resulting token-id
sequence, and drop the snippet if any n-gram appears strictly more
than ngram_max_count times. Filtered snippets are logged with
the offending occurrences highlighted in red (loguru color tags).
Snippets whose tokenized text has fewer than ngram_n tokens are
kept unchanged (no n-grams to evaluate; the planner already enforces
a minimum-duration threshold).
Sits between :class:SnippetCutPlannerStage and
:class:SnippetExtractionStage so filtered snippets never incur
audio decode / resample / file-write cost.
tokenizer_path is either a local directory loadable by
AutoTokenizer.from_pretrained or a HuggingFace Hub repository id
(e.g. openai/whisper-large-v3). When it’s a repo id, the
tokenizer is fetched once per node in :meth:setup_on_node so
workers in :meth:setup only ever read from the local cache.
Tokenize text and decide whether to drop the snippet.
On drop, emit a colorized warning showing the offending n-gram occurrences highlighted in red.
Count contiguous n-gram frequencies in a token id sequence.
Return n-grams whose frequency strictly exceeds max_count.
Wrap each char range in loguru <red>...</red> markup.
Literal < in the surrounding text is escaped to \< so
loguru’s tag parser leaves it alone. ranges must be merged and
sorted (use :func:_merge_char_ranges).
Char-range spans for every position where an offending n-gram starts.
Merge overlapping or touching char ranges; input may be unsorted.
Drop segments with no text and no words.
Returns (kept, dropped_count). Order is preserved.
Indices of segments that overlap any other segment.
Two segments are considered overlapping (and both indices are
returned) iff they share at least min_overlap_sec seconds of
intersection OR one fully contains the other. Brief touch-ups
smaller than min_overlap_sec where neither covers the other are
not flagged.
Implementation is a sweep-line scan over segments sorted by
(start, end). An end-time-keyed min-heap holds the currently
active intervals (those whose end is still beyond the cursor’s
start); each new segment evicts the heap prefix it can no longer
intersect and is then compared only against the survivors. For
typical diarized audio (a handful of overlapping speakers at any
instant) this is effectively O(n log n), vs the pairwise O(n^2)
of comparing every pair; the worst case where all intervals overlap
each other is still O(n^2) because the overlap relation itself
is dense in that case.
Greedy contiguous packing of segments into snippets.
Walks segments (assumed sorted by start) and grows a current
snippet while:
- its span
[first.start, last.end]stays withinmax_duration_sec, AND - the gap from the last accepted segment’s
endto the next segment’sstartis at mostmax_segment_gap_in_snippet.
Either constraint failing closes the current snippet and opens a new
one with the current segment. Single segments longer than
max_duration_sec are emitted as a one-segment candidate and then
dropped under too_long.
The gap constraint matters for ALM pretraining: two segments separated by a long silence often belong to semantically distinct conversations (e.g. a topic change, an ad break, two takes recorded back to back), and a snippet that bridges them would teach the model to associate unrelated content. Closing the snippet at long gaps keeps each training example semantically coherent.
Returns (snippets, drop_counts) where each snippet is a dict with
keys start, end, segments (the actual segment dicts) and
drop counts keys are too_long, too_short, no_text.
Precondition: segments must be non-overlapping (sorted by start
with each end <= next.start). OverlapFilterStage guarantees
this upstream in the pipeline. If overlapping segments are passed in,
gap becomes negative and the gap constraint is silently bypassed,
grouping content that should belong to separate snippets.
Return shallow-copied segments with timestamps shifted to snippet-relative.
Each segment-level and word-level start/end is shifted by
-snippet_start and clamped to [0, snippet_end - snippet_start].
Real diarization data has small (~10 ms) jitter where words are
annotated as starting fractionally before their parent segment or
ending fractionally after, so unclamped values can slip outside
[0, duration] even though the snippet boundaries themselves
align with segment boundaries; clamping keeps downstream consumers
from having to handle that.
Other fields are reused by reference — treat the returned segments as read-only.