Spaces:
Running on Zero
Running on Zero
File size: 9,623 Bytes
5c49122 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | """Live inference using the supplied LLMON parser and attention extractor."""
import json
import logging
import os
import re
from pathlib import Path
from pydantic import BaseModel, ConfigDict, Field, model_validator
from vendor import span_parser
ROOT = Path(__file__).resolve().parent
DEFAULT_MODEL_REPO = "ibm-granite/granite-4.1-3b-llmon-preview"
LEGACY_MODEL_REPO = "ibm-research/granite-4.1-3b-llmon-preview"
MODEL_ID = os.getenv("LLMON_MODEL_ID", "g413b-lmon-exp-207-x")
MODEL_LABEL = os.getenv("LLMON_MODEL_LABEL", "Granite LLMON")
logger = logging.getLogger("uvicorn.error")
EXAMPLES = {
row["id"]: row
for line in (ROOT / "new_dlb_examples.jsonl").read_text().splitlines()
if line.strip() and (row := json.loads(line))
}
def configured_model_source() -> tuple[str, str]:
"""Select the owner's new repository, including Spaces with old variables."""
repo = os.getenv("LLMON_MODEL_REPO", "").strip() or DEFAULT_MODEL_REPO
revision = os.getenv("LLMON_MODEL_REVISION", "").strip() or "main"
if repo == LEGACY_MODEL_REPO:
# A commit in the old repository is not a verified commit in the new one.
logger.warning(
"Migrating LLMON_MODEL_REPO from %s to %s at main; the old revision is not reused. "
"Set the new repository and pin its own revision in Space settings for reproducibility.",
LEGACY_MODEL_REPO,
DEFAULT_MODEL_REPO,
)
return DEFAULT_MODEL_REPO, "main"
return repo, revision
class AttentionRequest(BaseModel):
"""Bound a live request to the configured model and supported prompt format."""
model_config = ConfigDict(extra="forbid")
model_id: str = MODEL_ID
example_id: int | None = Field(default=None, ge=0)
prompt_llmon: str | None = Field(default=None, min_length=1, max_length=16384)
dataset_tag: str | None = None
max_tokens: int = Field(default=64, ge=1, le=64)
layer: int | None = Field(default=None, ge=0, le=255)
@model_validator(mode="after")
def validate_selection(self) -> "AttentionRequest":
"""Reject ambiguous requests and unknown model/dataset identifiers."""
if self.model_id != MODEL_ID:
raise ValueError("Select the configured LLMON model.")
if self.dataset_tag not in (None, "default"):
raise ValueError("Uploaded datasets are sent as individual prompts.")
if (self.example_id is None) == (self.prompt_llmon is None):
raise ValueError("Provide either an example ID or one LLMON prompt.")
if self.example_id is not None and self.example_id not in EXAMPLES:
raise ValueError("Unknown example ID.")
return self
def prompt(self) -> str:
"""Resolve a prompt without reading any user-selected filesystem path."""
return self.prompt_llmon if self.prompt_llmon is not None else EXAMPLES[self.example_id]["llmon_prompt_dirty"]
class InferenceEngine:
"""Keep one configured checkpoint in memory, with no fallback recordings."""
def __init__(self) -> None:
"""Initialize without downloading models or requiring CUDA."""
self.model = None
self.tokenizer = None
self.model_info = {}
self.status = "not_configured"
self.message = "The live model is not configured yet."
self.max_input_tokens = int(os.getenv("LLMON_MAX_INPUT_TOKENS", "1024"))
def prepare(self) -> None:
"""Load weights at startup, after spaces has enabled CUDA emulation."""
model_path = os.getenv("LLMON_MODEL_PATH")
import torch
from huggingface_hub import snapshot_download
from vendor.attention_extractor import load_model
device = os.getenv("LLMON_DEVICE", "cuda")
if device not in {"cuda", "cpu", "mps"}:
raise ValueError("LLMON_DEVICE must be cuda, cpu, or mps.")
if os.getenv("SPACE_ID") and device != "cuda":
raise ValueError("The live Space requires CUDA; CPU fallback is disabled.")
if device == "cuda" and not torch.cuda.is_available():
self.status = "gpu_unavailable"
self.message = "A GPU has not been configured for this live demo."
return
self.status = "loading"
repo = None
requested_revision = None
resolved_revision = None
if not model_path:
repo, requested_revision = configured_model_source()
model_path = snapshot_download(
repo_id=repo,
revision=requested_revision,
allow_patterns=["*.json", "*.safetensors", "*.model", "*.txt", "*.tiktoken"],
)
# snapshot_download returns .../snapshots/<commit>; expose that SHA,
# not the possibly moving branch name, and never a local model path.
snapshot_name = Path(model_path).name
if re.fullmatch(r"[0-9a-f]{40}", snapshot_name):
resolved_revision = snapshot_name
self.model, self.tokenizer, self.model_info = load_model(model_path)
self.model.to(device)
self.model_info.update(
model_id=MODEL_ID,
device=device,
model_repo=repo,
model_revision=resolved_revision,
model_requested_revision=requested_revision,
)
self.status = "ready"
self.message = "Live model ready."
if repo:
logger.info("Loaded LLMON repository=%s revision=%s", repo, resolved_revision or requested_revision)
def ensure_ready(self) -> None:
"""Stop requests when the real checkpoint is unavailable."""
if self.status != "ready":
raise RuntimeError(self.message)
def parse(self, request: AttentionRequest) -> span_parser.ParseResult:
"""Check the token budget and the global-close grammar before inference."""
self.ensure_ready()
prompt = request.prompt()
encoded = self.tokenizer(prompt, add_special_tokens=False)
if len(encoded["input_ids"]) > self.max_input_tokens:
raise ValueError(f"Prompt exceeds the {self.max_input_tokens}-token input limit.")
parsed = span_parser.parse_llmon_prompt(prompt, self.tokenizer)
target_instructions = [s for s in parsed.spans if s.type == "instr" and s.is_target]
if len(target_instructions) != 1:
raise ValueError("Use global-close LLMON syntax with one selected instruction.")
return parsed
def infer(self, operation: str, payload: dict) -> dict:
"""Compute actual attention with the original float32 eager implementation."""
from vendor.attention_extractor import (
extract_generation_attention,
extract_prefill_attention,
)
request = AttentionRequest.model_validate(payload)
parsed = self.parse(request)
spans = [s.to_dict() for s in parsed.spans]
base = {
"model": self.model_info,
"example_id": request.example_id if request.example_id is not None else -1,
"spans": spans,
}
args = (self.model, self.tokenizer, request.prompt(), spans, parsed.span_labels, self.model_info)
if operation == "prefill":
return {
**base,
"tokens": parsed.tokens,
"span_labels": parsed.span_labels,
"attention_by_layer": extract_prefill_attention(*args),
}
attention_layers = [i for i, kind in enumerate(self.model_info["layer_types"]) if kind == "attention"]
if request.layer is not None and request.layer not in attention_layers:
raise ValueError("Select a layer with available attention data.")
return {**base, **extract_generation_attention(*args, max_tokens=request.max_tokens, layer=request.layer)}
def benchmark(self) -> dict:
"""Compute the three bundled examples using the upstream focus thresholds."""
rows = []
for example_id in EXAMPLES:
prefill = self.infer("prefill", {"model_id": MODEL_ID, "example_id": example_id})
layers = []
by_layer = prefill["attention_by_layer"]
for key in sorted(by_layer, key=int):
layer = by_layer[key]
layers.append(
{
"layer_index": layer["layer_index"],
"layer_type": layer["layer_type"],
**(layer.get("metrics") or {"ifr": None, "das": None, "leak": None}),
}
)
final = [layer for layer in layers if layer["layer_type"] == "attention"][-1]
ifr, das = final["ifr"], final["das"]
result = "success" if ifr > 0.6 and das > 0.5 else "failure" if ifr <= 0.6 and das <= 0.5 else "partial"
diag = "correct_attn" if result == "success" else "wrong_instr" if ifr <= 0.6 else "data_leakage"
rows.append(
{
"model_id": MODEL_ID,
"example_id": example_id,
"result": result,
"diag_type": diag,
"diag_description": "Attention focus heuristic; not answer correctness.",
"layers": layers,
"final_metrics": {k: final[k] for k in ["ifr", "das", "leak"]},
"kv_group_specialization": by_layer[str(final["layer_index"])].get("kv_groups", []),
}
)
return {"results": rows, "progress": None, "dataset_tag": "default"}
engine = InferenceEngine()
|