Download inference.py from nace-ai/drex-v2.0: direct link, hf CLI and curl.
- Browser
- Download file 5.96 kB
-
https://huggingface.co/nace-ai/drex-v2.0/resolve/main/inference.py
- Command line
-
hf download hf://nace-ai/drex-v2.0/inference.py
-
curl -L -o inference.py https://huggingface.co/nace-ai/drex-v2.0/resolve/main/inference.py
5.96 kB
| """Score a System One request with Drex v2.0. | |
| pip install -r requirements.txt | |
| python inference.py | |
| python inference.py --request examples/request.json | |
| The model returns a probability for every option in one forward pass. | |
| A state or question past the context limit returns an error, so the score covers the whole document. | |
| """ | |
| import argparse | |
| import json | |
| import math | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import torch | |
| sys.path.insert(0, str(Path(__file__).resolve().parent / "code")) | |
| from kev.api import SystemOneRequest, output_tokens, to_answers, to_record # noqa: E402 | |
| from kev.checkpoint import load as _load # noqa: E402 | |
| from kev.model import NON_HYBRID_CONTEXT_LENGTH, SERVE_MAX_BRANCH, SERVE_MAX_PACKED, SERVE_MAX_STATE # noqa: E402 | |
| EXAMPLE = { | |
| "state": { | |
| "ticket": "I was charged twice for the same order. Please refund the extra payment.", | |
| }, | |
| "questions": { | |
| "team": { | |
| "type": "choice", | |
| "instructions": "Which team should handle this ticket?", | |
| "criteria": { | |
| "billing": "Payments, charges, and refunds", | |
| "technical": "Bugs and outages", | |
| "other": "Anything else", | |
| }, | |
| }, | |
| "refund": { | |
| "type": "noul", | |
| "instructions": "Does the customer explicitly ask for a refund?", | |
| }, | |
| "urgency": { | |
| "type": "score", | |
| "instructions": "How urgent is this ticket?", | |
| "criteria": ["Routine", "Soon", "Urgent"], | |
| }, | |
| }, | |
| } | |
| def load(path): | |
| tok, model = _load(path) | |
| if not model.hybrid and max(SERVE_MAX_STATE, SERVE_MAX_BRANCH, SERVE_MAX_PACKED) > NON_HYBRID_CONTEXT_LENGTH: | |
| raise ValueError(f"this model supports at most {NON_HYBRID_CONTEXT_LENGTH} tokens; lower KEV_CONTEXT") | |
| return tok, model | |
| def _encode(tok, model, request): | |
| if not isinstance(request, dict) or "requests" in request: | |
| raise ValueError("each decision must be an object with state and questions, without a requests wrapper") | |
| rec, meta = to_record(SystemOneRequest.model_validate(request)) | |
| enc = model.encode(tok, rec, max_state=SERVE_MAX_STATE, max_branch=SERVE_MAX_BRANCH, strict=True) | |
| return enc, meta | |
| def _result(tok, enc, meta, probs, latency_ms): | |
| if len(probs) != len(meta) or any(len(row) != len(question["keys"]) or | |
| any(not math.isfinite(value) for value in row) | |
| for row, question in zip(probs, meta)): | |
| raise RuntimeError("model readout is incomplete or non-finite") | |
| answers = to_answers(probs, meta) | |
| return { | |
| "answers": answers, | |
| "usage": {"input_tokens": len(enc["ids"]), "output_tokens": _run_scorer(output_tokens, tok, answers)}, | |
| "latency_ms": latency_ms, | |
| } | |
| def _run_scorer(fn, *args): | |
| try: | |
| return fn(*args) | |
| except ValueError as exc: | |
| raise RuntimeError("model scoring failed") from exc | |
| def decide(tok, model, request): | |
| enc, meta = _encode(tok, model, request) | |
| started = time.perf_counter() | |
| with torch.no_grad(): | |
| probs = [p.float().tolist() for p in _run_scorer(model.probs, enc)] | |
| return _result(tok, enc, meta, probs, round((time.perf_counter() - started) * 1000, 1)) | |
| def decide_batch(tok, model, requests): | |
| if not isinstance(requests, list) or not requests: | |
| raise ValueError("requests must be a non-empty list") | |
| if len(requests) > 1024: | |
| raise ValueError("requests exceeds 1024 items") | |
| prepared = [_encode(tok, model, request) for request in requests] | |
| results = [None] * len(prepared) | |
| index = 0 | |
| while index < len(prepared): | |
| enc, _meta = prepared[index] | |
| if len(enc["ids"]) > SERVE_MAX_PACKED: | |
| started = time.perf_counter() | |
| with torch.no_grad(): | |
| probs = [p.float().tolist() for p in _run_scorer(model.probs, enc)] | |
| results[index] = _result(tok, enc, _meta, probs, round((time.perf_counter() - started) * 1000, 1)) | |
| index += 1 | |
| continue | |
| end = index | |
| longest = 0 | |
| # Bound padded tokens and the quadratic attention mask before batching. | |
| while end < len(prepared) and end - index < 8: | |
| size = len(prepared[end][0]["ids"]) | |
| if size > SERVE_MAX_PACKED: | |
| break | |
| padded = (max(longest, size) + 127) // 128 * 128 | |
| count = end - index + 1 | |
| if count * padded > SERVE_MAX_PACKED or count * padded * padded > SERVE_MAX_PACKED ** 2: | |
| break | |
| longest = max(longest, size) | |
| end += 1 | |
| if end == index: | |
| end = index + 1 | |
| group = prepared[index:end] | |
| started = time.perf_counter() | |
| with torch.no_grad(): | |
| logits = _run_scorer(model.forward_batch, [item[0] for item in group]) | |
| latency_ms = round((time.perf_counter() - started) * 1000, 1) | |
| if len(logits) != len(group): | |
| raise RuntimeError("batch readout did not return one result per request") | |
| for offset, ((enc, meta), row) in enumerate(zip(group, logits)): | |
| probs = [torch.softmax(item.float(), dim=-1).tolist() for item in row] | |
| results[index + offset] = _result(tok, enc, meta, probs, latency_ms) | |
| index = end | |
| return results | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Score one System One request with a Drex model.") | |
| parser.add_argument("--model", default=str(Path(__file__).resolve().parent), help="Checkpoint directory (default: this directory)") | |
| parser.add_argument("--request", help="JSON file with one System One request") | |
| args = parser.parse_args() | |
| tok, model = load(args.model) | |
| request = json.loads(Path(args.request).read_text()) if args.request else EXAMPLE | |
| print(json.dumps(decide(tok, model, request), indent=2)) | |
| if __name__ == "__main__": | |
| main() | |