mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-09 03:59:45 -10:00
Merge f-0213-mmr (e8f2380f3): make_model_release.py case B plans a new-shape Qwen4 engine as its own component (strata-q4-<shape> packages, build-feed --shapes, no Strata engine/Control/app rebuild, entry family qwen4exp_<shape>), with the Mac shape engine in case B (owner signs); full tools/check on Windows and Linux with STRATA_TEST_LLAMA_CPP set: no new failures (p2006's two known base failures; the known flaky test_tuiqa_wizard)
This commit is contained in:
commit
9522b52499
2 files changed
+93
-43
No files matched your search
@@ -11,7 +11,9 @@ engines have to be rebuilt for its shape; case C is refused) and prints the plan
|
||||
case A the feed (a `models` entry, the feed re-signed at the next sequence). No app rebuild: the app lists feed models
|
||||
from the feed (the Settings update check is enough). `--with-app` adds the app steps when a release has app text anyway.
|
||||
Engines, DeepSeek packages and Controls stay as they are: same files, same names.
|
||||
case B the Qwen engine packages and the Controls rebuilt at a new version, plus the feed and the app.
|
||||
case B the engine of the model's shape, as its own component (`--shape`, package_engine.py --shape, feed member shape_engines: strata_control/shape_engine.py),
|
||||
plus the feed. The Strata engine, the DeepSeek packages, the Controls and the app are not rebuilt: a Control of 0.2.13 or later already installs and runs
|
||||
a shape engine, and the model's family `qwen4exp_<shape>` makes it fetch it.
|
||||
|
||||
Each step shows where it runs, a rough time, the command (printed, never run) and its outputs with sizes. Sizes are those of the
|
||||
previous release's files (`--prev-upload`), so they are estimates for what gets rebuilt and exact for what stays.
|
||||
@@ -30,6 +32,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT)) # strata_control.shape_engine (the shape names)
|
||||
|
||||
# artifact names in a release upload folder (phaseA-tests/rel0211/PUBLISH-PLAN.md, scripts/relfinal/expect_rc1.py)
|
||||
ENGINE_RE = re.compile(r"^strata-engine-(?P<v>\d[\d.]*)-(?P<plat>.+?)\.(?:tar\.gz|zip)$")
|
||||
@@ -99,7 +102,7 @@ def plan(report: dict, set_index: int, a, prev: dict) -> dict:
|
||||
if case not in ("A", "B"):
|
||||
raise SystemExit("case %s: %s. A model release is planned for case A or B only (run tools/qwen4_intake.py)."
|
||||
% (case, v.get("reading", "no verdict in the report")))
|
||||
with_app = bool(a.with_app) or (case == "B" and not a.no_app) # case A: the app needs no rebuild for a feed model
|
||||
with_app = bool(a.with_app) # a feed model needs no app rebuild (the app lists it from the feed), case B included: the shape engine is fetched by the Control
|
||||
steps: list[dict] = []
|
||||
changed: list[dict] = [] # files that are new or different in the upload folder
|
||||
kept: list[dict] = []
|
||||
@@ -124,36 +127,38 @@ def plan(report: dict, set_index: int, a, prev: dict) -> dict:
|
||||
["intake-pinned.json"])
|
||||
n += 1
|
||||
if case == "B":
|
||||
shape = a.shape
|
||||
comp = "strata-" + shape.replace("_", "-")
|
||||
for e in prev["engine"]:
|
||||
if e["name"].endswith((".tar.gz", ".zip")):
|
||||
new = renamed(e["name"], e["version"], a.engine_version)
|
||||
changed.append({"name": new, "size": e["size"], "est": True, "why": "rebuilt for the model's shape"})
|
||||
for e in prev["control"]:
|
||||
new = renamed(e["name"], e["version"], a.engine_version)
|
||||
changed.append({"name": new, "size": e["size"], "est": True, "why": "rebuilt at the new version"})
|
||||
new = renamed(e["name"], e["version"], a.engine_version).replace("strata-engine-", comp + "-", 1)
|
||||
changed.append({"name": new, "size": e["size"], "est": True, "why": "the engine of the shape %s, its own component" % shape})
|
||||
kept += [{"name": e["name"], "size": e["size"], "why": "the Strata engine is not touched: Flash-Next and every other model keep it"} for e in prev["engine"]]
|
||||
kept += [{"name": e["name"], "size": e["size"], "why": "DeepSeek packages are not touched (check that no shared kernel header changed)"} for e in prev["dsv41"]]
|
||||
kept += [{"name": e["name"], "size": e["size"], "why": "no Control rebuild: a Control of 0.2.13 or later already knows shape engines"} for e in prev["control"]]
|
||||
by_box: dict[str, list] = {}
|
||||
for e in prev["engine"]:
|
||||
by_box.setdefault(box_for(e["plat"]), []).append(e)
|
||||
step("%d" % n, "A geometry header for the model's shape, made from the file, and the version bump", "owner PC (python and editing)", 3,
|
||||
step("%d" % n, "A geometry header for the model's shape, made from the file", "owner PC (python)", 2,
|
||||
["# the shape fields the intake listed:"] + ["# %s is %s (kernels built for %s)" % (d["key"], d["got"], d.get("kernels_built_for", d.get("limit")))
|
||||
for d in v.get("shape_fields_that_differ", [])]
|
||||
+ ["python tools/q4geo_synth.py --from-gguf <first shard> --header <geo.hpp>",
|
||||
"# bump %s in CMakeLists.txt, metal/CMakeLists.txt and PKGVER in packaging/build_hip_linux.sh BEFORE any build (and before the Terminal is packed)" % a.engine_version,
|
||||
"# every build passes -DSTRATA_GEOMETRY_FILE=<geo.hpp> (Linux packaging scripts: STRATA_CMAKE_EXTRA=\"-DSTRATA_GEOMETRY_FILE=<geo.hpp>\")"],
|
||||
note="a package built for the new shape refuses Flash-Next files: decide with the owner whether it replaces the engine or ships as its own component (RESULTS-Q4-P8.md G4)")
|
||||
+ ["python tools/q4geo_synth.py --from-gguf <first shard> --header <geo.hpp> --label %s" % shape,
|
||||
"# every build passes -DSTRATA_GEOMETRY_FILE=<geo.hpp> (Linux packaging scripts: STRATA_CMAKE_EXTRA=\"-DSTRATA_GEOMETRY_FILE=<geo.hpp>\"); give the engines their own version %s" % a.engine_version],
|
||||
note="the engine built for this shape refuses Flash-Next files, so it ships as its own component named after the shape (%s) and never in place of the Strata engine (owner, 2026-11-08; RESULTS-SHAPE.md)" % comp)
|
||||
n += 1
|
||||
for box, es in sorted(by_box.items()):
|
||||
step("%d" % n, "Build the Qwen engine packages on %s" % box, "%s (%s)" % (box, BOX_NOTE.get(box, "see the 0.2.11 build plan")), 25 if box.startswith("owner") else 15 if box == "l620" else 10,
|
||||
["# per package, as in phaseA-tests/rel0211/scripts (build_*.sh, packaging/package_engine.py), then:",
|
||||
step("%d" % n, "Build the %s engine packages on %s" % (shape, box), "%s (%s)" % (box, BOX_NOTE.get(box, "see the 0.2.11 build plan")), 25 if box.startswith("owner") else 15 if box == "l620" else 10,
|
||||
["# per package, as in phaseA-tests/rel0211/scripts (build_*.sh), with the shape's header; then, instead of the usual package_engine.py call:",
|
||||
"python packaging/package_engine.py --src <stage> --backend <backend> --version %s --shape %s --min-runtime <the Control that knows shape engines> ... # the usual --arch/--variant/--tree/--min-driver of that platform" % (a.engine_version, shape),
|
||||
"python packaging/feed_tool.py sign-package <package-folder> --key <FEED-KEY> # OWNER'S KEY, not run here",
|
||||
"python packaging/feed_tool.py pack-engine <package-folder> --out <packs> --url-prefix="],
|
||||
[renamed(e["name"], e["version"], a.engine_version) + " (" + gib(e["size"]) + ", estimated)" for e in es])
|
||||
"python packaging/feed_tool.py pack-engine <package-folder> --out <shape-packs> --url-prefix= # the archive is %s-<version>-<platform>-<backend>, the entry names the shape" % comp],
|
||||
[renamed(e["name"], e["version"], a.engine_version).replace("strata-engine-", comp + "-", 1) + " (" + gib(e["size"]) + ", estimated)" for e in es])
|
||||
n += 1
|
||||
step("%d" % n, "Controls: pack at %s" % a.engine_version, "Linux (tar keeps modes), Windows, Mac as in 0.2.11", 6,
|
||||
["python packaging/feed_tool.py pack-control apps/desktop/src-tauri/resources/control --version %s --platform <platform> --out <control> --url-prefix=" % a.engine_version],
|
||||
[renamed(e["name"], e["version"], a.engine_version) for e in prev["control"]])
|
||||
n += 1
|
||||
kept += [{"name": e["name"], "size": e["size"], "why": "DeepSeek packages are not touched (check that no shared kernel header changed)"} for e in prev["dsv41"]]
|
||||
step("%d" % n, "Mac: the owner signs and notarises the unsigned %s engine on his Mac, then it is signed into the feed" % shape, "the owner's Mac, about 45 min of his time", 45,
|
||||
["bash sign-and-notarize.sh --engine strata-engine-%s-macos-arm64-metal-unsigned-folder.tar.gz # the code is generic: the entrypoint comes from the manifest (the package's id is %s)" % (a.engine_version, comp),
|
||||
"python packaging/feed_tool.py sign-package <the signed folder> --key <FEED-KEY> # OWNER'S KEY, not run here", "python packaging/feed_tool.py pack-engine <the signed folder> --out <shape-packs> --url-prefix="],
|
||||
[comp + "-%s-macos-arm64-metal.tar.gz" % a.engine_version], owner=True,
|
||||
note="a Mac installs a model of its own shape like every other platform (Terminal and app): the Mac engine of the shape is part of the release; after the Mac package exists and before build-feed")
|
||||
else:
|
||||
kept += [{"name": e["name"], "size": e["size"], "why": "same file, same name; the feed entry is reused"} for e in prev["engine"] + prev["dsv41"] + prev["control"]]
|
||||
|
||||
@@ -173,15 +178,15 @@ def plan(report: dict, set_index: int, a, prev: dict) -> dict:
|
||||
changed.append({"name": "latest.json", "size": None, "est": True, "why": "updater json at %s" % a.app_version})
|
||||
else:
|
||||
kept += [{"name": e["name"], "size": e["size"], "why": "no app change"} for e in prev["app"]]
|
||||
if with_app or case == "B":
|
||||
mac_extra = " --terminal <terminal.tar.gz> --engine <engine-unsigned-folder.tar.gz>" if case == "B" else ""
|
||||
if with_app:
|
||||
mac_extra = ""
|
||||
step("%d" % n, "Mac: the owner signs and notarises the unsigned Mac files on his Mac", "the owner's Mac, about 45 min of his time", 45,
|
||||
["bash sign-and-notarize.sh Strata-%s-macos-arm64.dmg%s --updater-archive --out signed" % (a.app_version, mac_extra)],
|
||||
["the signed folder (dmg, Strata.app.tar.gz and its .sig%s)" % (", the Mac engine and Terminal" if case == "B" else "")], owner=True)
|
||||
["the signed folder (dmg, Strata.app.tar.gz and its .sig)"], owner=True)
|
||||
n += 1
|
||||
feed_cmd = ("python packaging/feed_tool.py build-feed --out <RELEASE> --key <FEED-KEY> --sequence %d --engine <engine-entries> --dsv41 <dsv41-entries> "
|
||||
"--drafters <drafter-entries> --models <models-folder> --control <control-entries>%s # OWNER'S KEY; `--models` is Q4-PREP-B's change"
|
||||
% (a.sequence, " --app-json latest.json" if with_app else ""))
|
||||
"--drafters <drafter-entries> --models <models-folder> --control <control-entries>%s%s # OWNER'S KEY; `--models` is Q4-PREP-B's change"
|
||||
% (a.sequence, " --shapes <shape-entries>" if case == "B" else "", " --app-json latest.json" if with_app else ""))
|
||||
step("%d" % n, "Sign the feeds at sequence %d (the shared one and linux-aarch64)" % a.sequence, "owner PC, with the owner's key at that moment", 3,
|
||||
[feed_cmd, "python packaging/feed_tool.py verify-feed <RELEASE> --pub <FEED-KEY.pub.json>"],
|
||||
list(FEED_NAMES), owner=True)
|
||||
@@ -208,6 +213,17 @@ def plan(report: dict, set_index: int, a, prev: dict) -> dict:
|
||||
"warnings": warn, "upload_bytes": up}
|
||||
|
||||
|
||||
def entry_family(s: dict, a) -> str:
|
||||
"""The family the feed entry carries: the GGUF architecture, and for a model of its own shape (case B) `qwen4exp_<shape>`, which makes a Control fetch that shape's engine
|
||||
(strata_control/shape_engine.py) and makes an older Control, which does not know the family, leave the model out."""
|
||||
arch = s.get("architecture")
|
||||
shape = getattr(a, "shape", None)
|
||||
if shape and arch == "qwen4exp":
|
||||
from strata_control import shape_engine
|
||||
return shape_engine.family_of(shape)
|
||||
return arch
|
||||
|
||||
|
||||
def model_entry(s: dict, a) -> dict:
|
||||
d = s.get("draft_catalog_entry", {})
|
||||
g = s.get("general", {})
|
||||
@@ -217,7 +233,7 @@ def model_entry(s: dict, a) -> dict:
|
||||
"_status": "DRAFT: made from the intake report by packaging/make_model_release.py; field names follow the Q4-PREP-B brief and are to be checked against strata_control/feed.py",
|
||||
"id": a.id, "title": g.get("name") or d.get("title"), "by": d.get("by"),
|
||||
"licence": {"name": g.get("license"), "url": g.get("license.link") or "[FILL: licence URL]"},
|
||||
"family": s.get("architecture"), "hf": {"repo": a.hf_repo or "[FILL: repository]", "revision": a.revision or "[FILL: pinned commit]"},
|
||||
"family": entry_family(s, a), "hf": {"repo": a.hf_repo or "[FILL: repository]", "revision": a.revision or "[FILL: pinned commit]"},
|
||||
"files": [{"name": f["name"], "size": f["size"], "sha256": f.get("sha256")} for f in s["files"]],
|
||||
"mmproj": ({"name": s["projectors"][0]["file"], "size": s["projectors"][0]["size"]} if s.get("projectors") else None),
|
||||
"drafter": "mtp" if s.get("mtp", {}).get("in_file") else "none",
|
||||
@@ -245,7 +261,7 @@ def feed_entry(s: dict, a) -> dict:
|
||||
"about": a.about or "[FILL: one plain sentence]",
|
||||
"licence": a.licence or g.get("license") or "[FILL: licence]",
|
||||
"licence_url": a.licence_url or g.get("license.link") or "[FILL: https licence url]",
|
||||
"family": s.get("architecture"), "repo": a.hf_repo or "[FILL: repository]", "revision": a.revision or "[FILL: pinned commit, 40 hex]",
|
||||
"family": entry_family(s, a), "repo": a.hf_repo or "[FILL: repository]", "revision": a.revision or "[FILL: pinned commit, 40 hex]",
|
||||
"quants": [{"name": quant, "about": a.quant_about or "[FILL: one plain line]", "ram_gb": a.ram_gb if a.ram_gb else "[FILL: ram_gb, measured]", "files": files}],
|
||||
"pictures": bool(s.get("projectors") and all(p["supported"] for p in s["projectors"])),
|
||||
"drafter": "mtp" if s.get("mtp", {}).get("in_file") else "none",
|
||||
@@ -313,7 +329,8 @@ def main(argv=None) -> int:
|
||||
ap.add_argument("--set", type=int, default=0, help="which set of the report (default 0)")
|
||||
ap.add_argument("--id", required=True, help="the model's id in the feed")
|
||||
ap.add_argument("--app-version", required=True)
|
||||
ap.add_argument("--engine-version", help="case B: the new engine and Control version")
|
||||
ap.add_argument("--engine-version", help="case B: the version of the new shape engine")
|
||||
ap.add_argument("--shape", help="case B: the name of the geometry the engines are compiled for (q4_lite): the engine ships as its own component strata-q4-<shape> and the model's family is qwen4exp_<shape>")
|
||||
ap.add_argument("--min-engine", help="the minimum engine version for the model (case A: the engine that runs it)")
|
||||
ap.add_argument("--sequence", type=int, required=True, help="the next feed sequence")
|
||||
ap.add_argument("--prev-upload", type=Path, help="the previous release's upload folder (names and sizes)")
|
||||
@@ -345,7 +362,13 @@ def main(argv=None) -> int:
|
||||
s = report["sets"][a.set]
|
||||
case = a.case or s.get("verdict", {}).get("case")
|
||||
if case == "B" and not a.engine_version:
|
||||
raise SystemExit("case B rebuilds the engines: give --engine-version")
|
||||
raise SystemExit("case B builds the engine of the new shape: give --engine-version")
|
||||
if case == "B":
|
||||
from strata_control import shape_engine
|
||||
if not a.shape or not shape_engine.valid(a.shape):
|
||||
raise SystemExit("case B: give --shape, the name of the geometry the engines are compiled for (like q4_lite: lower case, digits and _); that engine ships as its own component")
|
||||
elif a.shape:
|
||||
raise SystemExit("--shape is for case B (a model of a new shape); case A runs on the engines as they are")
|
||||
prev = scan_upload(a.prev_upload)
|
||||
if a.prev_upload is None:
|
||||
print("note: no --prev-upload, so sizes of the files that stay or get rebuilt are unknown", file=sys.stderr)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""packaging/make_model_release.py: the model-release plan is a dry run, case A leaves the engines alone, case B rebuilds them, and the
|
||||
steps that need the release key only print a placeholder.
|
||||
"""packaging/make_model_release.py: the model-release plan is a dry run, case A leaves the engines alone, case B ships the engine of the new shape as its own
|
||||
component (the Strata engine, the Controls and the app are not rebuilt), and the steps that need the release key only print a placeholder.
|
||||
|
||||
python -m unittest tests.terminal.test_make_model_release
|
||||
"""
|
||||
@@ -92,7 +92,7 @@ class Plan(unittest.TestCase):
|
||||
self.assertTrue(any("App text" in s["what"] for s in p["steps"]))
|
||||
|
||||
def test_the_plan_names_the_boxes_of_the_rehearsal(self):
|
||||
code, out, err = run(self.base(self.a, "--case", "B", "--engine-version", "0.1.58"))
|
||||
code, out, err = run(self.base(self.a, "--case", "B", "--engine-version", "0.1.58", "--shape", "q4_lite"))
|
||||
self.assertEqual(code, 0, err)
|
||||
for good in ("l620", "p1006", "owner PC", "m1002"):
|
||||
self.assertIn(good, out)
|
||||
@@ -101,20 +101,47 @@ class Plan(unittest.TestCase):
|
||||
self.assertIn("STRATA_GEOMETRY_FILE", out)
|
||||
self.assertIn("--from-gguf", out)
|
||||
|
||||
def test_case_b_rebuilds_engines_and_controls_at_the_new_version(self):
|
||||
code, out, err = run(self.base(self.a, "--json", "--case", "B", "--engine-version", "0.1.58"))
|
||||
def test_case_b_ships_the_shape_engine_as_its_own_component_and_rebuilds_nothing_else(self):
|
||||
code, out, err = run(self.base(self.a, "--json", "--case", "B", "--engine-version", "0.1.58", "--shape", "q4_lite"))
|
||||
self.assertEqual(code, 0, err)
|
||||
p = json.loads(out)
|
||||
names = {c["name"] for c in p["changed"]}
|
||||
self.assertIn("strata-engine-0.1.58-linux-x86_64-sycl.tar.gz", names)
|
||||
self.assertIn("strata-engine-0.1.58-windows-x86_64-cuda-cuda13.zip", names)
|
||||
self.assertIn("strata-control-0.1.58-linux-x86_64.tar.gz", names)
|
||||
self.assertIn("strata-q4-lite-0.1.58-linux-x86_64-sycl.tar.gz", names)
|
||||
self.assertIn("strata-q4-lite-0.1.58-windows-x86_64-cuda-cuda13.zip", names)
|
||||
self.assertFalse(any(n.startswith(("strata-engine-", "strata-control-", "strata-dsv41-")) for n in names), "the Strata engine and the Controls are not rebuilt")
|
||||
self.assertFalse(any("0.1.57" in n for n in names))
|
||||
self.assertIn("strata-dsv41-0.1.57-linux-x86_64-cuda-cuda13.tar.gz", {k["name"] for k in p["kept"]})
|
||||
self.assertTrue(any("Mac" in s["what"] and s["owner_only"] for s in p["steps"]))
|
||||
self.assertTrue(p["upload_bytes"] > 1_800_000_000)
|
||||
code, _, err = run(self.base(self.a, "--case", "B"))
|
||||
self.assertNotEqual(code, 0) # case B needs an engine version
|
||||
kept = {k["name"] for k in p["kept"]}
|
||||
self.assertTrue({n for n in PREV if n.startswith(("strata-engine-", "strata-dsv41-", "strata-control-", "Strata-"))} <= kept)
|
||||
self.assertFalse(any("Controls: pack" in s["what"] or "App text" in s["what"] for s in p["steps"]))
|
||||
self.assertIn("strata-q4-lite-0.1.58-macos-arm64-metal.tar.gz", names)
|
||||
self.assertTrue(any("Mac" in s["what"] and s["owner_only"] for s in p["steps"]), "the owner signs the Mac shape engine")
|
||||
self.assertTrue(any("--shape q4_lite" in c for s in p["steps"] for c in s["commands"]))
|
||||
self.assertTrue(any("--shapes <shape-entries>" in c for s in p["steps"] for c in s["commands"]))
|
||||
self.assertEqual(p["entry"]["family"], "qwen35moe", "the family changes only for a qwen4exp model")
|
||||
for missing in ({"--shape": None}, {"--engine-version": None}):
|
||||
args = ["--case", "B", "--engine-version", "0.1.58", "--shape", "q4_lite"]
|
||||
for k in missing:
|
||||
i = args.index(k)
|
||||
del args[i:i + 2]
|
||||
code, _, err = run(self.base(self.a, *args))
|
||||
self.assertNotEqual(code, 0, missing) # case B needs an engine version and a shape
|
||||
code, _, err = run(self.base(self.a, "--case", "B", "--engine-version", "0.1.58", "--shape", "Not A Shape"))
|
||||
self.assertNotEqual(code, 0)
|
||||
code, _, err = run(self.base(self.a, "--shape", "q4_lite"))
|
||||
self.assertNotEqual(code, 0) # --shape is for case B
|
||||
|
||||
def test_a_case_b_entry_names_the_family_of_its_shape(self):
|
||||
from strata_control import feed as feedmod
|
||||
rep = json.loads(self.a.read_text())
|
||||
rep["sets"][0]["architecture"] = "qwen4exp"
|
||||
b = Path(self.tmp.name) / "b.json"
|
||||
b.write_text(json.dumps(rep))
|
||||
out = Path(self.tmp.name) / "outb"
|
||||
code, _, err = run(self.base(b, "--case", "B", "--engine-version", "0.1.59", "--shape", "q4_lite", "--write", str(out), "--revision", "a" * 40, "--hf-repo", "x/y"))
|
||||
self.assertEqual(code, 0, err)
|
||||
self.assertEqual(json.loads((out / "models" / "m1.entry.json").read_text())["family"], "qwen4exp_q4_lite")
|
||||
self.assertEqual(json.loads((out / "models" / "m1.model.json").read_text())["family"], "qwen4exp_q4_lite")
|
||||
self.assertEqual(feedmod.shape_engine.shape_of_family("qwen4exp_q4_lite"), "q4_lite")
|
||||
|
||||
def test_case_c_is_refused(self):
|
||||
p = Path(self.tmp.name) / "c.json"
|
||||
|
||||
Reference in new issue
Block a user