# Tiny Hinglish Turn Detector An audio-native, low-latency **HOLD vs END** classifier for voice agents, built for the Shiprocket Data Scientist challenge. The model is evaluated only at VAD pause checkpoints: `HOLD` means “keep listening”; `END` means “the agent may respond.” The packaged artifact is a development preview: a 151,812-parameter causal TinyTCN using the most recent four seconds of audio. It exports to a 625,431-byte FP32 ONNX file and runs waveform-to-probability inference at 1.63 ms p95 on the measured Apple-arm CPU setup. It does **not** beat the acoustic logistic baseline on the current IID development split, and it has not been evaluated on the official test or on verified Hinglish speech. The repository is submission-grade engineering evidence, not a production-readiness claim. ## Current evidence, at a glance | Item | Status | What is actually established | |---|---|---| | Upstream train snapshot | Partial locally | 1 of 83 Parquet shards audited: 3,265 rows, 6.857 hours | | Data integrity on that shard | Measured | 3,265/3,265 valid; no exact-audio, ID, or label-conflict duplicates | | Split integrity | Measured, best effort | IID and source-held-out manifests pass observed-key crossing checks; all 3,265 groups are singletons because speaker/conversation/voice IDs are absent | | Interpretable development baseline | Measured, partial-shard only | 10 acoustic statistics: AP 0.7310, AUROC 0.7522, END recall 0.1313 at FPR 0.0181 | | TinyTCN preview | Measured, partial-shard only | 151,812 parameters: AP 0.6881, AUROC 0.7390, END recall 0.0625 at FPR 0.0181 | | Paired comparison | Baseline remains winner | TinyTCN minus baseline: AP −0.0429 (95% bootstrap CI −0.1195 to 0.0352); constrained recall −0.0688 (−0.1330 to −0.0122) | | Domain-shift stress | Measured, confounded | Source-held-out AP 0.5500/0.5368 for TinyTCN/baseline; both near chance in ranking, with wide nine-source bootstrap intervals | | Runtime/export | Measured | 611 KiB FP32 ONNX; exact PyTorch/ONNX parity; ONNX p95 0.956 ms neural-only and 1.629 ms waveform-to-probability | | Controller integration | Synthetic only | Metadata-bound 8-checkpoint/3-turn fixture emits 3 response edges with 0 duplicates; not model or product-quality evidence | | Code quality gate | Passed | Ruff 0.15.16; 136/136 automated tests passed via `unittest` discovery | | Accuracy/generalization | **Not established** | No full-corpus run, collected Hinglish recordings, real conversation replay, or official-test evaluation | | Publication tooling | Guarded | Hash-verified HF publisher plus curated Kaggle/GitHub upload builders; verify each remote runtime separately | The official test set remains deliberately sealed. The preview is real development evidence, but validation was reused for threshold selection, checkpoint selection, and bounded follow-up experiments. It is therefore adaptive development evidence—not an independent estimate. See [REPORT.md](REPORT.md) for the full reasoning and [MODEL_CARD.md](MODEL_CARD.md) for the release contract. The acoustic baseline is the first real-data development result: logistic regression on ten waveform statistics, trained on all 2,939 train rows and scored on all 326 validation rows in the single audited shard. At a threshold selected on that same validation split for `FPR <= 0.02`, it has 3 FP, 21 TP, 163 TN, and 139 FN (`FPR 0.0181`, END recall `0.1313`, AUROC `0.7522`, AP `0.7310`). The selected TinyTCN reaches 3 FP, 10 TP, 163 TN, and 150 FN at threshold `0.7410008` on the same split (`FPR 0.0181`, END recall `0.0625`, AUROC `0.7390`, AP `0.6881`, Brier `0.2133`). Its FPR bootstrap interval is `[0, 0.0407]`, so this sample does not establish a population 2% interruption bound. Neither result is independent-test or Hinglish evidence. Because that baseline is the evidence winner, its 10-weight JSON artifact is packaged beside the neural model under `reference_models/`; the Gradio Space continues to demonstrate the ONNX TinyTCN and labels that choice explicitly. ## Why this design Turn detection is asymmetric. A false `END` interrupts the user and can derail an entire transaction; a false `HOLD` adds latency. Accuracy at an arbitrary 0.5 threshold therefore is not the primary target. The selection criterion is **maximum END recall subject to a false-positive (false-interruption) budget**, with FPR reported at 1%, 2%, and 5% operating points. The production boundary is also explicit: ```text 16 kHz audio suffix -> 80-bin log-mel frontend -> causal depthwise-separable TinyTCN -> mask-aware attentive mean/std pooling -> END probability + optional filler auxiliary heads -> calibrated SPEAKING / HOLD / END controller ``` The selected preview has 151,812 parameters, six 128-channel blocks, and a four-second suffix. Its convolutions are causal; final attentive pooling makes this a suffix-level classifier, not a state-cached streaming network. The repository retains a larger 396,164-parameter/eight-second configuration as a proposed full-data experiment, not as the packaged model. The controller—not the neural network—owns minimum silence, threshold relaxation, debounce, and maximum wait. No ASR transcript is required at inference. That keeps the path small and avoids coupling endpoint latency to transcription, while the auxiliary `midfiller` and `endfiller` objectives encourage the representation to notice exactly the failure cases the challenge emphasizes. A Whisper-tiny teacher configuration is included as an experiment, not assumed to be the best serving model. ## Repository map | Path | Purpose | |---|---| | `src/turn_detection/data/` | Lazy Parquet/HF ingestion, schema normalization, audio audit, transitive grouping, split validation | | `src/turn_detection/models/` | Canonical log-mel frontend, TinyTCN student, Whisper teacher, attentive pooling | | `src/turn_detection/training/` | Lazy dataloaders, masked multitask loss, threshold calibration, product metrics, trainer | | `src/turn_detection/runtime/` | NumPy preprocessing, ONNX inference, turn controller, replay evaluation | | `configs/` | Reproducible student, teacher, and smoke configurations | | `scripts/` | Download, audit, split, train, evaluate, export, benchmark, package, publish | | `data/collection/` | Deterministic 900-recording Hinglish assignment plan; no recordings are included | | `reports/` | Measured one-shard audit and split reports | | `tests/` | Unit and integration coverage across data, model, metrics, runtime, and prompts | | `deployment/kaggle/` | Flat, standalone ONNX inference templates for Kaggle Models | | `upload-ready/` | Generated Kaggle and GitHub upload folders; never a source input | ## Reproduce the pipeline Python 3.10–3.12 and [`uv`](https://docs.astral.sh/uv/) are recommended. These commands target a full source checkout. Raw audio, downloaded Parquet, processed manifests, and per-example predictions are intentionally not redistributed; recreate them with the pinned download/audit commands below. ```bash uv sync --extra all uv run pytest ``` The curated public repository already includes the selected ONNX artifact, so the demo resolves it without an environment variable: ```bash uv sync --extra demo uv run python app.py ``` Place the Hugging Face token in the ignored `.env` file; never commit it: ```bash cp .env.example .env # edit .env and set HF_TOKEN ``` ### 1. Download the pinned train snapshot ```bash bash scripts/download_dataset.sh ``` The script pins revision `e564e2ac567f774d1880aa1db6ce97afb8c519b7`, resumes safely, and refuses to declare success until all 83 Parquet shards are present. ### 2. Audit and create manifests ```bash uv run python scripts/audit_dataset.py \ data/raw/smart-turn-data-v3.2-train \ --output data/processed/manifest.jsonl \ --report artifacts/data_audit.json \ --fail-on-error uv run python scripts/prepare_splits.py \ --input data/processed/manifest.jsonl \ --output data/processed/splits.jsonl \ --report artifacts/split_report.json \ --split train=0.9 --split validation=0.1 \ --stratify endpoint,language,synthetic,dataset ``` The audit hashes encoded audio and creates privacy-preserving linkage keys for conversation, speaker, voice, recording, prompt, record, and repeated normalized text. Union-find turns overlapping keys into transitive components; the split tool assigns whole components and fails validation if any group, audio hash, or metadata key crosses splits. On the available shard, however, upstream rows expose no usable speaker, conversation, or TTS-voice identity: all 3,265 base linkage groups used by the IID split contain one row. (The separate stress split deliberately regroups rows by 12 source-dataset values.) The measured split is therefore best-effort row-disjoint and duplicate-safe, **not speaker-disjoint**. Its grouped bootstrap is effectively a row bootstrap; speaker/voice leakage remains unknown until richer identity metadata or a new consented speaker-disjoint set exists. For a harsher domain-shift test, add `--holdout-field dataset`. Interpret that result carefully: in the currently inspected shard, source is strongly confounded with language and synthetic status. Leave-one-source-out helpers are also available in `turn_detection.data.splits`. ### 3. Prove the training path, then run the real experiment ```bash # Fast generated-feature integration check; never report as model quality. uv run python scripts/train.py --config configs/smoke.json --smoke-test # Reproduce the selected one-shard preview (development evidence only). uv run python scripts/train.py \ --config configs/partial_shard_warmstart_lr3e4_5ep.json # Proposed primary full-data student experiment, once all shards are audited. uv run python scripts/train.py --config configs/tiny_tcn.yaml ``` Run the interpretable partial-shard sanity baseline independently: ```bash uv run python scripts/run_baselines.py audio \ --manifest data/processed/partial-iid-splits.jsonl \ --source-root . \ --output artifacts/partial-baseline \ --epochs 1000 --fpr-budget 0.02 ``` The trainer writes the resolved configuration, epoch history, calibrated threshold, and best checkpoint. Deterministic mode seeds model initialization, data order, and training. Missing filler labels stay missing and are masked from their auxiliary BCE losses. ### 4. Evaluate without touching the official test ```bash uv run python scripts/evaluate.py \ --checkpoint artifacts/partial-shard-warmstart-lr3e4-5ep/best.pt \ --source data/processed/partial-iid-splits.jsonl \ --source-root . \ --split validation \ --output reports/partial_tinytcn_metrics.json ``` Evaluation reports confusion counts, FPR/FNR, AUROC, average precision, Brier score, ECE/reliability bins, operating points under FPR budgets, and language/source/synthetic slices. Turn-level interruption rates are emitted only when genuine turn/conversation IDs exist; this shard supports only a clearly labelled clip-normalized per-audio-hour proxy. Final comparisons need real speaker- or conversation-cluster confidence intervals. The selected checkpoint was chosen by average precision after earlier runs had already been inspected on this validation set. Its threshold was also selected on the same 326 examples. Reproduction should yield the stored numbers, but they must not be treated as a fresh holdout result. ### 5. Freeze, unseal once, and evaluate the official test Only an experiment trained with exact `run.status: final` can be frozen. The freeze manifest hashes the checkpoint, selected config, source tree, preprocessing, controller policy, split manifest, threshold, and pinned test identity. `configs/final.yaml` and `artifacts/final/*` below are intentionally future-candidate placeholders; create them only after completing the full-data experiment. Only then: ```bash uv run python scripts/freeze_candidate.py \ --checkpoint artifacts/final/best.pt \ --config configs/final.yaml \ --output artifacts/final/frozen_manifest.json UNSEAL_OFFICIAL_TEST=I_HAVE_FROZEN_MODEL_AND_THRESHOLD \ FROZEN_MANIFEST=artifacts/final/frozen_manifest.json \ bash scripts/download_test_dataset.sh uv run python scripts/evaluate.py \ --checkpoint artifacts/final/best.pt \ --source data/raw/smart-turn-data-v3.2-test \ --dataset-id pipecat-ai/smart-turn-data-v3.2-test \ --revision 0500378e8ed6d38e37b016e24d261e8e6c6a6859 \ --split test \ --allow-sealed-test \ --frozen-manifest artifacts/final/frozen_manifest.json \ --output artifacts/final/test_metrics.json ``` Do not tune after reading the test result. A new model requires a new untouched test set or a clearly labeled exploratory result. ### 6. Export and benchmark ```bash uv run python scripts/export_onnx.py \ --checkpoint artifacts/final/best.pt \ --output artifacts/final/model.onnx uv run python scripts/benchmark.py \ --model artifacts/final/model.onnx \ --metadata artifacts/final/model_metadata.json \ --threads 1 --batch-size 1 \ --output artifacts/final/cpu_benchmark.json ``` ONNX export checks PyTorch/ONNX numerical parity. Runtime preprocessing, I/O names, activation semantics, threshold, parameter count, and evidence scope live in `model_metadata.json`; hashes, shapes, checkpoint/config/split bindings, parity, and quantization status live in `export_manifest.json`. Static INT8 is supported with representative calibration features; quantized scores require threshold recalibration before release. The packaged preview is `artifacts/partial-shard-warmstart-lr3e4-5ep/model.onnx`: 625,431 bytes, 151,812 parameters, and FP32 maximum absolute parity error `0.0`. On the recorded Apple-arm/one-thread runs, ONNX neural-only p50/p95/p99 is 0.919/0.956/0.974 ms; four-second waveform-to-probability p50/p95/p99 is 1.572/1.629/1.651 ms. These are local implementation measurements, not a deployment SLA. ### 7. Run the demo and exercise the controller ```bash TURN_MODEL_PATH=artifacts/partial-shard-warmstart-lr3e4-5ep/model.onnx \ uv run python app.py uv run python scripts/replay_stream.py \ --input data/collection/controller_replay_fixture.jsonl \ --output reports/controller_replay_integration.jsonl \ --metadata artifacts/partial-shard-warmstart-lr3e4-5ep/model_metadata.json \ --evidence-scope synthetic_integration ``` The exported metadata binds the endpoint threshold and the complete controller policy. `END` is latched until new speech or an explicit reset; only the first END transition sets `emit_response=true`, preventing duplicate agent replies. The bundled replay fixture is hand-authored integration data: its probabilities, targets, and latency values are synthetic. It validates policy binding, threshold relaxation, timeout, END latching, and edge emission only—it is not model, conversation, latency, or product-quality evidence. Replace its input with private, human-annotated VAD checkpoint logs for a real sequence study. If weights are absent, the demo displays a conspicuous heuristic fallback. That fallback validates only the UI and must never be cited as a model result. See [docs/demo.md](docs/demo.md) for the pause-checkpoint contract. ### 8. Build and publish a guarded release ```bash uv run python scripts/build_release.py \ --model artifacts/final/model.onnx \ --metadata artifacts/final/model_metadata.json \ --metrics artifacts/final/test_metrics.json \ --frozen-manifest artifacts/final/frozen_manifest.json # Dry validation first. uv run python scripts/publish_hf.py --username suvradeepp # Creates/uploads the model repo and Gradio Space only after identity validation. uv run python scripts/publish_hf.py --username suvradeepp --execute ``` For a personal account without Hugging Face PRO, the publisher requests the free-account-compatible ZeroGPU tier (`--space-hardware zero-a10g`) only when it must create a new Space. Hugging Face currently gates new personal Gradio CPU Spaces behind PRO even when CPU Basic has no hourly charge. ZeroGPU eligibility still requires a verified account in good standing and an available hosted Space slot. Paid accounts may override this with `--space-hardware cpu-basic`. The release builder refuses smoke checkpoints or a release without measured test metrics unless `--allow-development-artifact` is passed explicitly. That override must remain visibly development-only. For a reviewable partial-data preview, pass that flag to the builder and `--allow-development-release` to both publisher invocations. Development validation metrics are packaged as `development_metrics.json`, never `test_metrics.json`. The exact development-preview package command is: ```bash uv run python scripts/build_release.py \ --model artifacts/partial-shard-warmstart-lr3e4-5ep/model.onnx \ --metadata artifacts/partial-shard-warmstart-lr3e4-5ep/model_metadata.json \ --metrics reports/partial_tinytcn_metrics.json \ --output release \ --allow-development-artifact \ --include-synthetic-controller-replay uv run python scripts/publish_hf.py \ --username suvradeepp \ --release-dir release \ --allow-development-release uv run python scripts/build_upload_folders.py \ --release-dir release \ --output upload-ready \ --allow-development-release ``` `upload-ready/kaggle-model/` is a flat drag-and-drop ONNX bundle. `upload-ready/github-repository/` is a public-repository allowlist containing source, CI, tests, aggregate evidence, ONNX weights, and the two checkpoints needed to reproduce the selected warm start. It excludes credentials, caches, raw/processed data, per-example predictions, stale releases, and publish receipts. Keep the Kaggle variation private until the derived-weight rights review described in `NOTICE` and `MODEL_CARD.md` is resolved. Add `--execute` only after the dry validation succeeds. The publisher verifies the authenticated owner and every packaged hash before creating or updating the remote model repository and Space. Its receipt binds exact remote HEAD commits, requires a visible build/start transition for a new Space upload, and records the final `RUNNING` stage. Because the installed Hub API does not expose the commit actually serving behind a Space runtime, the receipt explicitly records `runtime_commit_bound=false` instead of overstating that guarantee. The release hash-binds the 2.7 MB split manifest but intentionally does not redistribute it because it contains upstream text and record identifiers. The public package carries deterministic preparation code, the pinned revision, manifest SHA-256, and aggregate reports; per-example rows stay private. ## Hinglish evaluation protocol The upstream corpus is multilingual, but a `hin` or `eng` tag is not evidence of within-utterance code-switching. This repository therefore includes a separate, consent-first Shiprocket-domain protocol: - 30 speakers × 30 assignments = 900 intended recordings; - 450 `HOLD` and 450 `END` targets; - 21/4/5 speaker-disjoint train/dev/test speakers; - minimal pairs with natural 0.3–2.0 second internal pauses plus roleplay; - three annotators answer whether responding at the checkpoint feels like an interruption; uncertain votes remain disagreement, not forced labels. Only the assignments and protocol exist today—**no participant audio has been collected**. Do not infer Hinglish performance until that benchmark is recorded and evaluated. See [docs/data_collection_protocol.md](docs/data_collection_protocol.md) and [DATA_CARD.md](DATA_CARD.md). ## What “good enough” means A candidate is releaseable only after all of these gates pass: 1. all 83 train shards audited with zero unresolved hard validation errors; 2. group-safe validation and source/speaker stress tests completed; 3. threshold frozen under a predeclared false-interruption budget; 4. real Hinglish minimal-pair test collected with consent and held out by speaker; 5. official test opened once and reported without subsequent tuning; 6. preprocessing parity, ONNX parity, batch-1 CPU latency, and controller replay measured; 7. failure slices reviewed for language, source, synthetic/human, filler, duration, device/noise, gender/region where consented, and uncertainty; 8. upstream data/model redistribution terms reviewed before publishing weights. The current repository passes export/runtime engineering gates and supplies honest bounded development evidence. It does not pass the full-data, Hinglish, official-test, sequence-replay, or rights-review gates. ## Documentation - [Technical report](REPORT.md) - [Model card](MODEL_CARD.md) - [Data card](DATA_CARD.md) - [Experiment registry](docs/experiment_registry.md) - [Failure-analysis protocol](docs/failure_analysis.md) - [Hinglish collection protocol](docs/data_collection_protocol.md) - [Demo/runtime contract](docs/demo.md) ## License and responsible use Repository-authored code is Apache-2.0; see [LICENSE](LICENSE) and [NOTICE](NOTICE). The upstream dataset card did not provide an explicit dataset license at the inspected revision. This repository does not redistribute its audio. Apache-2.0 on the code does **not** grant rights to upstream audio, recordings collected under the local protocol, speaker likeness, customer data, or the development model weights. The preview weights carry no license grant pending an upstream-rights review; review data and derived-weight terms before use or redistribution. This system decides conversational timing; it should not be used for speaker identification, emotion inference, surveillance, or consequential decisions.