mirror of https://github.com/razor-ai/soup.git
174 lines
6.1 KiB
Python
174 lines
6.1 KiB
Python
"""v0.52.0 Part D — BitNet 1.58-bit fine-tuning + export schema helpers.
|
|
|
|
Schema-only support for ``quantization='bitnet_1.58'`` and the new
|
|
``soup export --format bitnet`` / ``--format tq1_0`` GGUF flavours.
|
|
|
|
Live ``onebitllms`` wrapping + llama.cpp ``TQ1_0`` export wiring are
|
|
deferred to v0.52.1 (mirrors v0.50.0 stub-then-live pattern).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from types import MappingProxyType
|
|
from typing import Mapping
|
|
|
|
# Closed allowlist of BitNet-flavoured quant strings exposed to YAML.
|
|
BITNET_QUANT_FORMATS: frozenset[str] = frozenset({"bitnet_1.58"})
|
|
|
|
# Closed allowlist of BitNet export targets (the actual export formats).
|
|
BITNET_EXPORT_FORMATS: frozenset[str] = frozenset({"bitnet", "tq1_0"})
|
|
|
|
_BITNET_FAMILY_RE_PREFIXES: tuple[str, ...] = (
|
|
"bitnet", "falcon-e", "falcone", "1bitllm", "onebit",
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BitNetSpec:
|
|
"""Metadata for the BitNet quant path. Frozen — immutable."""
|
|
|
|
name: str
|
|
description: str
|
|
bits: float
|
|
live_wired: bool
|
|
|
|
|
|
_BITNET_METADATA: Mapping[str, BitNetSpec] = MappingProxyType({
|
|
"bitnet_1.58": BitNetSpec(
|
|
name="bitnet_1.58",
|
|
description="BitNet 1.58-bit ternary weights (axolotl + onebitllms)",
|
|
bits=1.58,
|
|
live_wired=False,
|
|
),
|
|
})
|
|
|
|
|
|
def is_bitnet_quant(value: object) -> bool:
|
|
"""Return True iff ``value`` is a BitNet quant string."""
|
|
if isinstance(value, bool):
|
|
return False
|
|
if not isinstance(value, str):
|
|
return False
|
|
return value in BITNET_QUANT_FORMATS
|
|
|
|
|
|
def is_bitnet_export_format(value: object) -> bool:
|
|
"""Return True iff ``value`` is a BitNet export-format string."""
|
|
if isinstance(value, bool):
|
|
return False
|
|
if not isinstance(value, str):
|
|
return False
|
|
return value in BITNET_EXPORT_FORMATS
|
|
|
|
|
|
def get_bitnet_spec(name: str) -> BitNetSpec:
|
|
"""Return the frozen :class:`BitNetSpec` for ``name`` or raise."""
|
|
if not is_bitnet_quant(name):
|
|
supported = ", ".join(sorted(BITNET_QUANT_FORMATS))
|
|
raise ValueError(
|
|
f"BitNet quant {name!r} not supported. Supported: {supported}"
|
|
)
|
|
return _BITNET_METADATA[name]
|
|
|
|
|
|
def is_bitnet_model(model_name: object) -> bool:
|
|
"""Best-effort detect whether ``model_name`` references a BitNet family.
|
|
|
|
Checks every slash-delimited component (lowercased) against the
|
|
``_BITNET_FAMILY_RE_PREFIXES`` prefix list. This is intentionally more
|
|
permissive than v0.39.0 ``is_gemma4_model`` because BitNet families
|
|
typically live under namespaced orgs (``1bitllm/...``, ``OneBitLLM/...``)
|
|
rather than being identifiable by repo name alone.
|
|
"""
|
|
if isinstance(model_name, bool):
|
|
return False
|
|
if not isinstance(model_name, str):
|
|
return False
|
|
if not model_name or "\x00" in model_name:
|
|
return False
|
|
# Check each path component so an org name like "1bitllm/foo" matches
|
|
# while still rejecting unrelated substrings (e.g. an SFT model that
|
|
# happens to embed "bitnet" inside a description path).
|
|
for part in model_name.lower().split("/"):
|
|
if any(part.startswith(prefix) for prefix in _BITNET_FAMILY_RE_PREFIXES):
|
|
return True
|
|
return False
|
|
|
|
|
|
def validate_bitnet_compat(*, task: str, backend: str, modality: str) -> None:
|
|
"""Schema-time gate for ``quantization='bitnet_1.58'``.
|
|
|
|
Rejects:
|
|
- non-string / bool args (defence-in-depth).
|
|
- ``backend == 'mlx'`` — onebitllms is CUDA-only in v0.52.0.
|
|
- ``modality != 'text'`` — vision/audio BitNet not modelled.
|
|
- ``task`` outside {sft, pretrain, dpo} — BitNet wiring is text-LM
|
|
training only this release.
|
|
"""
|
|
for name, value in (("task", task), ("backend", backend), ("modality", modality)):
|
|
if isinstance(value, bool):
|
|
raise TypeError(f"{name} must not be bool, got {value!r}")
|
|
if not isinstance(value, str) or not value:
|
|
raise ValueError(f"{name} must be a non-empty string")
|
|
if backend == "mlx":
|
|
raise ValueError(
|
|
"quantization='bitnet_1.58' is not supported on backend=mlx "
|
|
"(onebitllms is CUDA-only). Use backend='transformers'."
|
|
)
|
|
if modality != "text":
|
|
raise ValueError(
|
|
f"quantization='bitnet_1.58' is wired for modality='text' only; "
|
|
f"got modality={modality!r}"
|
|
)
|
|
if task not in ("sft", "pretrain", "dpo"):
|
|
raise ValueError(
|
|
f"quantization='bitnet_1.58' is only wired for "
|
|
f"task in (sft, pretrain, dpo); got task={task!r}"
|
|
)
|
|
|
|
|
|
def validate_bitnet_export(format_name: object) -> str:
|
|
"""Validate a BitNet export-format string. Returns canonical form."""
|
|
if isinstance(format_name, bool):
|
|
raise TypeError(
|
|
f"bitnet export format must not be bool, got {format_name!r}"
|
|
)
|
|
if not isinstance(format_name, str):
|
|
raise TypeError(
|
|
f"bitnet export format must be str, "
|
|
f"got {type(format_name).__name__}"
|
|
)
|
|
if not format_name:
|
|
raise ValueError("bitnet export format must be non-empty")
|
|
if "\x00" in format_name:
|
|
raise ValueError(
|
|
"bitnet export format must not contain null bytes"
|
|
)
|
|
canonical = format_name.lower()
|
|
if canonical not in BITNET_EXPORT_FORMATS:
|
|
supported = ", ".join(sorted(BITNET_EXPORT_FORMATS))
|
|
raise ValueError(
|
|
f"bitnet export format {format_name!r} not supported. "
|
|
f"Supported: {supported}"
|
|
)
|
|
return canonical
|
|
|
|
|
|
def build_bitnet_trainer() -> None:
|
|
"""Live BitNet trainer factory — deferred to v0.52.1."""
|
|
raise NotImplementedError(
|
|
"BitNet 1.58-bit fine-tuning live wiring deferred to v0.52.1. "
|
|
"Schema accepts quantization='bitnet_1.58' but no trainer integration "
|
|
"is registered yet."
|
|
)
|
|
|
|
|
|
def export_bitnet_gguf() -> None:
|
|
"""Live BitNet GGUF export — deferred to v0.52.1."""
|
|
raise NotImplementedError(
|
|
"BitNet / TQ1_0 GGUF export live wiring deferred to v0.52.1. "
|
|
"Schema accepts --format bitnet / --format tq1_0 but no export "
|
|
"pipeline is registered yet."
|
|
)
|