ADR-228: SpectrogramStage, TokenStage, SetManifestSplitter and DVC entry-points#233
Conversation
…C entry-points - SpectrogramStage: deterministic stage producing (n_mels, time_steps) .npy files via librosa - TokenStage: deterministic stage producing padded phoneme token .json files - SetManifestSplitter: splits augmented manifest into train/val/test by percentage - DVC entry-points: speech_07_compute_tokens, speech_08_compute_spectrograms, speech_09_create_set_manifests - PipelineParams extended with ComputeSpectrogramsParams, ComputeTokensParams, CreateSetManifestsParams - params.yaml updated with compute_spectrograms, compute_tokens, create_set_manifests sections
jodavis-claude
left a comment
There was a problem hiding this comment.
Review — ADR-228: SpectrogramStage, TokenStage, SetManifestSplitter
Summary
The implementation is correct and well-structured. All exit criteria from the task brief are met: _is_deterministic = True as a class variable, _derive_id returns input_sample.id unchanged, _get_applied_values returns {}, spectrograms are the right shape, tokens are padded/truncated to input_token_length, SetManifestSplitter validates percentages and writes via conventions.split_manifest_path, and all three DVC entry-points follow the established pattern. The 66 new tests give solid coverage.
Two issues to address before merging:
Priority 3 — Performance
TokenStage._generate_output does blocking file I/O on the async event loop (token_stage.py:77–78). SpectrogramStage correctly offloads np.save via run_in_executor (line 82). TokenStage's json.dump should be consistent. Practical impact is low today (sequential processing, fast writes), but the inconsistency with the stated pattern should be fixed.
Priority 4 — Documentation
No _doc_*.md for ml/pipeline/speech/. CONTRIBUTING.md requires documentation files for new subsystems. ADR-228 completes the first major cohort of speech pipeline stages and is the natural point to add one, covering: the two concerns in the directory (augmentation vs. featurisation), the _is_deterministic/parent_id design decisions, and SetManifestSplitter design intent.
…ency with SpectrogramStage
jodavis-claude
left a comment
There was a problem hiding this comment.
Sign-off review — ADR-228 (SpectrogramStage, TokenStage, SetManifestSplitter)
Both review threads from the first pass are resolved:
-
TokenStage blocking I/O (resolved) —
_generate_outputnow usesloop.run_in_executor(None, lambda: output_path.write_text(json.dumps(payload), encoding="utf-8")), consistent with thenp.savepattern inSpectrogramStage.import asyncioadded. (commit4b6c690) -
_doc_speech.mdmissing (resolved) — New doc covers both cohorts (augmentation vs. featurisation), the_is_deterministic/parent_id/_derive_iddesign decisions for featurisation stages, spectrogram padding/truncation strategy, blocking compute offload rationale, andSetManifestSplitterintent (augmented manifest,seed=42, stdlibrandom.Random,conventions.split_manifest_pathnaming). Links to source files throughout. (commitf3cf630)
No new issues found in the two modified files (token_stage.py, _doc_speech.md).
Outcome: approved — ready for human review.
There was a problem hiding this comment.
Pull request overview
Adds new speech-pipeline components in ml/ to (a) derive phoneme token targets, (b) compute log-mel spectrogram features, and (c) split an augmented manifest into train/val/test sets, along with new DVC-style stage entry points and corresponding PipelineParams/params.yaml updates.
Changes:
- Added
TokenStage,SpectrogramStage, andSetManifestSplitterplus three new stage entry-point scripts (speech_07/08/09_*). - Extended
PipelineParams(and tests) to loadcompute_spectrograms,compute_tokens, andcreate_set_manifestssections. - Added unit tests for the new stages/splitter and updated
ml/params.yamldefaults.
Confidence: 95/100
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| ml/pipeline/speech/token_stage.py | New deterministic stage writing per-sample token JSON outputs. |
| ml/pipeline/speech/spectrogram_stage.py | New deterministic stage writing per-sample spectrogram .npy outputs. |
| ml/pipeline/speech/set_splitter.py | New helper to split a manifest into train/val/test and write split manifests. |
| ml/pipeline/stages/speech_07_compute_tokens.py | CLI entry-point to run TokenStage over a manifest. |
| ml/pipeline/stages/speech_08_compute_spectrograms.py | CLI entry-point to run SpectrogramStage over a manifest. |
| ml/pipeline/stages/speech_09_create_set_manifests.py | CLI entry-point to split a manifest into set manifests. |
| ml/pipeline/stages/params.py | Adds dataclasses + loader wiring for the new stage parameter sections. |
| ml/params.yaml | Adds default config blocks for the new stage parameters. |
| ml/test/pipeline/stages/test_params.py | Extends params-loading tests for the new sections. |
| ml/test/pipeline/speech/test_token_stage.py | Adds unit tests for TokenStage. |
| ml/test/pipeline/speech/test_spectrogram_stage.py | Adds unit tests for SpectrogramStage. |
| ml/test/pipeline/speech/test_set_splitter.py | Adds unit tests for SetManifestSplitter. |
… for predictable split boundaries Python's round() uses bankers rounding, which can produce unintuitive results at .5 boundaries. Integer truncation for train/val with remainder assigned to test is simpler and avoids unexpected off-by-one behaviour on small manifests.
librosa.power_to_db may return float64; adding .astype(np.float32) ensures the saved .npy always matches the documented contract. Added test_output_npy_dtype_is_float32 to verify.
…Stage and TokenStage
Reviewer (jodavis) confirmed that per-file content_hash is used to determine
whether individual outputs are up-to-date between pipeline runs, not just the
DVC stage-level dependency. Returning {} from _get_applied_values means that
changing n_mels/time_steps/input_token_length/phoneme_list does not invalidate
previously-cached outputs on a per-file basis.
SpectrogramStage now returns {"n_mels": ..., "time_steps": ...}.
TokenStage now returns {"input_token_length": ..., "phoneme_list": [...]}.
Replaced test_get_applied_values_returns_empty_dict with three tests each that
assert the new config keys are present and reflect constructed values. Added
test_changing_n_mels_triggers_regen, test_changing_time_steps_triggers_regen,
test_changing_input_token_length_triggers_regen, and
test_changing_phoneme_list_triggers_regen to verify end-to-end regen behaviour.
Summary
SpectrogramStage— deterministicModifierStagethat computes log-mel spectrograms via librosa (offloaded to thread pool), pads/truncates to(n_mels, time_steps), and writes.npyoutputTokenStage— deterministicModifierStagethat converts transcripts to padded phoneme token sequences using an injectedVocabResult; silently skips missing words; writes.jsonoutputSetManifestSplitter— splits the fully-augmentedManifest[AudioSample]into train/val/test subsets usingconventions.split_manifest_path; validates percentages sum to 100speech_07_compute_tokens.py,speech_08_compute_spectrograms.py,speech_09_create_set_manifests.pyPipelineParamswithComputeSpectrogramsParams,ComputeTokensParams,CreateSetManifestsParamsTest plan
SpectrogramStage:_is_deterministicis a class variable set toTrue;_derive_idreturnsinput_sample.id;_get_applied_valuesreturns{}; output.npyshape is(n_mels, time_steps);parent_idequalsinput_sample.id; skip-unchanged path does not call reader on second runTokenStage: same determinism checks; output JSON hasphonemesandtokenskeys; tokens padded toinput_token_lengthusingctc_blank_idx; missing words skipped silently; skip-unchanged path worksSetManifestSplitter: percentages not summing to 100 raiseValueError; split counts match percentages; all input IDs appear in exactly one split; output files namedtrain_manifest.json,val_manifest.json,test_manifest.json; same seed produces same splitsPipelineParams: new param sections load correctly; missing sections raisescripts/validate-buildpasses (zero warnings)scripts/validate-testspasses (all 347 tests)🤖 Generated with Claude Code