Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/model-preparation.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,25 @@ captured capacities from 64 through 32,768 patches. Each feature slot holds an
advance. `--image-concurrency` defaults to the smaller of four and text
concurrency. `--language-model-only` omits image weights and graphs.

The `glm-5.3-flash-trellismx` alias selects `brandonmusic/glm-5.3-flash-tr3-fp8`,
a TrellisMX release quantized from `zai-org/GLM-5.3-Flash-BF16`. It overlays
per-layer, per-rank routed-expert sidecars on a `carrier/` directory that holds
every other tensor as stored, BF16 projections and head included. The sidecars
partition the experts for TP4, so only four GPUs can load it.
Preparation reads each sidecar once, checks its size and SHA-256 against the
release manifest, and builds its tensors from those bytes. Its routed experts
run on b12x's in-tree P8 runtime, which preparation builds with b12x's declared
GLM-5.3 Flash serving configuration (the runtime reads no environment variables)
and attaches under b12x's in-tree backend declaration. The launch geometry is a
prepared choice: each capacity's plan races the launch configurations b12x
compiles and fixes the winner before capture, and the plan declares the arena
every P8 call carves its buffers out of. Qualify a release with every GPU and
release test run, none skipped:

```sh
CUDA_VISIBLE_DEVICES=<assigned GPU> .venv/bin/python scripts/qualify_glm53_trellismx.py --release <release root>
```

## DeepSeek V4.1 Flash

The `deepseek-v4.1-flash` alias selects `deepseek-ai/DeepSeek-V4.1-Flash-1017`
Expand Down
4 changes: 2 additions & 2 deletions ignition/cli/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def recipe_help(model=None):
lines = ["Available model recipes:",
f" {'MODEL':<26}{'GPUs':<14}Drafts (default/max)"]
for recipe, checkpoint in catalog:
gpus = ",".join(map(str, recipe.tensor_parallel))
gpus = ",".join(map(str, recipe.checkpoint_tensor_parallel(checkpoint)))
drafts = ("none" if recipe.speculation is None else
f"{recipe.speculation.draft_tokens}/{recipe.max_draft_tokens}")
lines.append(f" {checkpoint.alias:<26}{gpus:<14}{drafts}")
Expand All @@ -29,7 +29,7 @@ def recipe_help(model=None):

