JG1310 commited on
Commit
1b97bdf
·
verified ·
1 Parent(s): b1136e1

strip: local-only backends; remove diagnostic endpoints

Browse files
Files changed (1) hide show
  1. arcana/gpu_models.py +12 -72
arcana/gpu_models.py CHANGED
@@ -4,20 +4,15 @@ Models load LAZILY and cache process-wide. They MUST be constructed inside an
4
  ``@spaces.GPU`` context (the only time the GPU is attached on ZeroGPU); nothing
5
  here runs at import, and CUDA must not initialise at import (the process forks).
6
 
7
- HYBRID LLM split (disk is ~1.5TB free, so we can cache several models; the limit
8
- is the 48GB VRAM PER @spaces.GPU CALL, and these are never resident together):
9
- * MAPPING (json) → a small dense model (8B Llama-Nemotron): reliable structured
10
- JSON, fast, ~5GB. Runs in the build call alongside FLUX.
11
- * READING (prose) → a larger model (Nemotron-3-Nano-30B by default on capable
12
- spaces): richer readings. Runs alone in the reading call (~18GB).
13
- Both env-overridable so a space can keep the 8B for reading too (set READ=MAP).
14
 
15
  Hard-won ZeroGPU facts (June 2026, RTX PRO 6000 Blackwell / CUDA 13 / torch cu130):
16
  * llama-cpp-python must be the cu130 wheel; preload the CUDA-13 runtime libs
17
  (site-packages/nvidia/cu13/lib) RTLD_GLOBAL before importing llama_cpp.
18
- * Nemotron-3 (ChatML) reasons unless the assistant turn is seeded with an empty
19
- <think></think> block — so for it we hand-build the prompt + create_completion.
20
- Llama-Nemotron (Llama-3 template) just needs "detailed thinking off" in system.
21
  """
22
  from __future__ import annotations
23
 
@@ -40,19 +35,10 @@ except Exception:
40
  pass
41
 
42
  # ---- model selection (env-overridable) ------------------------------------
43
- # SINGLE MODEL: Qwen3-14B for BOTH mapping and reading. We dropped Nemotron-3-30B
44
- # (and the Nemotron prize): as a 3.5B-ACTIVE MoE at Q4 it was too literal-minded —
45
- # it emitted tool calls instead of JSON, refused "divination", and went meta in the
46
- # reader. Qwen3-14B is the same family as the original endpoint version (Qwen3-32B)
47
- # at a safely ≤32B size; a plain capable instruct/reasoning model. It's a "thinking"
48
- # model, so we append "/no_think" (exactly as the old endpoint build did) for direct
49
- # output; JSON mapping is still grammar-constrained (response_format). One model,
50
- # warmed once. Mapping (design), loremaster and reading run in separate @spaces.GPU
51
- # calls; lore + FLUX run on two CONCURRENT GPUs so they never share 48GB.
52
- _DEF_8B = ("bartowski/nvidia_Llama-3.1-Nemotron-Nano-8B-v1-GGUF",
53
- "nvidia_Llama-3.1-Nemotron-Nano-8B-v1-Q4_K_M.gguf")
54
- _DEF_30B = ("unsloth/Nemotron-3-Nano-30B-A3B-GGUF",
55
- "Nemotron-3-Nano-30B-A3B-Q4_K_M.gguf")
56
  _DEF_QWEN14 = ("unsloth/Qwen3-14B-GGUF", "Qwen3-14B-Q4_K_M.gguf")
57
  MAP_REPO = os.environ.get("LOCAL_MAP_REPO", _DEF_QWEN14[0])
58
  MAP_FILE = os.environ.get("LOCAL_MAP_FILE", _DEF_QWEN14[1])
@@ -62,10 +48,6 @@ READ_FILE = os.environ.get("LOCAL_READ_FILE", _DEF_QWEN14[1])
62
  READ_N_CTX = int(os.environ.get("LOCAL_READ_N_CTX", "16384"))
63
 
64
  # FLUX.2-klein-9B (distilled → 4 steps, guidance_scale=1.0) run LOCALLY via diffusers.
65
- # Back to FLUX.2: the religious-imagery / quality drop we'd blamed on klein was really
66
- # Nemotron's weak art prompts — FLUX.1 had the same bias on RWS-styled prompts. With
67
- # Qwen3-14B art prompts + the no-religious guard in imagegen._style, klein is the better
68
- # (newer, sharper) model. Gated but already accepted on this account.
69
  IMAGE_MODEL = os.environ.get("LOCAL_IMAGE_MODEL", "black-forest-labs/FLUX.2-klein-9B")
70
 
71
  _models: dict = {}
@@ -99,9 +81,8 @@ def _get_llama(repo: str, fname: str, n_ctx: int):
99
  worker per call and only migrates torch-registered tensors; a llama.cpp context
100
  (C++/GPU pointers) isn't picklable and module-scope warmup doesn't carry. So the
101
  model loads in-worker on first call; warm workers keep it resident (2nd+ call
102
- fast). We just tune the construct: flash-attention (Llama-arch only — the 30B
103
- hybrid-Mamba doesn't support FA) + a big n_batch to speed long-prompt eval (the
104
- designer's few-shot mapping prompt)."""
105
  if fname not in _models:
