Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/miner_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ Validators score your weights under the arena's own prompt, and provenance fails
2. Harness: set the adapter prompt, the verifier and retries, measure again.
3. Distil: frontier answers with reasoning on schemas and question shapes like the arena's, filtered by the grader, fine tuned from the base (LoRA or full) and recorded under `training/`.
4. Reinforce: reward a query that returns the reference result on every database state; or preference pairs of a right teacher answer against a wrong draft.
5. Check: hold out whole domains, rename and revalue as the twin rule does, measure latency and memory inside your arena's envelope group (https://www.microtensor.cloud/docs/certified-inference/system/envelope-groups), train the router on the result.
5. Check: hold out whole domains, rename and revalue as the twin rule does, measure latency and memory inside your arena's envelope group with `mt envelope check --arena <arena> --slm ./slm_repo --items <dev items>` (https://www.microtensor.cloud/docs/certified-inference/system/envelope-groups), train the router on the result.

Items are fresh every epoch and twins deduct memorisation, so train the skill, not the sample databases. Full page: https://www.microtensor.cloud/docs/certified-inference/guides/training
3 changes: 2 additions & 1 deletion microtensor_subnet/artifact/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@
"tokenizer.json",
"tokenizer_config.json",
)
SLM_GGUF_REQUIRED_FILES: Final[tuple[str, ...]] = ("README.md", "LICENSE")
SLM_TRAINING_FILES: Final[tuple[str, ...]] = (
"training/recipe.json",
"training/data/sources.json",
)
SLM_METRICS_GLOB: Final[str] = "training/runs/*/metrics.jsonl"
SLM_MIN_PARAMETERS: Final[int] = 1_000_000_000
SLM_MIN_PARAMETERS: Final[int] = 100_000_000
SLM_MAX_PARAMETERS: Final[int] = 8_000_000_000
ROUTER_MAX_PARAMETERS: Final[int] = 1_500_000_000
ROUTER_CPU_PARAMETERS: Final[int] = 100_000_000
Expand Down
206 changes: 206 additions & 0 deletions microtensor_subnet/artifact/gguf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
from __future__ import annotations

import math
import struct
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, BinaryIO, Final

MAGIC: Final[bytes] = b"GGUF"
KV_BYTES: Final[int] = 2
SCALARS: Final[dict[int, str]] = {
0: "<B",
1: "<b",
2: "<H",
3: "<h",
4: "<I",
5: "<i",
6: "<f",
7: "<?",
10: "<Q",
11: "<q",
12: "<d",
}
STRING: Final[int] = 8
ARRAY: Final[int] = 9
TYPE_NAMES: Final[dict[int, str]] = {
0: "F32",
1: "F16",
2: "Q4_0",
3: "Q4_1",
6: "Q5_0",
7: "Q5_1",
8: "Q8_0",
9: "Q8_1",
10: "Q2_K",
11: "Q3_K",
12: "Q4_K",
13: "Q5_K",
14: "Q6_K",
15: "Q8_K",
16: "IQ2_XXS",
17: "IQ2_XS",
18: "IQ3_XXS",
19: "IQ1_S",
20: "IQ4_NL",
21: "IQ3_S",
22: "IQ2_S",
23: "IQ4_XS",
24: "I8",
25: "I16",
26: "I32",
27: "I64",
28: "F64",
29: "IQ1_M",
30: "BF16",
34: "TQ1_0",
35: "TQ2_0",
}
BELOW_FOUR_BITS: Final[frozenset[str]] = frozenset(
{
"Q2_K",
"Q3_K",
"IQ2_XXS",
"IQ2_XS",
"IQ3_XXS",
"IQ1_S",
"IQ3_S",
"IQ2_S",
"IQ1_M",
"TQ1_0",
"TQ2_0",
}
)
FILE_TYPES: Final[dict[int, str]] = {
0: "F32",
1: "F16",
2: "Q4_0",
3: "Q4_1",
7: "Q8_0",
8: "Q5_0",
9: "Q5_1",
10: "Q2_K",
11: "Q3_K_S",
12: "Q3_K_M",
13: "Q3_K_L",
14: "Q4_K_S",
15: "Q4_K_M",
16: "Q5_K_S",
17: "Q5_K_M",
18: "Q6_K",
30: "IQ4_XS",
32: "BF16",
}


class GgufError(ValueError):
pass


