Token Classification
Transformers
Safetensors
Malay
Brunei
xlm-roberta
brunei-malay
standard-malay
text-normalization
constrained-editing
Instructions to use ssh2025/brunei-malay-normalizer-v3 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ssh2025/brunei-malay-normalizer-v3 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="ssh2025/brunei-malay-normalizer-v3")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("ssh2025/brunei-malay-normalizer-v3") model = AutoModelForTokenClassification.from_pretrained("ssh2025/brunei-malay-normalizer-v3", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download bmb_normalizer/runtime.py from ssh2025/brunei-malay-normalizer-v3: direct link, hf CLI and curl.
- Browser
- Download file 4.9 kB
-
https://huggingface.co/ssh2025/brunei-malay-normalizer-v3/resolve/main/bmb_normalizer/runtime.py
- Command line
-
hf download hf://ssh2025/brunei-malay-normalizer-v3/bmb_normalizer/runtime.py
-
curl -L -o runtime.py https://huggingface.co/ssh2025/brunei-malay-normalizer-v3/resolve/main/bmb_normalizer/runtime.py
4.9 kB
| """Hash-verified inference runtime for the public V3 artifact.""" | |
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| import threading | |
| from .edit_policy_v3 import inference_policy | |
| from .edit_tags_v3 import render_tag_predictions, tokens | |
| class InputError(ValueError): | |
| """A safe, user-correctable inference request error.""" | |
| def _digest(path: Path) -> str: | |
| value = hashlib.sha256() | |
| with path.open("rb") as stream: | |
| for block in iter(lambda: stream.read(8 * 1024 * 1024), b""): | |
| value.update(block) | |
| return value.hexdigest() | |
| class V3Runtime: | |
| def __init__(self, root: Path, device: str = "cuda:0"): | |
| import torch | |
| from transformers import AutoModelForTokenClassification, AutoTokenizer | |
| root = root.resolve() | |
| manifest = json.loads((root / "release-manifest.json").read_text()) | |
| if manifest.get("product_version") != 3 or manifest.get("system_prompt") is not None: | |
| raise RuntimeError("V3 release identity mismatch") | |
| for name, expected in manifest["model_files"].items(): | |
| path = root / name | |
| if path.stat().st_size != expected["bytes"] or _digest(path) != expected["sha256"]: | |
| raise RuntimeError(f"V3 model artifact mismatch: {name}") | |
| for name in ("labels.json", "candidate-index.json"): | |
| expected = manifest["runtime_files"][name] | |
| path = root / name | |
| if path.stat().st_size != expected["bytes"] or _digest(path) != expected["sha256"]: | |
| raise RuntimeError(f"V3 runtime artifact mismatch: {name}") | |
| policy_expected = manifest["runtime_files"]["edit_policy_v3.py"]["sha256"] | |
| if _digest(Path(__file__).with_name("edit_policy_v3.py")) != policy_expected: | |
| raise RuntimeError("V3 deployed policy differs from release") | |
| if not torch.cuda.is_available() or not device.startswith("cuda:"): | |
| raise RuntimeError("A CUDA GPU is required for this release") | |
| self.labels = json.loads((root / "labels.json").read_text()) | |
| raw_index = json.loads((root / "candidate-index.json").read_text()) | |
| self.index = { | |
| first: [(tuple(key), int(label_id)) for key, label_id in values] | |
| for first, values in raw_index.items() | |
| } | |
| self.threshold = float(manifest["selected_threshold"]) | |
| self.torch = torch | |
| self.device = device | |
| self.tokenizer = AutoTokenizer.from_pretrained(root, local_files_only=True) | |
| self.model = AutoModelForTokenClassification.from_pretrained( | |
| root, local_files_only=True, torch_dtype=torch.float16, | |
| ).to(device).eval() | |
| self.lock = threading.Lock() | |
| self.gpu = f"{device} {torch.cuda.get_device_name(int(device.rsplit(':', 1)[1]))}" | |
| def run(self, text: str) -> dict: | |
| source_tokens = tokens(text) | |
| if not source_tokens: | |
| return {"output": text, "route": "exact_copy", "status": "identity", | |
| "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}} | |
| words = [str(token["text"]) for token in source_tokens] | |
| encoded = self.tokenizer( | |
| words, is_split_into_words=True, return_tensors="pt", truncation=False, | |
| ) | |
| if encoded.input_ids.shape[1] > 256: | |
| raise InputError("Input exceeds the verified 256-token budget; nothing was truncated.") | |
| encoded = encoded.to(self.device) | |
| allowed, forced = inference_policy(text, self.index, self.labels) | |
| selected = [self.labels[0]] * len(words) | |
| with self.lock, self.torch.inference_mode(): | |
| logits = self.model(**encoded).logits[0] | |
| seen = set() | |
| for token_index, word_id in enumerate(encoded.word_ids()): | |
| if word_id is None or word_id in seen: | |
| continue | |
| seen.add(word_id) | |
| ids = sorted(allowed[word_id]) | |
| probabilities = logits[token_index, ids].softmax(-1) | |
| confidence, local_index = probabilities.max(-1) | |
| chosen = forced[word_id] if forced[word_id] is not None else ids[int(local_index)] | |
| if forced[word_id] is None and chosen != 0 and float(confidence) < self.threshold: | |
| chosen = 0 | |
| selected[word_id] = chosen if isinstance(chosen, dict) else self.labels[chosen] | |
| output = render_tag_predictions(text, selected) | |
| count = int(encoded.input_ids.shape[1]) | |
| return { | |
| "output": output, | |
| "route": "source_conditioned_edit_tagger", | |
| "status": "normalized" if output != text else "identity", | |
| "product_version": 3, | |
| "guard_scope": "licensed source edits plus deterministic semantic and immutable-span policy", | |
| "usage": {"prompt_tokens": count, "completion_tokens": 0, "total_tokens": count}, | |
| } | |