Text Generation
Transformers
ONNX
Safetensors
English
qwen2
dictation
cleanup
transcript
lora
mumble
conversational
text-generation-inference
Instructions to use adikuma/mumble-cleanup with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use adikuma/mumble-cleanup with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="adikuma/mumble-cleanup") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("adikuma/mumble-cleanup") model = AutoModelForCausalLM.from_pretrained("adikuma/mumble-cleanup") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use adikuma/mumble-cleanup with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "adikuma/mumble-cleanup" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "adikuma/mumble-cleanup", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/adikuma/mumble-cleanup
- SGLang
How to use adikuma/mumble-cleanup with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "adikuma/mumble-cleanup" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "adikuma/mumble-cleanup", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "adikuma/mumble-cleanup" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "adikuma/mumble-cleanup", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use adikuma/mumble-cleanup with Docker Model Runner:
docker model run hf.co/adikuma/mumble-cleanup
| # the five quality metrics plus a 3-model comparison driver (raw input vs | |
| # qwen base zero-shot vs fine-tuned). all metrics are computed on the held-out | |
| # test split (real disfluencyspeech pairs the model never sees). | |
| # | |
| # the canonical reference is data/pairs/test.json which has rows like | |
| # { "raw": <transcript_a>, "clean": <transcript_c> } | |
| import json | |
| import re | |
| import string | |
| from pathlib import Path | |
| from typing import Callable, Optional | |
| import Levenshtein | |
| import torch | |
| from tqdm import tqdm | |
| # common dictation fillers we track for the removal-rate metric. | |
| FILLER_TOKENS = {"um", "uh", "er", "ah", "like", "you know", "i mean", "so", "well"} | |
| PUNCT_CHARS = set(".,;:!?-") | |
| # ---------- text utilities ---------- | |
| def _words(text: str) -> list[str]: | |
| return text.lower().strip().split() | |
| def _content_words(text: str) -> list[str]: | |
| # drop punctuation, lowercase, split. used by faithfulness metric. | |
| stripped = "".join(c for c in text if c not in string.punctuation) | |
| return stripped.lower().split() | |
| def _count_fillers(text: str) -> int: | |
| # count occurrences of each filler form as whole words. | |
| lower = " " + text.lower() + " " | |
| count = 0 | |
| for f in FILLER_TOKENS: | |
| count += len(re.findall(rf"(?<!\w){re.escape(f)}(?!\w)", lower)) | |
| return count | |
| def _punct_positions(text: str) -> list[tuple[int, str]]: | |
| # return (content_word_index, punct_char) for every sentence punctuation | |
| # mark, anchored to the index of the preceding content word. | |
| content = _content_words(text) | |
| raw_tokens = text.split() | |
| positions: list[tuple[int, str]] = [] | |
| content_idx = -1 | |
| for tok in raw_tokens: | |
| # strip trailing punctuation from the token to find the content word | |
| body = "".join(c for c in tok if c not in string.punctuation) | |
| if body: | |
| content_idx += 1 | |
| tail_punct = "".join(c for c in tok if c in PUNCT_CHARS) | |
| for p in tail_punct: | |
| positions.append((content_idx, p)) | |
| return positions | |
| # ---------- per-example metrics ---------- | |
| def disfluency_removal_rate(raw: str, out: str) -> Optional[float]: | |
| raw_count = _count_fillers(raw) | |
| if raw_count == 0: | |
| return None | |
| survived = _count_fillers(out) | |
| removed = max(0, raw_count - survived) | |
| return removed / raw_count | |
| def punctuation_f1(out: str, ref: str) -> tuple[int, int, int]: | |
| # returns (true_positives, predicted, gold) for a corpus-level micro-f1 | |
| out_positions = set(_punct_positions(out)) | |
| ref_positions = set(_punct_positions(ref)) | |
| tp = len(out_positions & ref_positions) | |
| return tp, len(out_positions), len(ref_positions) | |
| def faithfulness(out: str, ref: str) -> float: | |
| out_words = _content_words(out) | |
| ref_words = _content_words(ref) | |
| if not ref_words: | |
| return 1.0 | |
| # token-level levenshtein on the lowercased content-only word lists. | |
| dist = Levenshtein.distance(" ".join(out_words), " ".join(ref_words)) | |
| ref_len = len(" ".join(ref_words)) | |
| if ref_len == 0: | |
| return 1.0 | |
| return max(0.0, 1.0 - dist / ref_len) | |
| def length_ratio(out: str, ref: str) -> float: | |
| out_words = _content_words(out) | |
| ref_words = _content_words(ref) | |
| if not ref_words: | |
| return 0.0 | |
| return len(out_words) / len(ref_words) | |
| # ---------- aggregation ---------- | |
| def aggregate(rows: list[dict]) -> dict: | |
| # given a list of {"raw": ..., "out": ..., "clean": ...} rows, compute | |
| # corpus-level metrics. returns the dict shape consumed by the report. | |
| disfl = [ | |
| d for d in (disfluency_removal_rate(r["raw"], r["out"]) for r in rows) | |
| if d is not None | |
| ] | |
| tp_sum = pred_sum = gold_sum = 0 | |
| faithful_vals: list[float] = [] | |
| length_vals: list[float] = [] | |
| pass_count = 0 | |
| pass_thresholds = { | |
| "disfluency": 0.95, | |
| "punct_f1": 0.85, | |
| "faithfulness": 0.98, | |
| "length_min": 0.85, | |
| "length_max": 1.05, | |
| } | |
| per_example = [] | |
| for r in rows: | |
| tp, pred, gold = punctuation_f1(r["out"], r["clean"]) | |
| tp_sum += tp | |
| pred_sum += pred | |
| gold_sum += gold | |
| fa = faithfulness(r["out"], r["clean"]) | |
| faithful_vals.append(fa) | |
| lr = length_ratio(r["out"], r["clean"]) | |
| length_vals.append(lr) | |
| d = disfluency_removal_rate(r["raw"], r["out"]) | |
| ok = ( | |
| (d is None or d >= pass_thresholds["disfluency"]) | |
| and fa >= pass_thresholds["faithfulness"] | |
| and pass_thresholds["length_min"] <= lr <= pass_thresholds["length_max"] | |
| ) | |
| per_example.append( | |
| { | |
| "raw": r["raw"], | |
| "out": r["out"], | |
| "clean": r["clean"], | |
| "disfluency_removal": d, | |
| "faithfulness": fa, | |
| "length_ratio": lr, | |
| } | |
| ) | |
| if ok: | |
| pass_count += 1 | |
| precision = tp_sum / pred_sum if pred_sum > 0 else 0.0 | |
| recall = tp_sum / gold_sum if gold_sum > 0 else 0.0 | |
| punct_f1 = ( | |
| 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0 | |
| ) | |
| return { | |
| "num_rows": len(rows), | |
| "disfluency_removal_rate": sum(disfl) / len(disfl) if disfl else None, | |
| "punctuation_precision": precision, | |
| "punctuation_recall": recall, | |
| "punctuation_f1": punct_f1, | |
| "faithfulness_mean": sum(faithful_vals) / len(faithful_vals) if faithful_vals else 0.0, | |
| "length_ratio_mean": sum(length_vals) / len(length_vals) if length_vals else 0.0, | |
| "pass_rate": pass_count / len(rows) if rows else 0.0, | |
| "per_example": per_example[:50], # keep a sample for the report | |
| } | |
| # ---------- model generators ---------- | |
| def make_raw_generator() -> Callable[[str], str]: | |
| # baseline 1: no cleanup at all. lets us measure what shipping nothing | |
| # looks like on the same test set. | |
| def gen(raw: str) -> str: | |
| return raw | |
| return gen | |
| def make_qwen_generator(model_id_or_path: str, adapter_path: Optional[str] = None) -> Callable[[str], str]: | |
| # baseline 2 (no adapter) or row 3 (with adapter): qwen 0.5b. greedy | |
| # decode, max_new_tokens capped so the model cannot balloon into a chat | |
| # reply. | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| from cleanup.prompts import build_messages | |
| tokenizer = AutoTokenizer.from_pretrained(model_id_or_path, use_fast=True) | |
| if tokenizer.pad_token is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| model = AutoModelForCausalLM.from_pretrained( | |
| model_id_or_path, | |
| torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32, | |
| device_map="auto" if torch.cuda.is_available() else None, | |
| ) | |
| if adapter_path: | |
| from peft import PeftModel | |
| model = PeftModel.from_pretrained(model, adapter_path) | |
| model.eval() | |
| def gen(raw: str) -> str: | |
| messages = build_messages(raw) | |
| prompt = tokenizer.apply_chat_template( | |
| messages, tokenize=False, add_generation_prompt=True | |
| ) | |
| inputs = tokenizer(prompt, return_tensors="pt").to(model.device) | |
| raw_token_count = len(tokenizer.encode(raw)) | |
| max_new = min(256, max(8, int(raw_token_count * 1.6))) | |
| with torch.no_grad(): | |
| out_ids = model.generate( | |
| **inputs, | |
| do_sample=False, | |
| max_new_tokens=max_new, | |
| pad_token_id=tokenizer.pad_token_id, | |
| eos_token_id=tokenizer.eos_token_id, | |
| ) | |
| new_tokens = out_ids[0][inputs.input_ids.shape[1]:] | |
| text = tokenizer.decode(new_tokens, skip_special_tokens=True) | |
| return text.strip() | |
| return gen | |
| def evaluate_one(test_rows: list[dict], generator: Callable[[str], str]) -> dict: | |
| out_rows = [] | |
| for row in tqdm(test_rows, desc="generating"): | |
| out = generator(row["raw"]) | |
| out_rows.append({"raw": row["raw"], "clean": row["clean"], "out": out}) | |
| return aggregate(out_rows) | |
| def write_eval(report: dict, run_dir: Path) -> None: | |
| run_dir = Path(run_dir) | |
| (run_dir / "eval.json").write_text(json.dumps(report, indent=2, ensure_ascii=False)) | |