ssh2025's picture
Add files using upload-large-folder tool
a804a2f verified
Raw History Blame Contribute Delete
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},
}