106
  import time as _t
107
  from .timing import record
@@ -111,9 +92,7 @@ def _get_llama(repo: str, fname: str, n_ctx: int):
111
  from llama_cpp import Llama
112
  path = hf_hub_download(repo, fname, token=os.environ.get("HF_TOKEN"))
113
  kwargs = dict(model_path=path, n_gpu_layers=-1, n_ctx=n_ctx,
114
- n_batch=2048, verbose=False)
115
- if not _is_nemotron3(fname): # FA works for Llama/Qwen arch, not the Mamba hybrid
116
- kwargs["flash_attn"] = True
117
  try:
118
  _models[fname] = Llama(**kwargs)
119
  except Exception:
@@ -123,11 +102,6 @@ def _get_llama(repo: str, fname: str, n_ctx: int):
123
  return _models[fname]
124
 
125
 
126
- def _is_nemotron3(fname: str) -> bool:
127
- f = fname.lower()
128
- return "30b" in f or "nemotron-3-nano-3" in f
129
-
130
-
131
  def _is_qwen3(fname: str) -> bool:
132
  return "qwen3" in fname.lower()
133
 
@@ -149,40 +123,6 @@ def _chat(llm, fname: str, system: str, user: str, max_tokens: int,
149
  return (out["choices"][0]["message"]["content"] or "").strip()
150
 
151
 
152
- def _last_balanced_json(text: str) -> str:
153
- """Return the LAST top-level {...} block in text (the final answer after any
154
- reasoning), else the whole text. Lets a reasoning model think first, then we
155
- take the JSON it emits at the end."""
156
- objs, depth, start, in_str, esc = [], 0, None, False, False
157
- for i, c in enumerate(text):
158
- if in_str:
159
- if esc: esc = False
160
- elif c == "\\": esc = True
161
- elif c == '"': in_str = False
162
- continue
163
- if c == '"': in_str = True
164
- elif c == "{":
165
- if depth == 0: start = i
166
- depth += 1
167
- elif c == "}":
168
- if depth > 0:
169
- depth -= 1
170
- if depth == 0 and start is not None:
171
- objs.append(text[start:i + 1]); start = None
172
- return objs[-1] if objs else text
173
-
174
-
175
- def _suppress_gen(llm, system: str, user: str, max_tokens: int, temperature: float) -> str:
176
- """Nemotron-3 ChatML with an empty <think></think> seed → clean, non-reasoning
177
- prose (fast). Used for readings."""
178
- prompt = (f"<|im_start|>system\n{system}<|im_end|>\n"
179
- f"<|im_start|>user\n{user}<|im_end|>\n"
180
- f"<|im_start|>assistant\n<think></think>\n")
181
- out = llm.create_completion(prompt=prompt, max_tokens=max_tokens,
182
- temperature=temperature, stop=["<|im_end|>"])
183
- return (out["choices"][0]["text"] or "").strip()
184
-
185
-
186
  def llama_map(system: str, user: str, max_tokens: int = 12000,
187
  temperature: float = 0.6, schema: dict | None = None) -> str:
188
  """Structured JSON generation (mapping AND loremaster). When a JSON-SCHEMA is
 
4
  ``@spaces.GPU`` context (the only time the GPU is attached on ZeroGPU); nothing
5
  here runs at import, and CUDA must not initialise at import (the process forks).
6
 
7
+ ONE LLM (Qwen3-14B) serves all three roles (mapping, scribe, reading); the work is
8
+ fanned across separate @spaces.GPU calls (and concurrent GPU slots) rather than
9
+ across different models. The image model (FLUX.2-klein) is the only other model.
 
 
 
 
10
 
11
  Hard-won ZeroGPU facts (June 2026, RTX PRO 6000 Blackwell / CUDA 13 / torch cu130):
12
  * llama-cpp-python must be the cu130 wheel; preload the CUDA-13 runtime libs
13
  (site-packages/nvidia/cu13/lib) RTLD_GLOBAL before importing llama_cpp.
14
+ * Qwen3 is a "thinking" model — we append "/no_think" for a direct answer; JSON is
15
+ grammar-constrained via response_format (see _chat).
 
16
  """
17
  from __future__ import annotations
18
 
 
35
  pass
36
 
37
  # ---- model selection (env-overridable) ------------------------------------
38
+ # ONE open ≤32B LLM (Qwen3-14B, Apache-2.0) for all three roles: mapping, scribe,
39
+ # reading. A "thinking" model — _chat appends "/no_think" for a direct answer; JSON
40
+ # is grammar-constrained (response_format). The roles run in separate @spaces.GPU
41
+ # calls (and the scribe/painter fan across concurrent GPU slots).
 
 
 
 
 
 
 
 
 
42
  _DEF_QWEN14 = ("unsloth/Qwen3-14B-GGUF", "Qwen3-14B-Q4_K_M.gguf")
43
  MAP_REPO = os.environ.get("LOCAL_MAP_REPO", _DEF_QWEN14[0])
44
  MAP_FILE = os.environ.get("LOCAL_MAP_FILE", _DEF_QWEN14[1])
 
48
  READ_N_CTX = int(os.environ.get("LOCAL_READ_N_CTX", "16384"))
49
 
50
  # FLUX.2-klein-9B (distilled → 4 steps, guidance_scale=1.0) run LOCALLY via diffusers.
 
 
 
 
51
  IMAGE_MODEL = os.environ.get("LOCAL_IMAGE_MODEL", "black-forest-labs/FLUX.2-klein-9B")
52
 
53
  _models: dict = {}
 
81
  worker per call and only migrates torch-registered tensors; a llama.cpp context
82
  (C++/GPU pointers) isn't picklable and module-scope warmup doesn't carry. So the
83
  model loads in-worker on first call; warm workers keep it resident (2nd+ call
84
+ fast). We tune the construct: flash-attention + a big n_batch to speed long-prompt
85
+ eval (the designer's few-shot mapping prompt)."""
 
86
  if fname not in _models:
87
  import time as _t
88
  from .timing import record
 
92
  from llama_cpp import Llama
93
  path = hf_hub_download(repo, fname, token=os.environ.get("HF_TOKEN"))
94
  kwargs = dict(model_path=path, n_gpu_layers=-1, n_ctx=n_ctx,
95
+ n_batch=2048, flash_attn=True, verbose=False)
 
 
96
  try:
97
  _models[fname] = Llama(**kwargs)
98
  except Exception:
 
102
  return _models[fname]
103
 
104
 
 
 
 
 
 
105
  def _is_qwen3(fname: str) -> bool:
106
  return "qwen3" in fname.lower()
107
 
 
123
  return (out["choices"][0]["message"]["content"] or "").strip()
124
 
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  def llama_map(system: str, user: str, max_tokens: int = 12000,
127
  temperature: float = 0.6, schema: dict | None = None) -> str:
128
  """Structured JSON generation (mapping AND loremaster). When a JSON-SCHEMA is