@dataclass(frozen=True, slots=True)
class Tensor:
name: str
shape: tuple[int, ...]
kind: str

@property
def elements(self) -> int:
return math.prod(self.shape) if self.shape else 1


@dataclass(slots=True)
class Gguf:
version: int
metadata: dict[str, Any] = field(default_factory=dict)
tensors: list[Tensor] = field(default_factory=list)

@property
def parameters(self) -> int:
shapes = {t.name: t.shape for t in self.tensors}
tied = "output.weight" in shapes and shapes["output.weight"] == shapes.get(
"token_embd.weight"
)
return sum(t.elements for t in self.tensors if not (tied and t.name == "output.weight"))

@property
def file_type(self) -> str:
code = self.metadata.get("general.file_type")
return FILE_TYPES.get(int(code), str(code)) if code is not None else ""

@property
def below_four_bits(self) -> list[str]:
return [t.name for t in self.tensors if t.kind in BELOW_FOUR_BITS]

def number(self, suffix: str) -> int:
arch = str(self.metadata.get("general.architecture") or "")
value = self.metadata.get(f"{arch}.{suffix}")
return int(value) if isinstance(value, (int, float)) else 0

def kv_cache_bytes(self, max_len: int) -> int:
layers = self.number("block_count")
heads = self.number("attention.head_count")
kv_heads = self.number("attention.head_count_kv") or heads
width = self.number("embedding_length")
head_dim = self.number("attention.key_length") or (width // heads if heads else 0)
return 2 * layers * kv_heads * head_dim * max_len * KV_BYTES


def _unpack(handle: BinaryIO, fmt: str) -> Any:
size = struct.calcsize(fmt)
raw = handle.read(size)
if len(raw) != size:
raise GgufError("the GGUF header ends early")
return struct.unpack(fmt, raw)[0]


def _string(handle: BinaryIO) -> str:
length = _unpack(handle, "<Q")
raw = handle.read(length)
if len(raw) != length:
raise GgufError("the GGUF header ends early")
return raw.decode("utf-8", "replace")


def _value(handle: BinaryIO, kind: int) -> Any:
if kind in SCALARS:
return _unpack(handle, SCALARS[kind])
if kind == STRING:
return _string(handle)
if kind == ARRAY:
inner = _unpack(handle, "<I")
count = _unpack(handle, "<Q")
if inner in SCALARS:
size = struct.calcsize(SCALARS[inner])
handle.seek(size * count, 1)
return count
for _ in range(count):
_value(handle, inner)
return count
raise GgufError(f"unknown GGUF value type {kind}")


def read(path: Path) -> Gguf:
with path.open("rb") as handle:
if handle.read(4) != MAGIC:
raise GgufError(f"{path.name} is not a GGUF file")
version = _unpack(handle, "<I")
if version < 2:
raise GgufError(f"{path.name} is GGUF version {version}; 2 or later is required")
tensors = _unpack(handle, "<Q")
pairs = _unpack(handle, "<Q")
found = Gguf(version=version)
for _ in range(pairs):
key = _string(handle)
found.metadata[key] = _value(handle, _unpack(handle, "<I"))
for _ in range(tensors):
name = _string(handle)
dims = _unpack(handle, "<I")
shape = tuple(_unpack(handle, "<Q") for _ in range(dims))
kind = _unpack(handle, "<I")
_unpack(handle, "<Q")
found.tensors.append(Tensor(name, shape, TYPE_NAMES.get(kind, f"type{kind}")))
return found


def gguf_in(root: Path) -> list[Path]:
return sorted(p for p in root.glob("*.gguf") if p.is_file())
28 changes: 26 additions & 2 deletions microtensor_subnet/artifact/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from pathlib import Path
from typing import Any

from microtensor_subnet.artifact import gguf

HEADER_PREFIX_BYTES = 8
QUANT_SUFFIXES = (".qweight", ".qzeros", ".scales", ".g_idx")
INT4_PACK = 8
Expand Down Expand Up @@ -92,6 +94,28 @@ def kv_cache_bytes(root: Path, max_len: int) -> int:
return 2 * layers * kv_heads * head_dim * max_len * KV_BYTES


def model_parameters(root: Path) -> int:
weights = safetensors_in(root)
if weights:
return count_parameters(weights)
found = gguf.gguf_in(root)
if len(found) != 1:
raise ParameterError("the model needs safetensors weights or exactly one GGUF file")
try:
return gguf.read(found[0]).parameters
except (OSError, gguf.GgufError) as exc:
raise ParameterError(str(exc)) from exc


def footprint_bytes(root: Path, max_len: int) -> int:
weights = sum(path.stat().st_size for path in safetensors_in(root))
return weights + kv_cache_bytes(root, max_len)
weights = safetensors_in(root)
if weights:
return sum(path.stat().st_size for path in weights) + kv_cache_bytes(root, max_len)
found = gguf.gguf_in(root)
if not found:
raise ParameterError(f"no weights in {root.name}")
try:
header = gguf.read(found[0])
except (OSError, gguf.GgufError) as exc:
raise ParameterError(str(exc)) from exc
return found[0].stat().st_size + header.kv_cache_bytes(max_len)
54 changes: 43 additions & 11 deletions microtensor_subnet/artifact/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
from pathlib import Path
from typing import Any

from microtensor_subnet.artifact import contract
from microtensor_subnet.artifact.params import ParameterError, count_parameters, safetensors_in
from microtensor_subnet.artifact import contract, gguf
from microtensor_subnet.artifact.params import (
ParameterError,
count_parameters,
model_parameters,
safetensors_in,
)

_SHA = re.compile(r"^(sha256:)?[0-9a-f]{64}$")
_GIT = re.compile(r"^[0-9a-f]{40}$")
Expand Down Expand Up @@ -275,31 +280,44 @@ def _looks_secret(path: Path) -> bool:
return any(marker in head for marker in _SECRET_MARKERS)


def check_slm_repo(root: Path, pin: SlmPin | None, allowed_bases: Iterable[str] = ()) -> Findings:
def check_slm_repo(
root: Path,
pin: SlmPin | None,
allowed_bases: Iterable[str] = (),
*,
bounds: tuple[int, int] | None = None,
formats: Iterable[str] = (),
) -> Findings:
findings = Findings()
present = set(tree_files(root))
for name in contract.SLM_REQUIRED_FILES:
weights = safetensors_in(root)
ggufs = gguf.gguf_in(root)
required = (
contract.SLM_GGUF_REQUIRED_FILES if ggufs and not weights else contract.SLM_REQUIRED_FILES
)
for name in required:
if name not in present:
findings.problems.append(f"{name} is missing from the slm repository")
for name in contract.SLM_TRAINING_FILES:
if name not in present:
findings.problems.append(f"{name} is required for the archive")
if not list(root.glob(contract.SLM_METRICS_GLOB)):
findings.problems.append("training/runs/<id>/metrics.jsonl is required for the archive")
weights = safetensors_in(root)
if not weights:
findings.problems.append("no model.safetensors (or shards) in the slm repository root")
low, high = bounds or (contract.SLM_MIN_PARAMETERS, contract.SLM_MAX_PARAMETERS)
if not weights and not ggufs:
findings.problems.append("no model.safetensors (or shards) or model.gguf in the root")
else:
try:
parameters = count_parameters(weights)
parameters = model_parameters(root)
findings.notes.append(f"slm parameters {parameters}")
if not contract.SLM_MIN_PARAMETERS <= parameters <= contract.SLM_MAX_PARAMETERS:
if not low <= parameters <= high:
findings.problems.append(
f"slm has {parameters} parameters; the arena allows "
f"{contract.SLM_MIN_PARAMETERS} to {contract.SLM_MAX_PARAMETERS}"
f"slm has {parameters} parameters; the arena allows {low} to {high}"
)
except ParameterError as exc:
findings.problems.append(str(exc))
if ggufs and not weights:
findings.problems += _gguf_problems(ggufs[0], [f.upper() for f in formats])
if pin is not None:
for name, digest in pin.files.items():
if name not in present:
Expand All @@ -318,6 +336,20 @@ def check_slm_repo(root: Path, pin: SlmPin | None, allowed_bases: Iterable[str]
return findings


def _gguf_problems(path: Path, formats: list[str]) -> list[str]:
try:
header = gguf.read(path)
except (OSError, gguf.GgufError) as exc:
return [str(exc)]
problems: list[str] = []
low = header.below_four_bits
if low:
problems.append(f"{len(low)} tensors are below 4 bit, for example {low[0]}")
if formats and header.file_type.upper() not in formats:
problems.append(f"GGUF type {header.file_type or 'unknown'} is not one of {formats}")
return problems


def build_manifest(
root: Path,
*,
Expand Down
Loading
Loading