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()