File size: 6,714 Bytes
eda4739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
213
214
215
216
217
218
"""Remote Tinker inference helpers for the Cree1865 Hugging Face Space."""

from __future__ import annotations

from dataclasses import asdict, dataclass
from functools import lru_cache
import time
from typing import Any, Callable

DEFAULT_MODEL_PATH = (
    "tinker://c71aadd1-8e48-51b0-b890-149a2889b4fa:train:0/"
    "sampler_weights/final"
)

DEFAULT_SYSTEM_PROMPT = (
    "Answer Cree dictionary lookup and translation prompts concisely. "
    "Use Cree forms only when you are confident, preserve orthography exactly, "
    "and return only the requested answer."
)

EXAMPLE_PROMPTS = [
    "Translate the Cree word maskihkiy into English.",
    "Give the Cree dictionary headword for 'medicine'. Return only the Cree form.",
    "Translate 'I speak Cree' into Cree. Return only the answer.",
    "What does the Cree suffix -win usually mark in dictionary entries?",
]


@dataclass(frozen=True)
class TinkerGeneration:
    """Structured result from one Tinker sampler request."""

    responses: list[str]
    stop_reasons: list[str]
    prompt_tokens: int
    model_path: str
    elapsed_seconds: float

    def to_metadata(self) -> dict[str, Any]:
        return asdict(self)


def _import_tinker() -> Any:
    import tinker

    return tinker


@lru_cache(maxsize=4)
def get_cached_sampling_client(model_path: str) -> Any:
    """Create and cache one remote sampler client per Tinker model path."""

    tinker = _import_tinker()
    service_client = tinker.ServiceClient()
    return service_client.create_sampling_client(model_path=model_path)


def build_chat_prompt(
    tokenizer: Any,
    system_prompt: str,
    user_prompt: str,
    enable_thinking: bool,
) -> str:
    """Format a chat prompt with the sampler tokenizer template."""

    messages = [
        {"role": "system", "content": system_prompt.strip()},
        {"role": "user", "content": user_prompt.strip()},
    ]
    if hasattr(tokenizer, "apply_chat_template"):
        try:
            return tokenizer.apply_chat_template(
                messages,
                tokenize=False,
                add_generation_prompt=True,
                enable_thinking=enable_thinking,
            )
        except TypeError:
            return tokenizer.apply_chat_template(
                messages,
                tokenize=False,
                add_generation_prompt=True,
            )
    return f"{messages[0]['content']}\n\nUser: {messages[1]['content']}\nAssistant:"


def sample_tinker_response(
    *,
    prompt: str,
    system_prompt: str,
    model_path: str = DEFAULT_MODEL_PATH,
    max_tokens: int = 96,
    temperature: float = 0.3,
    top_p: float = 0.9,
    top_k: int = -1,
    seed: int = 42,
    num_samples: int = 1,
    enable_thinking: bool = False,
    sampling_client: Any | None = None,
    tinker_module: Any | None = None,
) -> TinkerGeneration:
    """Run one remote generation request against a Tinker sampler checkpoint."""

    if tinker_module is None:
        tinker_module = _import_tinker()
    if sampling_client is None:
        sampling_client = get_cached_sampling_client(model_path)

    start = time.perf_counter()
    tokenizer = sampling_client.get_tokenizer()
    formatted_prompt = build_chat_prompt(
        tokenizer,
        system_prompt=system_prompt,
        user_prompt=prompt,
        enable_thinking=enable_thinking,
    )
    prompt_tokens = tokenizer.encode(formatted_prompt)
    model_input = tinker_module.ModelInput.from_ints(prompt_tokens)
    sampling_params = tinker_module.SamplingParams(
        max_tokens=int(max_tokens),
        temperature=float(temperature),
        top_p=float(top_p),
        top_k=int(top_k),
        seed=int(seed),
    )
    response = sampling_client.sample(
        prompt=model_input,
        num_samples=int(num_samples),
        sampling_params=sampling_params,
    ).result()
    decoded = [
        tokenizer.decode(sequence.tokens, skip_special_tokens=True).strip()
        for sequence in response.sequences
    ]
    stop_reasons = [str(sequence.stop_reason) for sequence in response.sequences]
    return TinkerGeneration(
        responses=decoded,
        stop_reasons=stop_reasons,
        prompt_tokens=len(prompt_tokens),
        model_path=model_path,
        elapsed_seconds=round(time.perf_counter() - start, 3),
    )


def format_output(responses: list[str]) -> str:
    """Render one or more model samples for the Gradio textbox."""

    cleaned = [response.strip() or "[empty response]" for response in responses]
    if len(cleaned) == 1:
        return cleaned[0]
    return "\n\n---\n\n".join(
        f"Sample {index}\n{response}" for index, response in enumerate(cleaned, start=1)
    )


def generate_for_ui(
    *,
    prompt: str,
    system_prompt: str,
    max_tokens: int,
    temperature: float,
    top_p: float,
    seed: int,
    num_samples: int,
    enable_thinking: bool,
    model_path: str = DEFAULT_MODEL_PATH,
    sampling_client_factory: Callable[[str], Any] | None = None,
) -> tuple[str, dict[str, Any]]:
    """Gradio callback wrapper with validation and readable error reporting."""

    if not prompt or not prompt.strip():
        return (
            "Enter a prompt before running inference.",
            {"ok": False, "error_type": "validation"},
        )

    try:
        sampling_client = (
            sampling_client_factory(model_path)
            if sampling_client_factory is not None
            else get_cached_sampling_client(model_path)
        )
        result = sample_tinker_response(
            prompt=prompt,
            system_prompt=system_prompt or DEFAULT_SYSTEM_PROMPT,
            model_path=model_path,
            max_tokens=max_tokens,
            temperature=temperature,
            top_p=top_p,
            top_k=-1,
            seed=seed,
            num_samples=num_samples,
            enable_thinking=enable_thinking,
            sampling_client=sampling_client,
        )
        metadata = result.to_metadata()
        metadata.update(
            {
                "ok": True,
                "num_samples": int(num_samples),
                "temperature": float(temperature),
                "top_p": float(top_p),
                "seed": int(seed),
                "enable_thinking": bool(enable_thinking),
            }
        )
        return format_output(result.responses), metadata
    except Exception as exc:  # noqa: BLE001 - UI should surface backend failures.
        return (
            f"Inference error ({type(exc).__name__}): {exc}",
            {
                "ok": False,
                "error_type": type(exc).__name__,
                "message": str(exc),
                "model_path": model_path,
            },
        )