--- license: mit language: - en base_model: LiquidAI/LFM2.5-350M-Base tags: - speech-to-text - transcript-cleanup - text-correction - asr-post-processing - LFM - LiquidAI - grpo - full-fine-tune - inverse-text-normalization pipeline_tag: text-generation datasets: - juanquivilla/sotto-transcript-cleanup --- # SottoASR Transcript Cleanup — LFM2.5-350M (Full Precision, soup_30) [sottoasr.app](https://sottoasr.app) · [MLX 5-bit (recommended)](https://huggingface.co/juanquivilla/sotto-cleanup-lfm25-350m-mlx-5bit) · [MLX 4-bit (smaller)](https://huggingface.co/juanquivilla/sotto-cleanup-lfm25-350m-mlx-4bit) · [Training Dataset](https://huggingface.co/datasets/juanquivilla/sotto-transcript-cleanup) ## Overview Full-precision bf16 fine-tune of [LiquidAI/LFM2.5-350M-Base](https://huggingface.co/LiquidAI/LFM2.5-350M-Base) for on-device speech-to-text transcript cleanup. This is the **training artifact** — for on-device deployment on Apple Silicon, use the [5-bit MLX variant](https://huggingface.co/juanquivilla/sotto-cleanup-lfm25-350m-mlx-5bit). ## What's new in soup_30 soup_30 extends v45 with targeted training data covering five failure modes observed on real production transcripts: - **Multi-number sentences** ("the score was 21 to 14") - **Year-with-context drift** ("twenty eighteen and twenty twenty seven" → preserve both years) - **Disconnected number lists** (no phone-number hallucination on "one two three eleven forty six") - **Within-input duplicates** (same number stated twice — preserve consistency) - **Long-form preservation** (multi-paragraph dictations with embedded numbers) The v37+v47 training data (~30K examples) was re-audited end-to-end with a Qwen3.6-27B judge and bad rows were dropped. A new 66-case adversarial benchmark drives objective evaluation. ## Headline numbers | Capability | v36 | v45 | **soup_30 (this model)** | |---|---:|---:|---:| | Number accuracy (171-sample stratified val) | 12.9% | 95.9% | **96.5%** | | 66-case adversarial benchmark (greedy) | n/a | 76% | **86%** | | 66-case adversarial benchmark (temp 0.7 × 4) | n/a | 77% | **85%** | | Loops on 264 sampling-mode probes | n/a | 0 | **2** | | Filler-free on 241 long inputs | 67.2% | 68.0% | **73.0%** | | Sub-deletion >15% on 241 long inputs | 13.3% | 13.7% | **4.6%** | Composite score (0.35×num + 0.30×bench_greedy + 0.15×bench_sample + 0.10×filler_long + 0.05×(1-sub15) + 0.05×(1-loops/N)): **89.45** ## Training pipeline ``` LiquidAI/LFM2.5-350M-Base → SFT v23 → GRPO v23 (paragraph emission) → GRPO v36: full FT with substantive-deletion-aware reward → SFT v39: + 12.7K augmented number examples (ITN) → GRPO v40-v45: chained refinement, fixed reward + amplified filler penalty → SFT/GRPO soup_30: + 5.5K gap-filling examples (multi-number, year-context, disconnected, duplicates, long-form), Qwen-audited and cleaned ``` ## Usage ```python from transformers import AutoModelForCausalLM, AutoTokenizer import torch model = AutoModelForCausalLM.from_pretrained( "juanquivilla/sotto-cleanup-lfm25-350m", dtype=torch.bfloat16, trust_remote_code=True, ) tokenizer = AutoTokenizer.from_pretrained("juanquivilla/sotto-cleanup-lfm25-350m") text = "talk about server three sixty" prompt = f"### Input:\n{text}\n\n### Output:\n" inputs = tokenizer(prompt, return_tensors="pt").to(model.device) with torch.no_grad(): out = model.generate(**inputs, max_new_tokens=512, do_sample=False) output = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) if "###" in output: output = output[:output.index("###")] print(output.strip()) ``` ## All Variants | Variant | Size | Use Case | |---------|------|----------| | **[Full precision (this)](https://huggingface.co/juanquivilla/sotto-cleanup-lfm25-350m)** | 676 MB | Training, GPU inference | | **[MLX 5-bit](https://huggingface.co/juanquivilla/sotto-cleanup-lfm25-350m-mlx-5bit)** | ~237 MB | **Recommended for Apple Silicon** | | [MLX 4-bit](https://huggingface.co/juanquivilla/sotto-cleanup-lfm25-350m-mlx-4bit) | ~195 MB | Smallest | ## License MIT