File size: 23,665 Bytes
357e825 21edc83 b6ded85 b90431b f2b4b4e b6ded85 f2b4b4e 8f73bd7 357e825 d9eb5a7 357e825 c20e312 357e825 c20e312 357e825 b6ded85 357e825 c20e312 357e825 b90431b c20e312 d3e7351 c48f7c9 357e825 b90431b c20e312 f2b4b4e 8f73bd7 5f6a2e3 f2b4b4e 8f73bd7 f2b4b4e c20e312 d3e7351 c48f7c9 357e825 b6ded85 357e825 b6ded85 357e825 21edc83 357e825 21edc83 357e825 b90431b 3076240 b90431b 3076240 b90431b edd5989 b90431b c20e312 b90431b c20e312 3076240 8f73bd7 3076240 f2b4b4e 8f73bd7 f2b4b4e 3076240 d3e7351 c48f7c9 357e825 c20e312 357e825 b6ded85 c20e312 edd5989 b6ded85 edd5989 c20e312 357e825 edd5989 357e825 b90431b 357e825 b6ded85 357e825 b6ded85 357e825 b6ded85 357e825 b6ded85 357e825 c20e312 357e825 c20e312 2e955a1 c20e312 2e955a1 c20e312 2e955a1 c20e312 357e825 | 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 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 | from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Any, Protocol
try: # ZeroGPU: present on the Space, absent in local/CPU dev
import spaces
except ImportError: # pragma: no cover - local fallback
spaces = None
from .json_utils import parse_json_object
@dataclass(frozen=True)
class RuntimeStatus:
label: str
used_fallback: bool = False
def _same_look(a: dict, b: dict) -> bool:
"""True when two genome dicts render identically: the visible creature is
driven only by archetype + palette + parts, so a tweak that leaves all three
untouched produces no on-screen change."""
keys = ("archetype", "palette", "parts")
return all(a.get(k) == b.get(k) for k in keys)
# The app runs a single model: Gemma 4 12B (the only family that draws hard voxel
# forms in the model-selection spike β see wiki/model-selection-spike.md). Every
# AI moment β freeform generation and hatch fusion β goes through it. Gemma 4
# needs llama-cpp-python > 0.3.19 (the Space pin); the local sibling venv has
# 0.3.28. Override via env for a local gguf. See
# docs/superpowers/specs/2026-06-15-freeform-voxel-generation-design.md.
FREEFORM_MODEL_REPO = os.getenv("BUDDY_FREEFORM_MODEL_REPO", "unsloth/gemma-4-12b-it-GGUF")
FREEFORM_MODEL_FILE = os.getenv("BUDDY_FREEFORM_MODEL_FILE", "gemma-4-12b-it-Q4_K_M.gguf")
# The edit path no longer uses a model: an English keyword parser
# (edit_parser.parse_edit) maps the instruction to a catalog part + parameters
# and the assembler snaps it on deterministically β instant, no GPU. The
# generative model above is reserved for full generation + hatch fusion, and for
# the freeform-edit safety valve when no catalog part matches.
def _blocking_completion(
*,
model_path: str,
messages: list[dict[str, str]],
schema: dict[str, Any] | None,
max_tokens: int,
temperature: float,
top_p: float,
n_ctx: int,
n_threads: int | None,
n_gpu_layers: int,
flash_attn: bool,
label: str = "zerogpu-bench",
) -> str:
"""Build a Llama and run one chat completion.
On ZeroGPU the GPU is attached only inside the @spaces.GPU wrappers below
and detaches when they return, so the model must be (re)constructed here
every call with ``n_gpu_layers=-1`` β there is no persisting it across
calls (a llama.cpp object built in global scope would stay on CPU, since
``spaces`` only hooks PyTorch's ``.to("cuda")``).
"""
import time
from llama_cpp import Llama
t0 = time.perf_counter()
llm = Llama(
model_path=model_path,
n_ctx=n_ctx,
n_threads=n_threads,
n_gpu_layers=n_gpu_layers,
flash_attn=flash_attn,
verbose=False,
)
t1 = time.perf_counter()
kwargs: dict[str, Any] = dict(
messages=messages, max_tokens=max_tokens, temperature=temperature, top_p=top_p
)
if schema is not None:
# llama-cpp-python only honors {"type": "json_object", "schema": ...}; the
# OpenAI-style {"type": "json_schema"} is silently ignored, which left the
# model unconstrained (it returned `type` instead of `kind`, bare strings,
# or a {"genome": {...}} wrapper) and forced every edit onto the keyword
# fallback. json_object + schema turns the schema into an enforced GBNF
# grammar so fuse/tweak output actually matches GENOME_SCHEMA.
kwargs["response_format"] = {"type": "json_object", "schema": schema}
response = llm.create_chat_completion(**kwargs)
t2 = time.perf_counter()
usage = response.get("usage") or {}
tokens = int(usage.get("completion_tokens") or 0)
gen_s = t2 - t1
tok_s = tokens / gen_s if gen_s > 0 else 0.0
# Printed to the Space logs so each call's cost is visible (reload + gen).
print(
f"[{label}] reload={t1 - t0:.2f}s gen={gen_s:.2f}s "
f"total={t2 - t0:.2f}s tokens={tokens} ({tok_s:.1f} tok/s) "
f"n_gpu_layers={n_gpu_layers}",
flush=True,
)
return response["choices"][0]["message"]["content"]
def _run_completion(**kwargs: Any) -> str:
return _blocking_completion(**kwargs)
def _run_freeform_completion(**kwargs: Any) -> str:
return _blocking_completion(**kwargs)
def _warmup_completion(**kwargs: Any) -> str:
return _blocking_completion(label="zerogpu-warmup", **kwargs)
# Freeform Gemma-4-12B box-geometry calls generate 300-600+ tokens at ~20 tok/s
# and routinely hit 25-31s total. On the old shared duration=30 they raced the
# ZeroGPU detach and crashed mid-decode (ggml_cuda_mul_mat_q CUDA abort -> the
# call dies and the user gets the fallback after a full wait). Give them their
# own larger budget. Override with BUDDY_FREEFORM_GPU_DURATION if 12B gen grows.
_FREEFORM_GPU_DURATION = int(os.getenv("BUDDY_FREEFORM_GPU_DURATION", "60"))
if spaces is not None: # pragma: no cover - only active on the ZeroGPU Space
# Short calls (e.g. tweak suggestions) are quick once warm, so keep their GPU
# budget small for better queue behavior. The warmup eats the one-time ~75s
# CUDA kernel JIT compile, so it needs a larger budget.
_run_completion = spaces.GPU(duration=30)(_run_completion)
_run_freeform_completion = spaces.GPU(duration=_FREEFORM_GPU_DURATION)(_run_freeform_completion)
_warmup_completion = spaces.GPU(duration=90)(_warmup_completion)
class BuddyRuntime(Protocol):
def fuse_genomes(self, parent_a: dict, parent_b: dict) -> tuple[dict, RuntimeStatus]:
raise NotImplementedError
def interpret_genome_command(self, genome: dict, transcript: str) -> tuple[dict, RuntimeStatus]:
raise NotImplementedError
def generate_creature(self, prompt: str) -> tuple[dict, list[dict], RuntimeStatus]:
raise NotImplementedError
def fuse_creatures(self, parent_a: dict, parent_b: dict) -> tuple[dict, list[dict], RuntimeStatus]:
raise NotImplementedError
def edit_creature(self, genome: dict, boxes: list[dict], instruction: str) -> tuple[dict, list[dict], list[str], RuntimeStatus]:
raise NotImplementedError
def suggest_tweaks(self, boxes: list[dict]) -> list[str]:
raise NotImplementedError
class FakeBuddyRuntime:
_STATUS = RuntimeStatus("Generated by the local fallback", used_fallback=True)
def fuse_genomes(self, parent_a: dict, parent_b: dict) -> tuple[dict, RuntimeStatus]:
from .genome import genome_from_dict, genome_to_dict
from .fallback import fuse_fallback
child = fuse_fallback(genome_from_dict(parent_a), genome_from_dict(parent_b), seed=1)
return genome_to_dict(child), self._STATUS
def interpret_genome_command(self, genome: dict, transcript: str) -> tuple[dict, RuntimeStatus]:
from .genome import genome_from_dict, genome_to_dict
from .fallback import voice_edit_genome
edited = voice_edit_genome(genome_from_dict(genome), transcript)
return genome_to_dict(edited), self._STATUS
def generate_creature(self, prompt: str) -> tuple[dict, list[dict], RuntimeStatus]:
from .exemplars import pick_exemplar
ex = pick_exemplar(prompt)
genome = {"name": prompt.strip()[:40] or "Freeform Buddy", "archetype": "chick", "parts": []}
return genome, list(ex["boxes"]), self._STATUS
def fuse_creatures(self, parent_a: dict, parent_b: dict) -> tuple[dict, list[dict], RuntimeStatus]:
from .fallback import fuse_boxes_fallback
boxes = fuse_boxes_fallback(parent_a.get("boxes"), parent_b.get("boxes"), seed=1)
name = f"{(parent_a.get('name') or 'A')[:8]}Γ{(parent_b.get('name') or 'B')[:8]}"
return {"name": name, "archetype": "chick", "parts": []}, boxes, self._STATUS
def edit_creature(self, genome: dict, boxes: list[dict], instruction: str) -> tuple[dict, list[dict], list[str], RuntimeStatus]:
# Same model-free edit path as the real runtime: the keyword parser maps
# the instruction to a catalog part + parameters, the assembler snaps it
# on. An unmatched instruction defaults to horns so local UI testing
# always produces a visible change.
from .assembler import assemble_part
from .parts import PARTS
from .edit_parser import parse_edit
from .fallback import suggest_tweaks_fallback
spec = parse_edit(instruction)
if spec is not None:
nb = assemble_part(boxes, spec.part, spec.anchor, spec.scale,
spec.color, spec.count, spec.rotation)
else:
nb = assemble_part(boxes, "horns", PARTS["horns"]["default_anchor"])
return genome, nb, suggest_tweaks_fallback(nb), self._STATUS
def suggest_tweaks(self, boxes: list[dict]) -> list[str]:
from .fallback import suggest_tweaks_fallback
return suggest_tweaks_fallback(boxes)
@dataclass(frozen=True)
class LlamaRuntimeConfig:
model_path: str | None = None
model_repo: str = FREEFORM_MODEL_REPO
model_file: str = FREEFORM_MODEL_FILE
n_ctx: int = 2048
n_threads: int | None = None
n_gpu_layers: int = -1
flash_attn: bool = True
timeout_seconds: float = 35.0
max_tokens: int = 420
@classmethod
def from_env(cls) -> "LlamaRuntimeConfig":
return cls(
model_path=os.getenv("BUDDY_MODEL_PATH"),
model_repo=os.getenv("BUDDY_MODEL_REPO", FREEFORM_MODEL_REPO),
model_file=os.getenv("BUDDY_MODEL_FILE", FREEFORM_MODEL_FILE),
n_ctx=int(os.getenv("BUDDY_N_CTX", "2048")),
n_threads=_optional_int(os.getenv("BUDDY_N_THREADS")),
n_gpu_layers=int(os.getenv("BUDDY_N_GPU_LAYERS", "-1")),
flash_attn=os.getenv("BUDDY_FLASH_ATTN", "1") != "0",
timeout_seconds=float(os.getenv("BUDDY_TIMEOUT_SECONDS", "35")),
max_tokens=int(os.getenv("BUDDY_MAX_TOKENS", "420")),
)
class LlamaCppBuddyRuntime:
def __init__(self, config: LlamaRuntimeConfig | None = None) -> None:
self.config = config or LlamaRuntimeConfig.from_env()
def fuse_genomes(self, parent_a: dict, parent_b: dict) -> tuple[dict, RuntimeStatus]:
from .genome import genome_from_dict, genome_to_dict, repair_genome
from .fallback import fuse_fallback
from .prompts import build_fuse_messages, GENOME_SCHEMA
fb = fuse_fallback(genome_from_dict(parent_a), genome_from_dict(parent_b), seed=1)
try:
raw = self._chat_json(
build_fuse_messages(parent_a, parent_b), GENOME_SCHEMA,
max_tokens=int(os.environ.get("BUDDY_FUSE_MAX_TOKENS", "400")), temperature=0.9,
)
child = repair_genome(raw, fb)
return genome_to_dict(child), RuntimeStatus("Generated by the local model")
except Exception: # noqa: BLE001 - runtime failures must not break the demo
return genome_to_dict(fb), RuntimeStatus("Used the fallback splice", used_fallback=True)
def interpret_genome_command(self, genome: dict, transcript: str) -> tuple[dict, RuntimeStatus]:
from .genome import genome_from_dict, genome_to_dict, repair_genome
from .fallback import voice_edit_genome
from .prompts import build_genome_command_messages, GENOME_EDIT_SCHEMA
base = genome_from_dict(genome)
base_out = genome_to_dict(base)
fb = genome_to_dict(voice_edit_genome(base, transcript))
try:
edit = self._chat_json(
build_genome_command_messages(genome, transcript), GENOME_EDIT_SCHEMA,
max_tokens=int(os.environ.get("BUDDY_EDIT_MAX_TOKENS", "200")), temperature=0.5,
) or {}
merged = {**genome,
"parts": (edit.get("parts") or genome.get("parts")),
"palette": (edit.get("palette") or genome.get("palette")),
"archetype": (edit.get("archetype") or genome.get("archetype"))}
model_out = genome_to_dict(repair_genome(merged, base))
# A small model often returns a valid-but-empty edit (no parts /
# palette / archetype), which silently leaves the creature untouched
# so the user sees no reaction. When the model changed nothing visible
# but the deterministic keyword editor did, surface that instead so a
# tweak always lands. (The success path used to keep the original and
# never reach the keyword fallback, which only fired on an exception.)
if _same_look(model_out, base_out) and not _same_look(fb, base_out):
return fb, RuntimeStatus("Used the keyword editor", used_fallback=True)
return model_out, RuntimeStatus("Generated by the local model")
except Exception: # noqa: BLE001 - runtime failures must not break the demo
return fb, RuntimeStatus("Used the keyword editor", used_fallback=True)
def generate_creature(self, prompt: str) -> tuple[dict, list[dict], RuntimeStatus]:
# Model-free by design: the bench's from-scratch box must never call the
# model β only Splice (fuse_creatures) does. Pick a reference body by
# keyword, the same deterministic path the fake runtime uses, so real and
# fake agree. (The model-backed draw used to live here; it was retired when
# the bench input was made fully model-free.)
from .exemplars import pick_exemplar
ex = pick_exemplar(prompt)
genome = {"name": prompt.strip()[:40] or "Freeform Buddy", "archetype": "chick", "parts": []}
return genome, list(ex["boxes"]), RuntimeStatus("Built from a reference template")
def fuse_creatures(self, parent_a: dict, parent_b: dict) -> tuple[dict, list[dict], RuntimeStatus]:
import json
from .exemplars import pick_exemplar
from .boxes import repair_boxes
from .fallback import fuse_boxes_fallback
from .prompts import build_freeform_fuse_messages, BOX_SCHEMA
ex = pick_exemplar((parent_a.get("name") or "") + " " + (parent_b.get("name") or ""))
name = f"{(parent_a.get('name') or 'A')[:8]}Γ{(parent_b.get('name') or 'B')[:8]}"
fb_boxes = fuse_boxes_fallback(parent_a.get("boxes"), parent_b.get("boxes"), seed=1)
try:
raw = self._chat_json(
build_freeform_fuse_messages(parent_a, parent_b, json.dumps(ex)), BOX_SCHEMA,
max_tokens=int(os.environ.get("BUDDY_FREEFORM_MAX_TOKENS", "1000")),
temperature=0.9,
heavy=True,
) or {}
boxes = repair_boxes(raw.get("boxes") if isinstance(raw, dict) else None)
child_name = (raw.get("name") if isinstance(raw, dict) else None) or name
if boxes:
return {"name": child_name, "archetype": "chick", "parts": []}, boxes, RuntimeStatus("Generated by the local model")
except Exception: # noqa: BLE001 - runtime failures must not break the demo
pass
return {"name": name, "archetype": "chick", "parts": []}, fb_boxes, RuntimeStatus("Used a reference template", used_fallback=True)
def edit_creature(self, genome: dict, boxes: list[dict], instruction: str) -> tuple[dict, list[dict], list[str], RuntimeStatus]:
# Model-free by design: the bench tweak box must never call the model β only
# Splice (fuse_creatures) does. An English keyword parser maps the instruction
# to a catalog part + parameters and the assembler snaps it on deterministically.
# An instruction that matches no catalog part defaults to horns, so a tweak
# always produces a visible change (mirrors the fake runtime). (The model-backed
# freeform edit used to be a fallback here; it was retired with the box.)
from .assembler import assemble_part
from .parts import PARTS
from .edit_parser import parse_edit
from .fallback import suggest_tweaks_fallback
spec = parse_edit(instruction)
if spec is not None:
print(f"[edit] part={spec.part} anchor={spec.anchor} count={spec.count} "
f"scale={spec.scale} rot={spec.rotation}", flush=True)
new_boxes = assemble_part(boxes, spec.part, spec.anchor, spec.scale,
spec.color, spec.count, spec.rotation)
else:
new_boxes = assemble_part(boxes, "horns", PARTS["horns"]["default_anchor"])
return genome, new_boxes, suggest_tweaks_fallback(new_boxes), RuntimeStatus("Built from the part catalog")
def suggest_tweaks(self, boxes: list[dict]) -> list[str]:
from .fallback import suggest_tweaks_fallback
from .prompts import build_freeform_suggest_messages, TWEAK_SCHEMA
try:
raw = self._chat_json(
build_freeform_suggest_messages(boxes), TWEAK_SCHEMA,
max_tokens=int(os.environ.get("BUDDY_SUGGEST_MAX_TOKENS", "200")),
temperature=0.9,
) or {}
tweaks = raw.get("tweaks") if isinstance(raw, dict) else None
clean = [str(t).strip() for t in tweaks if str(t).strip()] if isinstance(tweaks, list) else []
if clean:
# Pad/trim to exactly 4, topping up from the fallback if short.
fill = suggest_tweaks_fallback(boxes)
while len(clean) < 4:
clean.append(next((f for f in fill if f not in clean), fill[len(clean) % len(fill)]))
return clean[:4]
except Exception: # noqa: BLE001 - suggestions are advisory; never break the bench
pass
return suggest_tweaks_fallback(boxes)
def _chat_json(
self,
messages: list[dict[str, str]],
schema: dict[str, Any],
*,
max_tokens: int | None = None,
temperature: float = 0.85,
heavy: bool = False,
) -> dict[str, Any] | None:
# Resolve the model file outside the GPU call (hf_hub_download caches
# to disk), then run inference inside the @spaces.GPU-wrapped helper so
# ZeroGPU attaches a GPU for the duration of the completion. heavy=True
# routes the long-running 12B freeform calls to the larger-duration GPU
# wrapper so they do not race the ZeroGPU detach and crash mid-decode.
model_path = self._resolve_model_path()
run = _run_freeform_completion if heavy else _run_completion
# On the freeform box path, BUDDY_FREEFORM_NO_GRAMMAR=1 drops the GBNF
# grammar: it ~2x's 12B decode (the grammar is intrinsic per-token CPU
# overhead, not schema bloat) while parse_json_object + repair_boxes catch
# the now-unconstrained output. Short (non-heavy) calls always keep the
# grammar. See wiki/deployment-strategy.md.
effective_schema = schema
if heavy and os.getenv("BUDDY_FREEFORM_NO_GRAMMAR") == "1":
effective_schema = None
content = run(
model_path=model_path,
messages=messages,
schema=effective_schema,
max_tokens=max_tokens or self.config.max_tokens,
temperature=temperature,
top_p=0.9,
n_ctx=max(self.config.n_ctx, 4096),
n_threads=self.config.n_threads,
n_gpu_layers=self.config.n_gpu_layers,
flash_attn=self.config.flash_attn,
)
return parse_json_object(content)
def _resolve_model_path(self) -> str:
# An explicit local path wins (local dev / tests); otherwise download the
# configured GGUF (hf_hub_download caches to disk). On a download failure
# each runtime method already falls back to a hand-authored exemplar, so
# there is no second model to fall back to.
if self.config.model_path:
return self.config.model_path
from huggingface_hub import hf_hub_download
return hf_hub_download(repo_id=self.config.model_repo, filename=self.config.model_file)
def default_runtime() -> BuddyRuntime:
if os.getenv("BUDDY_FORCE_FAKE_RUNTIME") == "1":
return FakeBuddyRuntime()
return LlamaCppBuddyRuntime()
_warmed = False
def warmup() -> None:
"""Trigger the one-time CUDA kernel JIT compile ahead of real traffic.
Wired to Gradio's ``demo.load`` so it fires when the page opens: the ~48s
first-call compile happens while the player reads the intro, so their first
real action is already warm. Runs once per container; safe to call
repeatedly and on CPU / when ``spaces`` is absent. No-ops in the fake runtime.
"""
global _warmed
if _warmed or os.getenv("BUDDY_FORCE_FAKE_RUNTIME") == "1":
return
_warmed = True # claim first so concurrent page loads do not double-fire
try:
cfg = LlamaRuntimeConfig.from_env()
# Warm the model every real action β draw / tweak / splice β runs.
model_path = LlamaCppBuddyRuntime(cfg)._resolve_model_path()
_warmup_completion(
model_path=model_path,
messages=[{"role": "user", "content": "warmup"}],
schema=None,
max_tokens=8,
temperature=0.0,
top_p=1.0,
n_ctx=cfg.n_ctx,
n_threads=cfg.n_threads,
n_gpu_layers=cfg.n_gpu_layers,
flash_attn=cfg.flash_attn,
)
if os.getenv("BUDDY_GRAMMAR_BENCH") == "1":
_grammar_bench(cfg, model_path)
except Exception as exc: # noqa: BLE001 - warmup is best-effort
_warmed = False # allow a retry on the next page load
print(f"[zerogpu-warmup] failed: {exc}", flush=True)
def _grammar_bench(cfg: "LlamaRuntimeConfig", model_path: str) -> None:
"""One-off diagnostic: run the freeform prompt UNCONSTRAINED (no GBNF grammar)
to isolate how much grammar-constrained sampling costs vs raw 12B decode.
Only the grammar-OFF run lives here: the grammar-ON throughput is already the
normal [zerogpu-bench] number every real freeform call prints (~17-22 tok/s),
so there is no need to re-measure it. Running BOTH inside this one demo.load
handler stacked a 3rd GPU acquisition after the warmup gen and ZeroGPU aborted
it ("GPU task aborted"); keeping the handler to 2 acquisitions (warmup + this)
is in the range the log shows succeeds. Uses the same n_ctx=4096 / freeform
GPU wrapper as the real calls so the number transfers directly. Gated by
BUDDY_GRAMMAR_BENCH=1; read [grammar-bench-off] off the log, compare against
the [zerogpu-bench] (grammar-on) lines, then unset the env."""
import json
from .exemplars import pick_exemplar
from .prompts import build_freeform_messages
prompt = "a small round dragon with two curved horns and a long tail"
ex = pick_exemplar(prompt)
try:
_run_freeform_completion(
model_path=model_path,
messages=build_freeform_messages(prompt, json.dumps(ex)),
schema=None,
max_tokens=int(os.getenv("BUDDY_GRAMMAR_BENCH_TOKENS", "400")),
temperature=0.8,
top_p=0.9,
n_ctx=max(cfg.n_ctx, 4096),
n_threads=cfg.n_threads,
n_gpu_layers=cfg.n_gpu_layers,
flash_attn=cfg.flash_attn,
label="grammar-bench-off",
)
except Exception as exc: # noqa: BLE001 - diagnostic only, never break warmup
print(f"[grammar-bench] failed: {exc}", flush=True)
def _optional_int(value: str | None) -> int | None:
if value is None or value == "":
return None
return int(value)
|