recipe, checkpoint = selected
lines = [f"Model recipe: {checkpoint.alias}", f" Checkpoint: {checkpoint.repository}",
f" --gpus: select {', '.join(map(str, recipe.tensor_parallel))} GPUs",
f" --gpus: select {', '.join(map(str, recipe.checkpoint_tensor_parallel(checkpoint)))} GPUs",
f" --max-concurrency: 1..{recipe.max_concurrency}; recipe default {recipe.concurrency}",
f" --prefill-chunk-size: 64..{recipe.context}, multiple of 64; default {recipe.prefill_chunk}"]
if recipe.max_images:
Expand Down
4 changes: 3 additions & 1 deletion ignition/cli/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ def _publisher(configuration, args, model=None, hub_cache=None):
return signer
from ignition.preparation.model_card import model_license
from ignition.preparation.checkpoint import inspect_indexed_checkpoint
from ignition.preparation.trellismx_release import trellismx_assets_root
try:
metadata = inspect_indexed_checkpoint(model.snapshot(hub_cache)).metadata_path
# A TrellisMX release overlays a carrier: its index and model card live under carrier/.
metadata = inspect_indexed_checkpoint(trellismx_assets_root(model.snapshot(hub_cache))).metadata_path
license = model_license(metadata,
None if model.directory is not None else model.checkpoint.repository,
model.checkpoint.revision)
Expand Down
3 changes: 2 additions & 1 deletion ignition/planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,8 @@ def plan(recipe: Recipe, checkpoint: Checkpoint, gpus: tuple[Gpu, ...], *, selec
if chunk % 64 or not 64 <= chunk <= recipe.context:
raise UsageError(f"--prefill-chunk-size for {checkpoint.alias} must be a multiple of 64"
f" from 64 through {recipe.context:,}")
world_sizes = recipe.tensor_parallel
# A checkpoint whose storage fixes its tensor-parallel sizes plans only those.
world_sizes = recipe.checkpoint_tensor_parallel(checkpoint)
if speculation is not None and speculation.vocabulary_parallel_sampling:
if recipe.name != "deepseek41":
raise UsageError("--vocabulary-parallel-sampling requires DeepSeek V4.1")
Expand Down
157 changes: 157 additions & 0 deletions ignition/preparation/trellismx_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 Flare community contributors
# SPDX-License-Identifier: Apache-2.0

"""Locate a checkpoint's assets when a TrellisMX release overlays a carrier.

A TrellisMX release is a routed-expert overlay: its root holds the manifest, the
per-layer sidecars and the design record, while the safetensors index, tokenizer,
chat template and generation config stay in ``carrier/`` with every tensor the
sidecars do not replace. Shared code that resolves or inspects a checkpoint asks
this module where those files are, without importing a recipe.

Only the manifest is read, and no payload. This module does not validate a release;
the recipe that serves one owns every gate. A root without this manifest is treated
exactly as before so no other checkpoint's behaviour moves, and a manifest that
cannot be read raises instead of letting its root pass for a plain checkpoint.
"""

from __future__ import annotations

import json
from pathlib import Path

MANIFEST_NAME = "trellismx-manifest.json"
MANIFEST_SCHEMA = "trellismx.hf-overlay-release.v1"
CARRIER_DIRECTORY = "carrier"
SIDECAR_DIRECTORY = "sidecars"
# The encoder transform the sidecars pin, which preparation reads beside them.
TRANSFORM_PATH = "design/transform.json"


def sidecar_path(layer: int, rank: int, world_size: int) -> str:
"""The release-relative path of one layer and tensor-parallel rank's sidecar."""
return f"{SIDECAR_DIRECTORY}/p8-layer-{layer:03d}-tp{world_size}-rank-{rank}.safetensors"


def _manifest(root: Path) -> dict | None:
"""``root``'s overlay manifest when it declares this schema, else None.

Raises ``ValueError`` when the manifest exists but is not a readable JSON object.
"""
manifest_path = root / MANIFEST_NAME
if not manifest_path.is_file():
return None
try:
manifest = json.loads(manifest_path.read_text())
except (OSError, ValueError) as error:
raise ValueError(f"{manifest_path} is unreadable: {error}") from error
if not isinstance(manifest, dict):
raise ValueError(f"{manifest_path} is not a JSON object")
return manifest if manifest.get("schema") == MANIFEST_SCHEMA else None


def _carrier_index(root: Path) -> Path:
return root / CARRIER_DIRECTORY / "model.safetensors.index.json"


def trellismx_assets_root(root: str | Path) -> Path:
"""The directory holding ``root``'s index and frontend assets.

The carrier directory when ``root`` declares this overlay schema and carries a
carrier index, and ``root`` itself otherwise.
"""
root = Path(root).expanduser()
return root / CARRIER_DIRECTORY if _manifest(root) is not None and _carrier_index(root).is_file() else root


def overlay_schema(root: str | Path) -> str | None:
"""The overlay manifest schema ``root`` is packaged with, or None for any other checkpoint."""
return MANIFEST_SCHEMA if _manifest(Path(root).expanduser()) is not None else None


def _allocation(manifest: dict) -> dict[int, int]:
"""The manifest's allocation: each layer index and the tensor-parallel size it is sliced for."""
allocation = manifest.get("allocation")
if not isinstance(allocation, dict) or not allocation:
raise ValueError("TrellisMX manifest allocation must be a nonempty object of layer indices")
layers = {}
for key, world_size in allocation.items():
try:
layer = int(key)
except ValueError:
layer = None
if layer is None or key != str(layer) or layer < 0 or type(world_size) is not int or world_size <= 0:
raise ValueError(f"TrellisMX manifest allocation entry {key!r}: {world_size!r} is invalid")
layers[layer] = world_size
return layers


def _manifest_files(manifest: dict) -> list[tuple[str, int]]:
"""Every file the manifest lists, which must be exactly the release's canonical inventory.

That inventory is one rank-named sidecar for every layer and rank the allocation
declares, so a list that omits, repeats, renames or adds an entry is rejected
instead of making an incomplete download look complete.
"""
files = manifest.get("files")
if not isinstance(files, list) or not files:
raise ValueError("TrellisMX manifest lists no files")
entries = []
for entry in files:
path = entry.get("path") if isinstance(entry, dict) else None
size = entry.get("bytes") if isinstance(entry, dict) else None
if not isinstance(path, str) or not path or Path(path).is_absolute() or ".." in Path(path).parts \
or type(size) is not int or size <= 0:
raise ValueError(f"TrellisMX manifest file entry {entry!r} is invalid")
entries.append((path, size))
inventory = {sidecar_path(layer, rank, world_size)
for layer, world_size in _allocation(manifest).items() for rank in range(world_size)}
listed = [path for path, _ in entries]
if len(listed) != len(set(listed)) or set(listed) != inventory:
missing, unexpected = sorted(inventory - set(listed)), sorted(set(listed) - inventory)
repeated = sorted({path for path in listed if listed.count(path) > 1})
raise ValueError("TrellisMX manifest files must be one sidecar per allocated layer and rank:"
f" missing {missing}, unexpected {unexpected}, repeated {repeated}")
return entries


def trellismx_files_present(root: str | Path) -> bool:
"""Whether every file ``root``'s release needs beside its carrier is present at its declared size.

Those files are the canonical inventory (one sidecar per allocated layer and rank)
and the encoder transform. True for a root that is not a release, which needs
nothing more; a manifest whose file list is not that inventory raises
``ValueError``. Only sizes are compared; preparation verifies each sidecar's
digest when it loads it.
"""
root = Path(root).expanduser()
manifest = _manifest(root)
if manifest is None:
return True
return _carrier_index(root).is_file() and (root / TRANSFORM_PATH).is_file() and all(
(root / path).is_file() and (root / path).stat().st_size == size for path, size in _manifest_files(manifest))


def trellismx_stored_bytes(root: str | Path) -> int | None:
"""Stored tensor bytes of ``root``'s release: the carrier's payload plus every manifest file.

None for a root that is not a release.
"""
root = Path(root).expanduser()
manifest = _manifest(root)
if manifest is None:
return None
try:
index = json.loads(_carrier_index(root).read_text())
except OSError as error:
raise ValueError(f"{_carrier_index(root)} is unreadable: {error}") from error
carrier = index.get("metadata", {}).get("total_size") if isinstance(index, dict) else None
if type(carrier) is not int or carrier <= 0:
raise ValueError(f"{root / CARRIER_DIRECTORY} has an invalid safetensors total_size: {carrier!r}")
return carrier + sum(size for _, size in _manifest_files(manifest))


__all__ = [
"CARRIER_DIRECTORY", "MANIFEST_NAME", "MANIFEST_SCHEMA", "SIDECAR_DIRECTORY", "TRANSFORM_PATH", "overlay_schema",
"sidecar_path", "trellismx_assets_root", "trellismx_files_present", "trellismx_stored_bytes",
]
44 changes: 40 additions & 4 deletions ignition/recipes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ def _csf_stored_bytes(container, snapshot):
def _snapshot_cached(snapshot: Path) -> bool:
"""Report whether the index and every tensor file it names are present."""
try:
from ignition.preparation.trellismx_release import trellismx_assets_root, trellismx_files_present

# A TrellisMX release overlays a carrier: its index and tokenizer live under carrier/, and
# it is complete only with every sidecar its manifest names.
if not trellismx_files_present(snapshot):
return False
snapshot = trellismx_assets_root(snapshot)
if _csf_container(snapshot) is not None:
from ignition.preparation.checkpoint import inspect_indexed_checkpoint

Expand Down Expand Up @@ -116,6 +123,18 @@ def snapshot(self, hub_cache: Path) -> Path:
def verify(self, hub_cache: Path) -> None:
"""Check index metadata; recipes validate tensor storage and geometry."""
snapshot = self.snapshot(hub_cache)
from ignition.preparation.trellismx_release import (overlay_schema, trellismx_files_present,
trellismx_stored_bytes)

try:
if overlay_schema(snapshot) is not None:
# An overlay release keeps its index under carrier/ and needs every file its manifest names.
trellismx_stored_bytes(snapshot)
if not trellismx_files_present(snapshot):
raise UsageError(f"{snapshot} lacks files its TrellisMX release manifest names")
return
except ValueError as error:
raise UsageError(f"{snapshot} has an unreadable TrellisMX release: {error}") from error
try:
container = _csf_container(snapshot)
if container is not None:
Expand Down Expand Up @@ -148,16 +167,23 @@ def _unsupported(reason: str) -> UsageError:
return UsageError(f"{reason}; supported models: {', '.join(aliases())}")


def _supporting(config_path: Path, source: str) -> tuple[Recipe, Checkpoint]:
def _supporting(config_path: Path, source: str, overlay: str | None = None) -> tuple[Recipe, Checkpoint]:
"""The first declared checkpoint of the named architecture packaged like ``source``.

A directory carrying an overlay manifest matches only checkpoints declaring that overlay,
and any other directory only checkpoints declaring none.
"""
try:
architectures = json.loads(config_path.read_text())["architectures"]
except (OSError, ValueError, KeyError, TypeError) as error:
raise _unsupported(f"{source} has no readable config.json architecture") from error
for recipe in recipes():
for checkpoint in recipe.checkpoints:
if isinstance(architectures, list) and checkpoint.architecture in architectures:
if (isinstance(architectures, list) and checkpoint.architecture in architectures
and checkpoint.overlay == (overlay or "")):
return recipe, checkpoint
raise _unsupported(f"no recipe supports architecture {architectures!r} in {source}")
packaging = f" packaged as {overlay}" if overlay else ""
raise _unsupported(f"no recipe supports architecture {architectures!r}{packaging} in {source}")


def _stored_bytes(index_path: Path, source: str) -> int:
Expand Down Expand Up @@ -215,12 +241,22 @@ def resolve(model: str, hub_cache: Path | None = None) -> ResolvedModel:
# A directory that a recipe declares keeps its declared placement.
if Path(checkpoint.repository).is_absolute() and Path(checkpoint.repository).resolve() == directory:
return ResolvedModel(recipe, checkpoint, directory)
recipe, qualified = _supporting(metadata / "config.json", str(directory))
from ignition.preparation.trellismx_release import overlay_schema, trellismx_stored_bytes

# An overlay release is placed like the checkpoint declared with its packaging, and stores
# its carrier's payload plus every file its manifest names.
try:
overlay = overlay_schema(directory)
overlay_stored = None if overlay is None else trellismx_stored_bytes(directory)
except ValueError as error:
raise UsageError(f"{directory} has an unreadable release manifest: {error}") from error
recipe, qualified = _supporting(metadata / "config.json", str(directory), overlay)
# The directory's path identifies its measurements on this host.
revision = hashlib.sha256(str(directory).encode()).hexdigest()[:40]
index = directory / "model.safetensors.index.json"
single = directory / "model.safetensors"
stored = (_csf_stored_bytes(container, directory) if container is not None else
overlay_stored if overlay_stored is not None else
_stored_bytes(index, str(directory)) if index.exists() else
single.stat().st_size if single.is_file() else qualified.stored_bytes)
return ResolvedModel(recipe, _replacement(qualified, str(directory), revision, stored), directory)
Expand Down
16 changes: 16 additions & 0 deletions ignition/recipes/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ class Checkpoint:
# contract without the value, and dashboards then show no weight bandwidth.
decode_weight_bytes: int = 0
plain_ranks: Mapping[int, "RankCosts"] | None = None
# The tensor-parallel sizes that can load this checkpoint when its storage fixes them, such
# as weights published as per-rank shards; empty means every size its recipe declares.
tensor_parallel: tuple[int, ...] = ()
# The schema of the overlay manifest this checkpoint is published with, when its storage
# overlays a carrier checkpoint. A local directory resolves to a checkpoint declaring the
# overlay its manifest names, and a directory without one to a checkpoint declaring none.
overlay: str = ""


@dataclass(frozen=True)
Expand Down Expand Up @@ -256,11 +263,20 @@ def __post_init__(self):
if any(costs is not None and set(costs) != set(self.ranks)
for costs in (checkpoint.ranks, checkpoint.plain_ranks)):
raise ValueError(f"{checkpoint.alias} declares costs for other tensor-parallel sizes than its recipe")
sizes = checkpoint.tensor_parallel
if (not isinstance(sizes, tuple) or any(type(size) is not int for size in sizes)
or list(sizes) != sorted(set(sizes)) or not set(sizes) <= set(self.ranks)):
raise ValueError(f"{checkpoint.alias} must declare its tensor-parallel sizes as an increasing tuple"
" of sizes its recipe declares")

@property
def tensor_parallel(self) -> tuple[int, ...]:
return tuple(sorted(self.ranks))

def checkpoint_tensor_parallel(self, checkpoint: Checkpoint) -> tuple[int, ...]:
"""The tensor-parallel sizes that can load `checkpoint`."""
return checkpoint.tensor_parallel or self.tensor_parallel

def costs(self, world_size: int, speculation: Speculation | None, checkpoint: Checkpoint | None = None) -> RankCosts:
"""Return one rank's costs under the given speculation settings, for `checkpoint` when it declares its own."""
ranks = self.ranks if checkpoint is None or checkpoint.ranks is None else checkpoint.ranks
Expand Down
Loading