Repository navigation
Expand file tree
/
Copy pathbuild_models.py
More file actions
176 lines (157 loc) · 7.75 KB
/
Copy pathbuild_models.py
File metadata and controls
176 lines (157 loc) · 7.75 KB
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
"""
Curatarr - Build Ollama Models
Pulls base models if not already present, then bakes system prompts
into curatarr-curator and curatarr-summarizer.
Run once after changing BASE_CURATOR_MODEL or BASE_SUMMARIZER_MODEL:
python build_models.py
A split install (Curatarr on a NAS, the gaming PC's card as a second host:
OLLAMA_PC_ENDPOINT) builds the same bake names on each machine, from a base
that fits its card:
python build_models.py --host main this install's Ollama (BASE_*)
python build_models.py --host pc the PC's (PC_BASE_*, its window)
python build_models.py --suggest 16 what the bench recommends for a 16 GB card
python build_models.py --check both hosts, and the embedding weights
"""
import argparse
import asyncio
import sys
sys.path.insert(0, ".")
# Windows consoles default to cp1252, which crashes on the → / ✅ in our
# prints. Force UTF-8 so the rebuild never dies on a status line.
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass
from src.services.setup_wizard import build_ollama_models
from src.config import settings
async def main(host: str = "main"):
from src.services.model_check import host_plan
plan = host_plan(host)
endpoint = plan["url"]
curator_base = plan["curator"]
summarizer_base = plan["summarizer"]
# Two-bake split: only build the pitcher when the split is enabled
# (PITCHER_MODEL set in .env). Gating on BASE_PITCHER_MODEL instead
# would pull 17 GB on every fresh install. On a split install it lives
# on the PC only: deletion runs wait for the PC.
pitcher_base = plan["pitcher"]
print(f"\nHost : {host} ({endpoint}, curator window {plan['num_ctx']})")
print(f"Curator base : {curator_base} → curatarr-curator")
print(f"Summarizer base : {summarizer_base} → curatarr-summarizer")
if pitcher_base:
print(f"Pitcher base : {pitcher_base} → curatarr-pitcher")
else:
print("Pitcher : not on this host — skipping")
if not plan["embedding"]:
print("Embeddings : not on this host (a split install embeds on the main host)")
print("\nMissing models will be pulled automatically.\n")
print("─" * 60)
results = await build_ollama_models(endpoint, curator_base, summarizer_base,
base_pitcher=pitcher_base, num_ctx=plan["num_ctx"],
with_embedding=bool(plan["embedding"]))
print("─" * 60)
if results.get("curator"):
print("✅ curatarr-curator ready")
else:
print(f"❌ curatarr-curator failed")
print(f" Is '{curator_base}' available on Ollama Hub?")
if results.get("summarizer"):
print("✅ curatarr-summarizer ready")
else:
print(f"❌ curatarr-summarizer failed")
print(f" Is '{summarizer_base}' available on Ollama Hub?")
if pitcher_base:
if results.get("pitcher"):
print("✅ curatarr-pitcher ready")
else:
print(f"❌ curatarr-pitcher failed")
print(f" Is '{pitcher_base}' available on Ollama Hub?")
if plan["embedding"]:
if results.get("embedding"):
print(f"✅ {plan['embedding']} (embeddings) ready")
else:
print(f"❌ {plan['embedding']} (embeddings) failed")
print(f" Could not pull '{plan['embedding']}' — without it, enrichment")
print(f" produces text but no vectors (every item stays vector_ready=0).")
if all(results.values()):
print("\n🎬 All models ready — restart Curatarr.\n")
else:
print("\n⚠️ Fix the errors above and run this script again.\n")
sys.exit(1)
def check() -> int:
"""``--check``: 0 = every model each host runs on is present, 1 = some
are missing (named; start.bat builds them), 2 = an Ollama does not
answer, 3 = the main host's embedding weights differ from the ones the
stored vectors were made with (a build cannot fix that - start.bat only
warns). Over HTTP, so it works in the container too. The first check
records which embedding weights the vectors come from - run it on the
old machine before a move."""
from src.services.model_check import (curator_fit_warning, host_plan, missing_on,
record_embedding_digest)
from src.services.ollama_hosts import split
rc = 0
for host in (["main", "pc"] if split() else ["main"]):
plan = host_plan(host)
missing = missing_on(plan["url"], plan["models"])
if missing is None:
print(f"[{host}] Ollama at {plan['url']} is not answering - its models cannot be checked.")
rc = max(rc, 2)
elif missing:
print(f"[{host}] Missing Ollama models: " + ", ".join(missing)
+ f" (python build_models.py --host {host})")
rc = max(rc, 1)
else:
print(f"[{host}] Ollama models present: " + ", ".join(plan["models"]))
ident = record_embedding_digest()
if ident["status"] == "mismatch":
print(f"Embedding weights DIFFER: {ident['model']} on the main host is "
f"{(ident['host'] or '')[:12]}, the stored vectors were made with "
f"{ident['recorded'][:12]}. New vectors would not compare with the old ones - "
f"pull the same weights, or re-embed the library.")
rc = 3 if rc == 0 else rc
elif ident["status"] == "match":
print(f"Embedding weights match the stored vectors ({ident['model']} "
f"{ident['recorded'][:12]}).")
warning = curator_fit_warning()
if warning:
# A warning, not a failure: the models ARE present, and an operator
# who set CURATOR_NUM_GPU=-1 on purpose has chosen the slow path.
print("Warning: " + warning)
return rc
def suggest(vram_gb: float) -> int:
"""What the hardware bench recommends for a card of ``vram_gb``. Reads
the bench's matrix only - talks to no Ollama."""
from src.services.model_catalog import hardware_package
pkg = hardware_package(vram_gb)
rec = (pkg or {}).get("recommended")
if not rec:
print((pkg or {}).get("note") or "No bench data for this card.")
return 1
exp = rec.get("expected") or {}
print(f"{vram_gb:g} GB card - the bench's package (tier {pkg['tier_gb']} GB):")
print(f" curator base : {rec['model']}")
print(f" profile / window : {rec.get('profile')} / {rec.get('num_ctx')}"
f" (LLM_PROFILE / CURATOR_NUM_CTX, or PC_* for the PC)")
print(f" num_gpu : {rec.get('num_gpu')} (CURATOR_NUM_GPU)")
if rec.get("server"):
print(" Ollama server : " + ", ".join(f"{k}={v}" for k, v in rec["server"].items()))
if exp:
print(f" measured : {exp.get('gen_tok_s')} tok/s, verdict {exp.get('judge_s')} s, "
f"{exp.get('vram_gb')} GB VRAM")
print(" The bench ran on a card of its own: a card shared with other apps "
"(Plex transcodes, Immich) has less room than its size.")
return 0
if __name__ == "__main__":
ap = argparse.ArgumentParser(description="Build or check Curatarr's Ollama bakes")
ap.add_argument("--check", action="store_true", help="check every host's models")
ap.add_argument("--host", choices=("main", "pc"), default="main",
help="which Ollama to build on (pc = OLLAMA_PC_ENDPOINT)")
ap.add_argument("--suggest", type=float, metavar="VRAM_GB",
help="print the bench's recommendation for a card this size")
args = ap.parse_args()
if args.check:
sys.exit(check())
if args.suggest is not None:
sys.exit(suggest(args.suggest))
asyncio.run(main(args.host))