mirror of
https://github.com/kvcache-ai/ktransformers.git
synced 2026-10-17 05:11:19 +08:00
a5d7ad9047
* feat(sft): add native MXFP4 routed-expert LoRA kernels * test(sft): validate DeepSeek V4 MXFP4 routed experts * feat(sft): AVX2 tier for MXFP4 routed-expert LoRA SFT MXFP4_SFT_MOE on non-AVX512 builds, same Python contract as the AMX / AVX512-BF16 kernel from #2185 (KTMoEWrapper(method="MXFP4_SFT")), for AVX2+FMA+F16C hosts (Zen 2/3 EPYC, older Xeons). * operators/avx2/sft_mxfp4-moe.hpp: AVX2_SFT_MXFP4_MOE_TP part on top of AVX2_MXFP4_MOE_TP (frozen packed E2M1/UE8M0 base, LoRA gate/up/down, V4 swiglu clamp, transpose-free packed-weight dX, authoritative LoRA grads). * operators/moe-sft-tp.hpp: AVX2 result merge; AVX-512 merge loops and the block-FP8 staging guarded so the wrapper builds without AVX-512. * ext_bindings.cpp: bind MXFP4_SFT_MOE -> TP_MOE_SFT<AVX2_SFT_MXFP4_MOE_TP> when USE_AMX_AVX_KERNEL is off; MOESFTBindings no longer AMX-only. * python/sft/backend.py: runtime reports cpu_variant=avx2, kernel avx2-mxfp4-g32. * test_mxfp4_sft_numerical.py: inference regression uses AVX2MXFP4_MOE when AMXFP4_KGroup_MOE is absent. Validated on 2x EPYC 7452 (AVX2, no AVX-512): test_mxfp4_sft_numerical.py TP1/TP2 all stages rel-L2 <= 0.4% (limits 3-5%); validate_dsv4_mxfp4_sft_layer.py on DeepSeek-V4-Flash-0731 layer 3, TP1 and TP2: passed, forward/grad_input/ LoRA grads rel-L2 0.10-0.29%, base weights unchanged. Note: the build uses -ffast-math, so exp() past the float range is undefined; the scalar sigmoid tails clamp the argument like the vector path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Mgsduzz2fx1gSvfHbR8RZ * fix(sft): clamp MXFP4 scalar-tail sigmoid exponent * feat(sft): integrate native V4 experts with LoRA training and export Wrap only routed experts while preserving V4 routing and shared branches. Validate native checkpoint provenance and stream a BF16 non-expert cache without dequantizing routed experts. Add audited static SGLang export and reproducible training, resume and generation acceptance helpers. CPU retain/GPU recompute GAS1/GAS4 smoke and 265 KT regression tests pass; long-run quality and resume acceptance remain in progress. * fix(sft): retain async inference buffers until CUDA work completes * test(sft): isolate V4 adapter effects with matched-kernel controls Allow baseline and non-expert-only exports to retain a zero-B expert adapter so all ablations execute the same native forward kernel. Add paired held-out generation reporting with explicitly diagnostic lexical metrics. * test(sft): compare PEFT targets semantically during V4 resume audits * test(sft): cancel queued V4 inference checks after a failed request * test(sft): preserve raw V4 responses before finite-output validation * feat: prepare V4 cache atomically and package static adapter export * build: preserve accepted CUDA carriers across Python ABIs * test: retain FP8 loading contracts after V4 wrapper integration * test: exclude historical concatenated-sample smoke from delivery * test(sft): audit unpacked batches and single-GPU resume state * docs(sft): align April V4 recipe with approved unpacked delivery * test: keep frozen-parameter audit indices exact for large tensors * test: record the loaded native backend in acceptance evidence * test: timestamp training audit events for startup measurements * docs: pin V4 serving dependencies and account for reserved token slots * [refactor](sft): keep V4 follow-up focused on runtime integration --------- Co-authored-by: 05yuki <ceu.azul.samu@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2078 lines
89 KiB
Python
2078 lines
89 KiB
Python
# KT SFT artifact contracts
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Validated, framework-neutral artifact interfaces for KT SFT.
|
|
|
|
Transformers owns model construction and Trainer sequencing. This module owns
|
|
the on-disk KT formats and returns immutable load plans which Transformers can
|
|
apply without knowing the manifest schema.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import hashlib
|
|
import json
|
|
import math
|
|
import os
|
|
import re
|
|
import shutil
|
|
import tempfile
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Iterator, Mapping
|
|
|
|
from safetensors import safe_open
|
|
|
|
from .backend import RAWINT4_WEIGHT_LAYOUT, get_rawint4_checkpoint_contract
|
|
from .weight_manifest import validate_persistent_int8_weights
|
|
|
|
|
|
KT_NON_EXPERT_MANIFEST_NAME = "kt_non_expert_manifest.json"
|
|
KT_NON_EXPERT_INDEX_NAME = "model.safetensors.index.json"
|
|
KT_ADAPTER_MANIFEST_NAME = "kt_adapter_manifest.json"
|
|
FUSED_EXPERT_LORA_NAME = "fused_expert_lora.safetensors"
|
|
KT_NON_EXPERT_MANIFEST_VERSION = 2
|
|
KT_ADAPTER_MANIFEST_VERSION = 1
|
|
|
|
_LEGACY_NON_EXPERT_VERSION = 1
|
|
_LEGACY_NON_EXPERT_PRODUCER = "llamafactory.prepare-kt-cache"
|
|
_NON_EXPERT_PRODUCER = "kt-kernel.prepare-non-expert-cache"
|
|
_STANDARD_ADAPTER_NAMES = ("adapter_model.safetensors", "adapter_model.bin")
|
|
_FUSED_LORA_NAMES = (
|
|
"gate_lora_a",
|
|
"gate_lora_b",
|
|
"up_lora_a",
|
|
"up_lora_b",
|
|
"down_lora_a",
|
|
"down_lora_b",
|
|
)
|
|
_FP32_ROUTER_BIAS = re.compile(r"^model\.layers\.\d+\.mlp\.gate\.e_score_correction_bias$")
|
|
_ROUTED_EXPERT = re.compile(r"(?:^|\.)experts(?:\.|$)")
|
|
_ROUTED_EXPERT_PARAMETER = re.compile(
|
|
r"\.experts\.(?:\d+\.|gate_up_proj(?:\.|$)|down_proj(?:\.|$)|gate_proj(?:\.|$)|up_proj(?:\.|$))"
|
|
)
|
|
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
_PARAMETER_MARKER = "_is_kt_int8_routed_expert_base_parameter"
|
|
_MODULE_MARKER = "_is_kt_int8_routed_expert_base_module"
|
|
_MODULE_PATHS = "_kt_int8_routed_expert_module_paths"
|
|
_RUNTIME_PARAMETER_MARKER = "_is_kt_routed_expert_runtime_parameter"
|
|
_RUNTIME_MODULE_MARKER = "_is_kt_routed_expert_runtime_module"
|
|
_RUNTIME_MODULE_PATHS = "_kt_routed_expert_runtime_module_paths"
|
|
_RUNTIME_MODULE_REFS = "_kt_routed_expert_runtime_module_refs"
|
|
_RUNTIME_TENSOR_CONTRACTS = "_kt_routed_expert_runtime_tensor_contracts"
|
|
_SUPPORTED_MOE_ARCHITECTURES = (
|
|
"DeepseekV2",
|
|
"DeepseekV3",
|
|
"DeepseekV4",
|
|
"KimiK2",
|
|
"Qwen2Moe",
|
|
"Qwen3Moe",
|
|
"Qwen3_5Moe",
|
|
"Glm4Moe",
|
|
"Mixtral",
|
|
)
|
|
_EXPERT_WEIGHT_FORMATS = frozenset({"bf16", "int8", "fp8", "rawint4", "mxfp4"})
|
|
_RAWINT4_BASE_IDENTITY_SCHEMA = "safetensors-sharded-sampled-v1"
|
|
_RAWINT4_SAMPLE_BYTES = 64 * 1024
|
|
_RAWINT4_SAMPLE_COUNT = 8
|
|
|
|
|
|
class KTArtifactError(RuntimeError):
|
|
"""A KT artifact is incomplete, unsafe, or incompatible with the runtime."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class KTNonExpertCache:
|
|
"""A completely validated BF16 non-expert cache."""
|
|
|
|
path: str
|
|
manifest_path: str
|
|
manifest: Mapping[str, Any]
|
|
checkpoint_files: tuple[str, ...]
|
|
weight_keys: frozenset[str]
|
|
source_config: Mapping[str, Any]
|
|
|
|
@property
|
|
def fingerprint(self) -> str:
|
|
return str(self.manifest["fingerprint"])
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class KTPretrainedLoadPlan:
|
|
"""Immutable instructions for replacing only a checkpoint's weight source."""
|
|
|
|
source_model_name_or_path: str
|
|
weight_path: str
|
|
checkpoint_files: tuple[str, ...]
|
|
weight_keys: frozenset[str]
|
|
manifest: Mapping[str, Any]
|
|
manifest_path: str
|
|
routed_weight_path: str
|
|
routed_manifest: Mapping[str, Any]
|
|
routed_manifest_path: str
|
|
lora_rank: int | None = None
|
|
lora_alpha: float | None = None
|
|
disable_source_quantizer: bool = True
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class KTAdapterManifest:
|
|
"""Validated combined standard-PEFT and KT adapter manifest."""
|
|
|
|
path: str
|
|
payload: Mapping[str, Any]
|
|
artifact_paths: tuple[str, ...]
|
|
|
|
|
|
def _sha256_file(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def _read_json(path: Path, description: str) -> dict[str, Any]:
|
|
if path.is_symlink() or not path.is_file():
|
|
raise KTArtifactError(f"{description} must be a regular file: {path}")
|
|
try:
|
|
with path.open(encoding="utf-8") as handle:
|
|
payload = json.load(handle)
|
|
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
raise KTArtifactError(f"could not read {description} {path}: {exc}") from exc
|
|
if not isinstance(payload, dict):
|
|
raise KTArtifactError(f"{description} must contain a JSON object: {path}")
|
|
return payload
|
|
|
|
|
|
def _require_string(value: Any, field: str, path: Path) -> str:
|
|
if not isinstance(value, str) or not value:
|
|
raise KTArtifactError(f"{path}: {field} must be a non-empty string")
|
|
return value
|
|
|
|
|
|
def _require_sha256(value: Any, field: str, path: Path) -> str:
|
|
value = _require_string(value, field, path)
|
|
if not _SHA256.fullmatch(value):
|
|
raise KTArtifactError(f"{path}: {field} must be a lowercase SHA256 digest")
|
|
return value
|
|
|
|
|
|
def _positive_int(value: Any, field: str, path: Path) -> int:
|
|
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
|
|
raise KTArtifactError(f"{path}: {field} must be a positive integer")
|
|
return int(value)
|
|
|
|
|
|
def _same_source(expected: str | os.PathLike[str], actual: str) -> bool:
|
|
expected_value = os.fspath(expected)
|
|
if os.path.exists(expected_value) or os.path.exists(actual):
|
|
return os.path.realpath(expected_value) == os.path.realpath(actual)
|
|
return expected_value == actual
|
|
|
|
|
|
def _cache_fingerprint(
|
|
version: int,
|
|
source_fingerprint: str,
|
|
file_records: list[dict[str, Any]],
|
|
tensor_count: int,
|
|
tensor_bytes: int,
|
|
dtype_counts: Mapping[str, int],
|
|
) -> str:
|
|
digest = hashlib.sha256()
|
|
digest.update(f"kt-non-expert-cache-v{version}\0".encode())
|
|
digest.update(source_fingerprint.encode())
|
|
digest.update(b"\0")
|
|
digest.update(str(tensor_count).encode())
|
|
digest.update(b"\0")
|
|
digest.update(str(tensor_bytes).encode())
|
|
digest.update(b"\0")
|
|
digest.update(json.dumps(dict(dtype_counts), sort_keys=True, separators=(",", ":")).encode())
|
|
digest.update(b"\0")
|
|
for record in sorted(file_records, key=lambda item: item["name"]):
|
|
digest.update(record["name"].encode())
|
|
digest.update(b"\0")
|
|
digest.update(str(record["size"]).encode())
|
|
digest.update(b"\0")
|
|
digest.update(record["sha256"].encode())
|
|
digest.update(b"\0")
|
|
return digest.hexdigest()
|
|
|
|
|
|
def _source_fingerprint(config_digest: str, index_digest: str) -> str:
|
|
digest = hashlib.sha256()
|
|
digest.update(b"kt-source-checkpoint-v2\0")
|
|
digest.update(config_digest.encode())
|
|
digest.update(b"\0")
|
|
digest.update(index_digest.encode())
|
|
return digest.hexdigest()
|
|
|
|
|
|
def _canonical_json_sha256(payload: Mapping[str, Any]) -> str:
|
|
return hashlib.sha256(
|
|
json.dumps(dict(payload), sort_keys=True, separators=(",", ":")).encode()
|
|
).hexdigest()
|
|
|
|
|
|
def _rawint4_shard_record(path: Path) -> dict[str, Any]:
|
|
"""Build a portable sampled identity plus a local replacement guard.
|
|
|
|
Hashing every byte of a Kimi checkpoint would stream roughly 595 GB each
|
|
time an adapter is saved or loaded. The portable identity instead covers
|
|
the safetensors header and evenly spaced payload windows. For the original
|
|
path, device/inode/ctime additionally makes an in-place shard replacement
|
|
fail closed even when it avoids those sampled windows.
|
|
"""
|
|
|
|
if not path.is_file():
|
|
raise KTArtifactError(f"RAWINT4 base shard must be a readable file: {path}")
|
|
with path.open("rb") as handle:
|
|
stat = os.fstat(handle.fileno())
|
|
size = stat.st_size
|
|
prefix = handle.read(8)
|
|
if len(prefix) != 8:
|
|
raise KTArtifactError(f"RAWINT4 base shard has no safetensors header: {path}")
|
|
header_size = int.from_bytes(prefix, byteorder="little", signed=False)
|
|
if header_size <= 0 or header_size > size - 8:
|
|
raise KTArtifactError(f"RAWINT4 base shard has an invalid safetensors header: {path}")
|
|
header = handle.read(header_size)
|
|
if len(header) != header_size:
|
|
raise KTArtifactError(f"RAWINT4 base shard has a truncated safetensors header: {path}")
|
|
|
|
payload_start = 8 + header_size
|
|
payload_size = size - payload_start
|
|
window_size = min(_RAWINT4_SAMPLE_BYTES, payload_size)
|
|
offsets = {payload_start}
|
|
if payload_size > window_size:
|
|
span = payload_size - window_size
|
|
offsets.update(
|
|
payload_start + (span * sample_idx // (_RAWINT4_SAMPLE_COUNT - 1))
|
|
for sample_idx in range(_RAWINT4_SAMPLE_COUNT)
|
|
)
|
|
sample_digest = hashlib.sha256()
|
|
for offset in sorted(offsets):
|
|
handle.seek(offset)
|
|
chunk = handle.read(window_size)
|
|
if len(chunk) != window_size:
|
|
raise KTArtifactError(f"RAWINT4 base shard changed while fingerprinting: {path}")
|
|
sample_digest.update(str(offset - payload_start).encode())
|
|
sample_digest.update(b"\0")
|
|
sample_digest.update(chunk)
|
|
final_stat = os.fstat(handle.fileno())
|
|
|
|
identity_fields = ("st_dev", "st_ino", "st_ctime_ns", "st_size")
|
|
if any(getattr(stat, field) != getattr(final_stat, field) for field in identity_fields):
|
|
raise KTArtifactError(f"RAWINT4 base shard changed while fingerprinting: {path}")
|
|
path_stat = path.stat()
|
|
if any(getattr(stat, field) != getattr(path_stat, field) for field in identity_fields):
|
|
raise KTArtifactError(f"RAWINT4 base shard was replaced while fingerprinting: {path}")
|
|
|
|
return {
|
|
"name": path.name,
|
|
"size": size,
|
|
"header_sha256": hashlib.sha256(header).hexdigest(),
|
|
"sample_sha256": sample_digest.hexdigest(),
|
|
"file_identity": {
|
|
"device": stat.st_dev,
|
|
"inode": stat.st_ino,
|
|
"ctime_ns": stat.st_ctime_ns,
|
|
},
|
|
}
|
|
|
|
|
|
def _rawint4_base_fingerprint(
|
|
config_sha256: str,
|
|
index_sha256: str,
|
|
shards: list[Mapping[str, Any]],
|
|
) -> str:
|
|
digest = hashlib.sha256()
|
|
digest.update(b"kt-rawint4-base-safetensors-v1\0")
|
|
digest.update(config_sha256.encode())
|
|
digest.update(b"\0")
|
|
digest.update(index_sha256.encode())
|
|
digest.update(b"\0")
|
|
for record in sorted(shards, key=lambda item: str(item.get("name", ""))):
|
|
portable = {
|
|
"name": record.get("name"),
|
|
"size": record.get("size"),
|
|
"header_sha256": record.get("header_sha256"),
|
|
"sample_sha256": record.get("sample_sha256"),
|
|
}
|
|
digest.update(json.dumps(portable, sort_keys=True, separators=(",", ":")).encode())
|
|
digest.update(b"\0")
|
|
return digest.hexdigest()
|
|
|
|
|
|
def _rawint4_base_identity(model_name_or_path: str | os.PathLike[str]) -> dict[str, Any]:
|
|
root = _safe_root(model_name_or_path, "RAWINT4 base checkpoint")
|
|
config_path = root / "config.json"
|
|
index_path = root / KT_NON_EXPERT_INDEX_NAME
|
|
try:
|
|
resolved_config_path = config_path.resolve(strict=True)
|
|
resolved_index_path = index_path.resolve(strict=True)
|
|
except (OSError, RuntimeError) as exc:
|
|
raise KTArtifactError(f"could not resolve RAWINT4 base checkpoint metadata: {exc}") from exc
|
|
config = _read_json(resolved_config_path, "RAWINT4 base config")
|
|
index = _read_json(resolved_index_path, "RAWINT4 base safetensors index")
|
|
weight_map = index.get("weight_map")
|
|
if not isinstance(weight_map, dict) or not weight_map:
|
|
raise KTArtifactError(f"{index_path}: weight_map must be a non-empty object")
|
|
shard_names = set()
|
|
for key, shard_name in weight_map.items():
|
|
if not isinstance(key, str) or not key:
|
|
raise KTArtifactError(f"{index_path}: tensor names must be non-empty strings")
|
|
if (
|
|
not isinstance(shard_name, str)
|
|
or shard_name != os.path.basename(shard_name)
|
|
or not shard_name.endswith(".safetensors")
|
|
):
|
|
raise KTArtifactError(f"{index_path}: unsafe safetensors shard {shard_name!r}")
|
|
shard_names.add(shard_name)
|
|
config_digest = _canonical_json_sha256(config)
|
|
index_digest = _canonical_json_sha256(index)
|
|
shards = [_rawint4_shard_record(root / name) for name in sorted(shard_names)]
|
|
return {
|
|
"model_name_or_path": str(root),
|
|
"fingerprint_schema": _RAWINT4_BASE_IDENTITY_SCHEMA,
|
|
"fingerprint": _rawint4_base_fingerprint(config_digest, index_digest, shards),
|
|
"config_sha256": config_digest,
|
|
"index_sha256": index_digest,
|
|
"shards": shards,
|
|
}
|
|
|
|
|
|
def _validate_rawint4_base_identity(
|
|
saved: Any,
|
|
current: Any,
|
|
manifest_path: Path,
|
|
) -> None:
|
|
if not isinstance(saved, Mapping) or not isinstance(current, Mapping):
|
|
raise KTArtifactError(f"{manifest_path}: RAWINT4 base identity must be an object")
|
|
for label, value in (("saved", saved), ("runtime", current)):
|
|
if value.get("fingerprint_schema") != _RAWINT4_BASE_IDENTITY_SCHEMA:
|
|
raise KTArtifactError(f"{manifest_path}: unsupported {label} RAWINT4 base fingerprint schema")
|
|
fingerprint = _require_sha256(
|
|
value.get("fingerprint"), f"base.{label}.fingerprint", manifest_path
|
|
)
|
|
config_digest = _require_sha256(
|
|
value.get("config_sha256"), f"base.{label}.config_sha256", manifest_path
|
|
)
|
|
index_digest = _require_sha256(
|
|
value.get("index_sha256"), f"base.{label}.index_sha256", manifest_path
|
|
)
|
|
shards = value.get("shards")
|
|
if not isinstance(shards, list) or not shards:
|
|
raise KTArtifactError(f"{manifest_path}: base.{label}.shards must be non-empty")
|
|
names = set()
|
|
for shard_idx, record in enumerate(shards):
|
|
if not isinstance(record, Mapping):
|
|
raise KTArtifactError(
|
|
f"{manifest_path}: base.{label}.shards[{shard_idx}] must be an object"
|
|
)
|
|
name = _require_string(
|
|
record.get("name"), f"base.{label}.shards[{shard_idx}].name", manifest_path
|
|
)
|
|
if name != os.path.basename(name) or not name.endswith(".safetensors") or name in names:
|
|
raise KTArtifactError(f"{manifest_path}: invalid RAWINT4 base shard name {name!r}")
|
|
names.add(name)
|
|
_positive_int(record.get("size"), f"base.{label}.shards[{shard_idx}].size", manifest_path)
|
|
_require_sha256(
|
|
record.get("header_sha256"),
|
|
f"base.{label}.shards[{shard_idx}].header_sha256",
|
|
manifest_path,
|
|
)
|
|
_require_sha256(
|
|
record.get("sample_sha256"),
|
|
f"base.{label}.shards[{shard_idx}].sample_sha256",
|
|
manifest_path,
|
|
)
|
|
identity = record.get("file_identity")
|
|
if not isinstance(identity, Mapping) or any(
|
|
isinstance(identity.get(field), bool) or not isinstance(identity.get(field), int)
|
|
for field in ("device", "inode", "ctime_ns")
|
|
):
|
|
raise KTArtifactError(
|
|
f"{manifest_path}: invalid base.{label}.shards[{shard_idx}].file_identity"
|
|
)
|
|
if fingerprint != _rawint4_base_fingerprint(config_digest, index_digest, shards):
|
|
raise KTArtifactError(f"{manifest_path}: {label} RAWINT4 base fingerprint is invalid")
|
|
|
|
if saved["fingerprint"] != current["fingerprint"]:
|
|
raise KTArtifactError(f"{manifest_path}: RAWINT4 base checkpoint fingerprint changed")
|
|
|
|
saved_path = _require_string(saved.get("model_name_or_path"), "base.model_name_or_path", manifest_path)
|
|
current_path = _require_string(
|
|
current.get("model_name_or_path"), "runtime.base.model_name_or_path", manifest_path
|
|
)
|
|
if _same_source(current_path, saved_path):
|
|
saved_identity = {
|
|
record["name"]: record["file_identity"] for record in saved["shards"]
|
|
}
|
|
current_identity = {
|
|
record["name"]: record["file_identity"] for record in current["shards"]
|
|
}
|
|
if saved_identity != current_identity:
|
|
raise KTArtifactError(
|
|
f"{manifest_path}: RAWINT4 base shards were replaced at the recorded path"
|
|
)
|
|
|
|
|
|
def _fsync_directory(path: Path) -> None:
|
|
descriptor = os.open(path, os.O_RDONLY)
|
|
try:
|
|
os.fsync(descriptor)
|
|
finally:
|
|
os.close(descriptor)
|
|
|
|
|
|
def _write_json_atomic(path: Path, payload: Mapping[str, Any]) -> None:
|
|
descriptor, temporary_name = tempfile.mkstemp(
|
|
prefix=f".{path.name}.", suffix=".tmp", dir=path.parent
|
|
)
|
|
try:
|
|
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
json.dump(dict(payload), handle, ensure_ascii=False, indent=2, sort_keys=True)
|
|
handle.write("\n")
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
os.replace(temporary_name, path)
|
|
_fsync_directory(path.parent)
|
|
except BaseException:
|
|
with contextlib.suppress(FileNotFoundError):
|
|
os.unlink(temporary_name)
|
|
raise
|
|
|
|
|
|
def _invalidate_ready_manifest(path: Path) -> None:
|
|
"""Remove an earlier ready marker before replacing any bundle member."""
|
|
|
|
if not path.exists() and not path.is_symlink():
|
|
return
|
|
if path.is_symlink() or not path.is_file():
|
|
raise KTArtifactError(f"existing KT adapter manifest must be a regular file: {path}")
|
|
path.unlink()
|
|
_fsync_directory(path.parent)
|
|
|
|
|
|
def _safe_root(path: str | os.PathLike[str], description: str) -> Path:
|
|
root = Path(path)
|
|
if not root.is_absolute():
|
|
raise KTArtifactError(f"{description} must be an absolute path: {root}")
|
|
if root.is_symlink() or not root.is_dir():
|
|
raise KTArtifactError(f"{description} must be a real directory: {root}")
|
|
return root.resolve()
|
|
|
|
|
|
def _distributed_validation_context() -> tuple[Any | None, int, int]:
|
|
"""Return an initialized process group, never launcher-env guesses."""
|
|
|
|
try:
|
|
import torch.distributed as dist
|
|
except ImportError:
|
|
return None, 0, 1
|
|
if not dist.is_available() or not dist.is_initialized() or dist.get_world_size() <= 1:
|
|
return None, 0, 1
|
|
return dist, int(dist.get_rank()), int(dist.get_world_size())
|
|
|
|
|
|
def _synchronize_artifact_validation(
|
|
dist: Any | None,
|
|
rank: int,
|
|
world_size: int,
|
|
error: Exception | None,
|
|
signature: tuple[Any, ...] | None,
|
|
) -> None:
|
|
"""Make validation failure and artifact disagreement symmetric across ranks."""
|
|
|
|
if dist is None or world_size <= 1:
|
|
if error is not None:
|
|
raise error
|
|
return
|
|
local_error = None if error is None else f"rank {rank}: {type(error).__name__}: {error}"
|
|
gathered: list[tuple[str | None, tuple[Any, ...] | None] | None] = [None] * world_size
|
|
dist.all_gather_object(gathered, (local_error, signature))
|
|
failures = [entry[0] for entry in gathered if entry is not None and entry[0] is not None]
|
|
if failures:
|
|
raise KTArtifactError("distributed KT artifact validation failed: " + "; ".join(failures))
|
|
signatures = {entry[1] for entry in gathered if entry is not None}
|
|
if len(signatures) != 1:
|
|
raise KTArtifactError("distributed KT ranks resolved different artifact provenance")
|
|
|
|
|
|
def _inspect_cache_tensors(
|
|
root: Path,
|
|
index: Mapping[str, Any],
|
|
*,
|
|
source_config: Mapping[str, Any],
|
|
verify_hashes: bool,
|
|
file_records: Mapping[str, Mapping[str, Any]],
|
|
) -> tuple[frozenset[str], int, dict[str, int], tuple[str, ...]]:
|
|
index_path = root / KT_NON_EXPERT_INDEX_NAME
|
|
weight_map = index.get("weight_map")
|
|
if not isinstance(weight_map, dict) or not weight_map:
|
|
raise KTArtifactError(f"{index_path}: weight_map must be a non-empty object")
|
|
shard_names: set[str] = set()
|
|
for key, shard_name in weight_map.items():
|
|
if not isinstance(key, str) or not key:
|
|
raise KTArtifactError(f"{index_path}: tensor names must be non-empty strings")
|
|
if not isinstance(shard_name, str) or shard_name != os.path.basename(shard_name):
|
|
raise KTArtifactError(f"{index_path}: unsafe shard name {shard_name!r}")
|
|
if not shard_name.endswith(".safetensors"):
|
|
raise KTArtifactError(f"{index_path}: every shard must use safetensors")
|
|
shard_names.add(shard_name)
|
|
|
|
expected_files = shard_names | {KT_NON_EXPERT_INDEX_NAME}
|
|
if set(file_records) != expected_files:
|
|
raise KTArtifactError(
|
|
f"cache file inventory differs from its index: expected={sorted(expected_files)}, "
|
|
f"actual={sorted(file_records)}"
|
|
)
|
|
|
|
layer_count = source_config.get("num_hidden_layers")
|
|
mtp_prefix = f"model.layers.{layer_count}." if isinstance(layer_count, int) else None
|
|
reject_mtp = source_config.get("model_type") == "deepseek_v3" and mtp_prefix is not None
|
|
observed_keys: set[str] = set()
|
|
observed_bytes = 0
|
|
dtype_counts: dict[str, int] = {}
|
|
checkpoint_files: list[str] = []
|
|
for name in sorted(expected_files):
|
|
path = root / name
|
|
if path.is_symlink() or not path.is_file():
|
|
raise KTArtifactError(f"cache artifact must be a regular file: {path}")
|
|
record = file_records[name]
|
|
if path.stat().st_size != record.get("size"):
|
|
raise KTArtifactError(f"cache size mismatch for {name}")
|
|
if verify_hashes and _sha256_file(path) != record.get("sha256"):
|
|
raise KTArtifactError(f"cache SHA256 mismatch for {name}")
|
|
if name == KT_NON_EXPERT_INDEX_NAME:
|
|
continue
|
|
checkpoint_files.append(str(path))
|
|
with safe_open(path, framework="pt", device="cpu") as handle:
|
|
for key in handle.keys():
|
|
if key in observed_keys:
|
|
raise KTArtifactError(f"duplicate tensor {key!r} across cache shards")
|
|
tensor = handle.get_slice(key)
|
|
dtype = tensor.get_dtype()
|
|
expected_dtype = "F32" if _FP32_ROUTER_BIAS.fullmatch(key) else "BF16"
|
|
if dtype != expected_dtype:
|
|
raise KTArtifactError(
|
|
f"cache tensor {key!r} must be {expected_dtype}, got {dtype}"
|
|
)
|
|
if _ROUTED_EXPERT.search(key) or key.endswith(".weight_scale_inv"):
|
|
raise KTArtifactError(f"cache contains routed/quantized tensor {key!r}")
|
|
if reject_mtp and key.startswith(mtp_prefix):
|
|
raise KTArtifactError(f"cache contains excluded MTP tensor {key!r}")
|
|
elements = 1
|
|
for dimension in tensor.get_shape():
|
|
elements *= dimension
|
|
observed_bytes += elements * (4 if dtype == "F32" else 2)
|
|
dtype_counts[dtype] = dtype_counts.get(dtype, 0) + 1
|
|
observed_keys.add(key)
|
|
if observed_keys != set(weight_map):
|
|
raise KTArtifactError("cache shard keys do not match the safetensors index")
|
|
return frozenset(observed_keys), observed_bytes, dtype_counts, tuple(checkpoint_files)
|
|
|
|
|
|
def write_kt_non_expert_cache_manifest(
|
|
cache_path: str | os.PathLike[str],
|
|
source_model_name_or_path: str | os.PathLike[str],
|
|
) -> Mapping[str, Any]:
|
|
"""Publish a schema-v2 ready manifest for an already converted cache.
|
|
|
|
This is the only producer in the public API. The legacy LLaMA-Factory v1
|
|
producer remains readable but is never emitted by kt-kernel.
|
|
"""
|
|
|
|
root = _safe_root(cache_path, "KT non-expert cache")
|
|
source = _safe_root(source_model_name_or_path, "source model")
|
|
if (root / KT_NON_EXPERT_MANIFEST_NAME).exists():
|
|
raise KTArtifactError(f"cache manifest already exists: {root / KT_NON_EXPERT_MANIFEST_NAME}")
|
|
source_config_path = source / "config.json"
|
|
source_index_path = source / KT_NON_EXPERT_INDEX_NAME
|
|
source_config = _read_json(source_config_path, "source config")
|
|
_read_json(source_index_path, "source safetensors index")
|
|
cache_index_path = root / KT_NON_EXPERT_INDEX_NAME
|
|
cache_index = _read_json(cache_index_path, "cache safetensors index")
|
|
|
|
source_config_digest = _sha256_file(source_config_path)
|
|
source_index_digest = _sha256_file(source_index_path)
|
|
source_fingerprint = _source_fingerprint(source_config_digest, source_index_digest)
|
|
files = [cache_index_path, *(root / name for name in sorted(set(cache_index.get("weight_map", {}).values())))]
|
|
records: dict[str, dict[str, Any]] = {}
|
|
for path in files:
|
|
if path.is_symlink() or not path.is_file() or path.name in records:
|
|
raise KTArtifactError(f"invalid or duplicate cache artifact: {path}")
|
|
records[path.name] = {
|
|
"name": path.name,
|
|
"size": path.stat().st_size,
|
|
"sha256": _sha256_file(path),
|
|
}
|
|
weight_keys, tensor_bytes, dtype_counts, _ = _inspect_cache_tensors(
|
|
root,
|
|
cache_index,
|
|
source_config=source_config,
|
|
verify_hashes=True,
|
|
file_records=records,
|
|
)
|
|
quantization = source_config.get("quantization_config")
|
|
if not isinstance(quantization, dict) or quantization.get("quant_method") != "fp8":
|
|
raise KTArtifactError("source config must describe an FP8 checkpoint")
|
|
block_size = quantization.get("weight_block_size")
|
|
if (
|
|
not isinstance(block_size, (list, tuple))
|
|
or len(block_size) != 2
|
|
or any(isinstance(value, bool) or not isinstance(value, int) or value <= 0 for value in block_size)
|
|
):
|
|
raise KTArtifactError("source FP8 weight_block_size must contain two positive integers")
|
|
record_list = [records[name] for name in sorted(records)]
|
|
payload = {
|
|
"version": KT_NON_EXPERT_MANIFEST_VERSION,
|
|
"status": "ready",
|
|
"fingerprint": _cache_fingerprint(
|
|
KT_NON_EXPERT_MANIFEST_VERSION,
|
|
source_fingerprint,
|
|
record_list,
|
|
len(weight_keys),
|
|
tensor_bytes,
|
|
dtype_counts,
|
|
),
|
|
"source": {
|
|
"model_name_or_path": str(source),
|
|
"fingerprint": source_fingerprint,
|
|
"config_sha256": source_config_digest,
|
|
"index_sha256": source_index_digest,
|
|
},
|
|
"converter": {
|
|
"name": _NON_EXPERT_PRODUCER,
|
|
"version": KT_NON_EXPERT_MANIFEST_VERSION,
|
|
"default_dtype": "BF16",
|
|
"fp32_exceptions": ["model.layers.*.mlp.gate.e_score_correction_bias"],
|
|
"weight_block_size": list(block_size),
|
|
},
|
|
"files": record_list,
|
|
"tensors": {"count": len(weight_keys), "bytes": tensor_bytes, "dtypes": dtype_counts},
|
|
}
|
|
_write_json_atomic(root / KT_NON_EXPERT_MANIFEST_NAME, payload)
|
|
return payload
|
|
|
|
|
|
def _validate_non_expert_cache(
|
|
cache_path: str | os.PathLike[str],
|
|
source_model_name_or_path: str | os.PathLike[str],
|
|
*,
|
|
verify_hashes: bool = True,
|
|
) -> KTNonExpertCache:
|
|
root = _safe_root(cache_path, "KT non-expert cache")
|
|
source = _safe_root(source_model_name_or_path, "source model")
|
|
manifest_path = root / KT_NON_EXPERT_MANIFEST_NAME
|
|
manifest = _read_json(manifest_path, "KT non-expert cache manifest")
|
|
version = manifest.get("version")
|
|
if version not in {_LEGACY_NON_EXPERT_VERSION, KT_NON_EXPERT_MANIFEST_VERSION}:
|
|
raise KTArtifactError(f"{manifest_path}: unsupported version {version!r}")
|
|
if manifest.get("status") != "ready":
|
|
raise KTArtifactError(f"{manifest_path}: status must be 'ready'")
|
|
fingerprint = _require_sha256(manifest.get("fingerprint"), "fingerprint", manifest_path)
|
|
source_record = manifest.get("source")
|
|
if not isinstance(source_record, dict):
|
|
raise KTArtifactError(f"{manifest_path}: source must be an object")
|
|
recorded_source = _require_string(
|
|
source_record.get("model_name_or_path"), "source.model_name_or_path", manifest_path
|
|
)
|
|
if not _same_source(source, recorded_source):
|
|
raise KTArtifactError(
|
|
f"{manifest_path}: cache source {recorded_source!r} does not match {str(source)!r}"
|
|
)
|
|
source_fingerprint = _require_sha256(
|
|
source_record.get("fingerprint"), "source.fingerprint", manifest_path
|
|
)
|
|
source_config_path = source / "config.json"
|
|
source_index_path = source / KT_NON_EXPERT_INDEX_NAME
|
|
expected_source_files = {
|
|
"config_sha256": source_config_path,
|
|
"index_sha256": source_index_path,
|
|
}
|
|
for field, path in expected_source_files.items():
|
|
expected = _require_sha256(source_record.get(field), f"source.{field}", manifest_path)
|
|
if path.is_symlink() or not path.is_file() or _sha256_file(path) != expected:
|
|
raise KTArtifactError(f"{manifest_path}: source.{field} does not match {path}")
|
|
if version == KT_NON_EXPERT_MANIFEST_VERSION:
|
|
expected_source_fingerprint = _source_fingerprint(
|
|
source_record["config_sha256"], source_record["index_sha256"]
|
|
)
|
|
if source_fingerprint != expected_source_fingerprint:
|
|
raise KTArtifactError(f"{manifest_path}: source fingerprint is invalid")
|
|
source_config = _read_json(source_config_path, "source config")
|
|
quantization = source_config.get("quantization_config")
|
|
if not isinstance(quantization, dict) or quantization.get("quant_method") != "fp8":
|
|
raise KTArtifactError(f"{manifest_path}: source config must describe an FP8 checkpoint")
|
|
block_size = quantization.get("weight_block_size")
|
|
if (
|
|
not isinstance(block_size, (list, tuple))
|
|
or len(block_size) != 2
|
|
or any(isinstance(value, bool) or not isinstance(value, int) or value <= 0 for value in block_size)
|
|
):
|
|
raise KTArtifactError(f"{manifest_path}: invalid source FP8 weight_block_size")
|
|
producer = _LEGACY_NON_EXPERT_PRODUCER if version == 1 else _NON_EXPERT_PRODUCER
|
|
expected_converter = {
|
|
"name": producer,
|
|
"version": version,
|
|
"default_dtype": "BF16",
|
|
"fp32_exceptions": ["model.layers.*.mlp.gate.e_score_correction_bias"],
|
|
"weight_block_size": list(block_size),
|
|
}
|
|
if manifest.get("converter") != expected_converter:
|
|
raise KTArtifactError(f"{manifest_path}: converter contract is unsupported")
|
|
|
|
files = manifest.get("files")
|
|
if not isinstance(files, list) or not files:
|
|
raise KTArtifactError(f"{manifest_path}: files must be a non-empty list")
|
|
records: dict[str, Mapping[str, Any]] = {}
|
|
for index, record in enumerate(files):
|
|
if not isinstance(record, dict):
|
|
raise KTArtifactError(f"{manifest_path}: files[{index}] must be an object")
|
|
name = _require_string(record.get("name"), f"files[{index}].name", manifest_path)
|
|
if name != os.path.basename(name) or name in records:
|
|
raise KTArtifactError(f"{manifest_path}: invalid or duplicate filename {name!r}")
|
|
_positive_int(record.get("size"), f"files[{index}].size", manifest_path)
|
|
_require_sha256(record.get("sha256"), f"files[{index}].sha256", manifest_path)
|
|
records[name] = record
|
|
cache_index = _read_json(root / KT_NON_EXPERT_INDEX_NAME, "cache safetensors index")
|
|
weight_keys, observed_bytes, observed_dtypes, checkpoint_files = _inspect_cache_tensors(
|
|
root,
|
|
cache_index,
|
|
source_config=source_config,
|
|
verify_hashes=verify_hashes,
|
|
file_records=records,
|
|
)
|
|
tensors = manifest.get("tensors")
|
|
if not isinstance(tensors, dict):
|
|
raise KTArtifactError(f"{manifest_path}: tensors must be an object")
|
|
if tensors.get("count") != len(weight_keys):
|
|
raise KTArtifactError(f"{manifest_path}: tensor count does not match the index")
|
|
if tensors.get("bytes") != observed_bytes or tensors.get("dtypes") != observed_dtypes:
|
|
raise KTArtifactError(f"{manifest_path}: tensor metadata does not match cache shards")
|
|
expected_fingerprint = _cache_fingerprint(
|
|
int(version),
|
|
source_fingerprint,
|
|
[dict(record) for record in records.values()],
|
|
len(weight_keys),
|
|
observed_bytes,
|
|
observed_dtypes,
|
|
)
|
|
if fingerprint != expected_fingerprint:
|
|
raise KTArtifactError(f"{manifest_path}: fingerprint does not match its records")
|
|
return KTNonExpertCache(
|
|
path=str(root),
|
|
manifest_path=str(manifest_path),
|
|
manifest=manifest,
|
|
checkpoint_files=checkpoint_files,
|
|
weight_keys=weight_keys,
|
|
source_config=source_config,
|
|
)
|
|
|
|
|
|
def _config_value(config: Any, name: str, default: Any = None) -> Any:
|
|
if isinstance(config, Mapping):
|
|
return config.get(name, default)
|
|
return getattr(config, name, default)
|
|
|
|
|
|
def _config_expert_weight_format(config: Any) -> str:
|
|
weight_format = _config_value(config, "kt_expert_weight_format", None)
|
|
if isinstance(weight_format, str) and weight_format.strip():
|
|
return weight_format.strip().lower()
|
|
backend = _config_value(config, "kt_backend", None)
|
|
if not isinstance(backend, str):
|
|
return ""
|
|
return {
|
|
"int8": "int8",
|
|
"amxint8": "int8",
|
|
"fp8": "fp8",
|
|
"amxfp8": "fp8",
|
|
"rawint4": "rawint4",
|
|
"amxint4_kgroup": "rawint4",
|
|
"mxfp4": "mxfp4",
|
|
}.get(backend.strip().lower(), "")
|
|
|
|
|
|
def is_kt_routed_expert_parameter_name(name: str) -> bool:
|
|
"""Whether a checkpoint key is a routed expert base parameter owned by KT."""
|
|
|
|
return isinstance(name, str) and _ROUTED_EXPERT_PARAMETER.search(name) is not None
|
|
|
|
|
|
def is_kt_supported_moe_model(model: Any) -> bool:
|
|
"""Return whether the model architecture is implemented by KT SFT wrappers."""
|
|
|
|
config = getattr(model, "config", None)
|
|
architectures = getattr(config, "architectures", None)
|
|
architecture_match = isinstance(architectures, (list, tuple)) and any(
|
|
isinstance(architecture, str) and any(marker in architecture for marker in _SUPPORTED_MOE_ARCHITECTURES)
|
|
for architecture in architectures
|
|
)
|
|
if architecture_match:
|
|
return True
|
|
text_config = getattr(config, "text_config", None)
|
|
model_types = {
|
|
str(getattr(config, "model_type", "")),
|
|
str(getattr(text_config, "model_type", "")),
|
|
}
|
|
return bool(model_types.intersection({"kimi_k2", "kimi_k25", "kimi_k2_5", "kimi_k26", "kimi_k2_6", "deepseek_v4"}))
|
|
|
|
|
|
def should_disable_kt_source_quantizer(
|
|
kt_config: Any,
|
|
model_config: Any,
|
|
explicit_quantization_config: Any | None = None,
|
|
) -> bool:
|
|
"""Return whether KT consumes the checkpoint's quantized tensors directly.
|
|
|
|
RAWINT4 checkpoints keep ordinary model weights in BF16 and quantize only
|
|
routed experts. KT owns those packed expert tensors, so the framework must
|
|
not replace the expert module structure with its source quantizer. The
|
|
nested checkpoint metadata remains available for KT's strict validation.
|
|
"""
|
|
|
|
weight_format = _config_expert_weight_format(kt_config)
|
|
if weight_format == "mxfp4":
|
|
if explicit_quantization_config is not None:
|
|
raise KTArtifactError("MXFP4 SFT cannot use an explicit framework quantizer")
|
|
if getattr(model_config, "model_type", None) != "deepseek_v4":
|
|
raise KTArtifactError("native MXFP4 SFT currently requires DeepSeek V4")
|
|
cache_path = _config_value(kt_config, "kt_non_expert_weight_path")
|
|
if not cache_path:
|
|
raise KTArtifactError("Set kt_non_expert_weight_path for the automatic V4 BF16 non-expert cache")
|
|
# Config loading precedes the pretrained artifact resolver, which owns
|
|
# first-use preparation and full validation before any tensor is loaded.
|
|
return True
|
|
rawint4_requested = weight_format == "rawint4"
|
|
skip_loading = bool(_config_value(kt_config, "kt_skip_expert_loading", True))
|
|
if not rawint4_requested or not skip_loading:
|
|
return False
|
|
if explicit_quantization_config is not None:
|
|
raise KTArtifactError("RAWINT4 SFT cannot be combined with an explicit quantization_config")
|
|
try:
|
|
get_rawint4_checkpoint_contract(model_config)
|
|
except ValueError as exc:
|
|
raise KTArtifactError(str(exc)) from exc
|
|
return True
|
|
|
|
|
|
def get_kt_fused_lora_exclude_modules(kt_config: Any, model: Any) -> str | None:
|
|
"""Return the exact PEFT exclusion regex for KT-owned expert projections."""
|
|
|
|
if not bool(_config_value(kt_config, "kt_force_fused_expert_lora", False)):
|
|
return None
|
|
|
|
wrappers = tuple(getattr(model, "_kt_wrappers", ()) or ())
|
|
if not wrappers:
|
|
raise KTArtifactError("KT fused expert LoRA requires wrapped MoE layers before PEFT injection")
|
|
|
|
module_names = {id(module): name for name, module in model.named_modules()}
|
|
patterns = []
|
|
for wrapper in wrappers:
|
|
if not bool(getattr(wrapper, "_use_fused_expert_lora", False)):
|
|
continue
|
|
wrapper_name = module_names.get(id(wrapper))
|
|
if not wrapper_name:
|
|
raise KTArtifactError("KT fused expert LoRA wrapper is missing from the model module tree")
|
|
moe_config = getattr(wrapper, "moe_config", None)
|
|
weight_names = tuple(getattr(moe_config, "weight_names", ()))
|
|
experts_attr = str(getattr(wrapper, "_experts_attr", "experts"))
|
|
if not weight_names or any(not isinstance(name, str) or not name for name in weight_names):
|
|
raise KTArtifactError(f"{wrapper_name}: invalid KT expert projection contract")
|
|
projections = "|".join(re.escape(name) for name in weight_names)
|
|
patterns.append(
|
|
rf"{re.escape(wrapper_name)}\.{re.escape(experts_attr)}\.\d+\.(?:{projections})"
|
|
)
|
|
|
|
if not patterns:
|
|
raise KTArtifactError("KT fused expert LoRA did not claim any routed-expert projections")
|
|
return rf"(?:{'|'.join(patterns)})"
|
|
|
|
|
|
def _loading_value(loading_info: Any, name: str, default: Any) -> Any:
|
|
if isinstance(loading_info, Mapping):
|
|
return loading_info.get(name, default)
|
|
return getattr(loading_info, name, default)
|
|
|
|
|
|
def validate_kt_prequantized_loading_info(
|
|
kt_config: Any,
|
|
loading_info: Any,
|
|
model: Any | None = None,
|
|
) -> None:
|
|
"""Fail closed after native quantized routed experts were skipped.
|
|
|
|
This covers the pre-quantized path without a BF16 non-expert load plan.
|
|
The plan-based path additionally calls :func:`validate_kt_pretrained_load`
|
|
to compare the instantiated model against the cache's exact key set.
|
|
"""
|
|
|
|
weight_format = _config_expert_weight_format(kt_config)
|
|
skip_loading = _config_value(kt_config, "kt_skip_expert_loading", True)
|
|
if weight_format not in {"int8", "fp8", "rawint4", "mxfp4"} or not bool(skip_loading):
|
|
return
|
|
config = getattr(model, "config", None)
|
|
text_config = getattr(config, "text_config", None)
|
|
effective_config = text_config if text_config is not None else config
|
|
model_type = getattr(effective_config, "model_type", None)
|
|
layer_count = getattr(effective_config, "num_hidden_layers", None)
|
|
mtp_prefix = (
|
|
f"model.layers.{layer_count}."
|
|
if model_type == "deepseek_v3" and isinstance(layer_count, int) and not isinstance(layer_count, bool)
|
|
else None
|
|
)
|
|
missing = sorted(
|
|
key
|
|
for key in (_loading_value(loading_info, "missing_keys", ()) or ())
|
|
if not is_kt_routed_expert_parameter_name(key)
|
|
)
|
|
mismatched = sorted(
|
|
mismatch
|
|
for mismatch in (_loading_value(loading_info, "mismatched_keys", ()) or ())
|
|
if not is_kt_routed_expert_parameter_name(mismatch[0])
|
|
)
|
|
conversion_errors = {
|
|
key: error
|
|
for key, error in (_loading_value(loading_info, "conversion_errors", {}) or {}).items()
|
|
if not is_kt_routed_expert_parameter_name(key)
|
|
}
|
|
unexpected = sorted(
|
|
key
|
|
for key in (_loading_value(loading_info, "unexpected_keys", ()) or ())
|
|
if not (mtp_prefix is not None and key.startswith(mtp_prefix))
|
|
)
|
|
error_messages = list(_loading_value(loading_info, "error_msgs", ()) or ())
|
|
failures = []
|
|
if missing:
|
|
failures.append(f"missing_keys={missing}")
|
|
if mismatched:
|
|
failures.append(f"mismatched_keys={mismatched}")
|
|
if conversion_errors:
|
|
failures.append(f"conversion_errors={conversion_errors}")
|
|
if unexpected:
|
|
failures.append(f"unexpected_keys={unexpected}")
|
|
if error_messages:
|
|
failures.append(f"error_msgs={error_messages}")
|
|
if failures:
|
|
raise KTArtifactError(
|
|
f"KT {weight_format.upper()} checkpoint loading requires an exact non-expert model match; "
|
|
+ "; ".join(failures)
|
|
)
|
|
|
|
|
|
def resolve_kt_pretrained_artifacts(
|
|
kt_config: Any,
|
|
pretrained_model_name_or_path: str | os.PathLike[str],
|
|
quantization_config: Any | None = None,
|
|
) -> KTPretrainedLoadPlan | None:
|
|
"""Resolve and fully validate KT-owned pretrained artifacts.
|
|
|
|
``quantization_config`` means an explicit caller override. The source
|
|
checkpoint's embedded FP8 quantizer is represented by
|
|
``disable_source_quantizer`` on the returned plan instead.
|
|
"""
|
|
|
|
if _config_expert_weight_format(kt_config) == "mxfp4":
|
|
from .deepseek_v4 import resolve_native_load_plan
|
|
|
|
return resolve_native_load_plan(kt_config, pretrained_model_name_or_path, quantization_config)
|
|
cache_path = _config_value(kt_config, "kt_non_expert_weight_path")
|
|
if not cache_path:
|
|
return None
|
|
if _config_expert_weight_format(kt_config) != "int8":
|
|
raise KTArtifactError("kt_non_expert_weight_path requires routed INT8 expert weights")
|
|
if quantization_config is not None:
|
|
raise KTArtifactError(
|
|
"kt_non_expert_weight_path cannot be combined with an explicit quantization_config"
|
|
)
|
|
weight_path = _config_value(kt_config, "kt_weight_path")
|
|
if not isinstance(weight_path, str) or not weight_path:
|
|
raise KTArtifactError("routed INT8 loading requires kt_weight_path")
|
|
|
|
dist, distributed_rank, world_size = _distributed_validation_context()
|
|
cache = None
|
|
validated_routed = None
|
|
routed_manifest = None
|
|
local_error = None
|
|
signature = None
|
|
try:
|
|
# All ranks retain structural, size, dtype, and inventory checks. Only
|
|
# rank 0 streams the large payloads for SHA256 verification.
|
|
cache = _validate_non_expert_cache(
|
|
cache_path,
|
|
pretrained_model_name_or_path,
|
|
verify_hashes=distributed_rank == 0,
|
|
)
|
|
source_config = cache.source_config
|
|
layer_count = _positive_int(
|
|
source_config.get("num_hidden_layers"), "num_hidden_layers", Path(cache.manifest_path)
|
|
)
|
|
first_moe_layer = source_config.get("first_k_dense_replace", 0)
|
|
if isinstance(first_moe_layer, bool) or not isinstance(first_moe_layer, int) or first_moe_layer < 0:
|
|
raise KTArtifactError("source first_k_dense_replace must be a non-negative integer")
|
|
layer_indices = tuple(range(first_moe_layer, layer_count))
|
|
validated_routed = validate_persistent_int8_weights(
|
|
weight_path,
|
|
layer_indices=layer_indices,
|
|
numa_count=int(_config_value(kt_config, "kt_threadpool_count", 1) or 1),
|
|
expert_num=int(source_config["n_routed_experts"]),
|
|
hidden_size=int(source_config["hidden_size"]),
|
|
intermediate_size=int(source_config["moe_intermediate_size"]),
|
|
# None hashes schema-v2 but keeps legacy v1 on its strict
|
|
# structure/size contract; non-owner ranks never repeat hashes.
|
|
verify_hashes=None if distributed_rank == 0 else False,
|
|
)
|
|
routed_manifest = _read_json(validated_routed.path, "routed INT8 manifest")
|
|
signature = (
|
|
cache.fingerprint,
|
|
cache.path,
|
|
str(validated_routed.root),
|
|
hashlib.sha256(
|
|
json.dumps(routed_manifest, sort_keys=True, separators=(",", ":")).encode()
|
|
).hexdigest(),
|
|
)
|
|
except Exception as exc:
|
|
local_error = (
|
|
exc
|
|
if isinstance(exc, KTArtifactError)
|
|
else KTArtifactError(f"routed INT8 artifact validation failed: {exc}")
|
|
)
|
|
_synchronize_artifact_validation(dist, distributed_rank, world_size, local_error, signature)
|
|
if cache is None or validated_routed is None or routed_manifest is None:
|
|
raise KTArtifactError("KT artifact validation completed without a load plan")
|
|
rank = _config_value(kt_config, "kt_lora_rank")
|
|
alpha = _config_value(kt_config, "kt_lora_alpha")
|
|
return KTPretrainedLoadPlan(
|
|
source_model_name_or_path=os.fspath(pretrained_model_name_or_path),
|
|
weight_path=cache.path,
|
|
checkpoint_files=cache.checkpoint_files,
|
|
weight_keys=cache.weight_keys,
|
|
manifest=cache.manifest,
|
|
manifest_path=cache.manifest_path,
|
|
routed_weight_path=str(validated_routed.root),
|
|
routed_manifest=routed_manifest,
|
|
routed_manifest_path=str(validated_routed.path),
|
|
lora_rank=int(rank) if isinstance(rank, int) and not isinstance(rank, bool) else None,
|
|
lora_alpha=float(alpha) if isinstance(alpha, (int, float)) and not isinstance(alpha, bool) else None,
|
|
)
|
|
|
|
|
|
def _canonical_loaded_key(key: str) -> str | None:
|
|
if "._original_router.weight" in key:
|
|
return None
|
|
if "._original_router.e_score_correction_bias" in key:
|
|
return key.replace(
|
|
"._original_router.e_score_correction_bias", ".gate.e_score_correction_bias"
|
|
)
|
|
return key
|
|
|
|
|
|
def validate_kt_pretrained_load(
|
|
plan: KTPretrainedLoadPlan | None,
|
|
loading_info: Mapping[str, Any],
|
|
model: Any,
|
|
) -> KTPretrainedLoadPlan | None:
|
|
"""Validate the instantiated model and attach immutable artifact provenance."""
|
|
|
|
if plan is None:
|
|
return None
|
|
if loading_info is None:
|
|
raise KTArtifactError("pretrained loader did not return loading diagnostics")
|
|
missing = set(_loading_value(loading_info, "missing_keys", ()) or ())
|
|
invalid_missing = sorted(key for key in missing if not _ROUTED_EXPERT.search(key))
|
|
failures: dict[str, Any] = {}
|
|
if invalid_missing:
|
|
failures["missing_keys"] = invalid_missing
|
|
unexpected = list(_loading_value(loading_info, "unexpected_keys", ()) or ())
|
|
mismatched = [
|
|
value
|
|
for value in (_loading_value(loading_info, "mismatched_keys", ()) or ())
|
|
if not is_kt_routed_expert_parameter_name(value[0])
|
|
]
|
|
error_messages = list(_loading_value(loading_info, "error_msgs", ()) or ())
|
|
if unexpected:
|
|
failures["unexpected_keys"] = unexpected
|
|
if mismatched:
|
|
failures["mismatched_keys"] = mismatched
|
|
if error_messages:
|
|
failures["error_msgs"] = error_messages
|
|
conversion_errors = {
|
|
key: value
|
|
for key, value in (_loading_value(loading_info, "conversion_errors", {}) or {}).items()
|
|
if not is_kt_routed_expert_parameter_name(key)
|
|
}
|
|
if conversion_errors:
|
|
failures["conversion_errors"] = dict(conversion_errors)
|
|
if failures:
|
|
raise KTArtifactError(f"non-expert checkpoint did not load exactly: {failures}")
|
|
state_dict = model.state_dict()
|
|
loaded_keys = set()
|
|
for key in state_dict:
|
|
canonical = _canonical_loaded_key(key)
|
|
if canonical is not None and not _ROUTED_EXPERT.search(canonical):
|
|
loaded_keys.add(canonical)
|
|
if loaded_keys != set(plan.weight_keys):
|
|
raise KTArtifactError(
|
|
"loaded non-expert keys differ from the validated cache: "
|
|
f"missing={sorted(set(plan.weight_keys) - loaded_keys)[:16]}, "
|
|
f"unexpected={sorted(loaded_keys - set(plan.weight_keys))[:16]}"
|
|
)
|
|
config = getattr(model, "config", None)
|
|
if config is not None:
|
|
if hasattr(config, "name_or_path"):
|
|
config.name_or_path = plan.source_model_name_or_path
|
|
if hasattr(config, "_name_or_path"):
|
|
config._name_or_path = plan.source_model_name_or_path
|
|
model._kt_pretrained_load_plan = plan
|
|
model._kt_base_model_name_or_path = plan.source_model_name_or_path
|
|
model._kt_non_expert_cache_path = plan.weight_path
|
|
model._kt_non_expert_cache_manifest = plan.manifest
|
|
model._kt_routed_int8_manifest_path = plan.routed_manifest_path
|
|
model._kt_routed_int8_manifest = plan.routed_manifest
|
|
return plan
|
|
|
|
|
|
def _deepseek_routed_paths(config: Any) -> tuple[str, ...]:
|
|
layer_count = getattr(config, "num_hidden_layers", None)
|
|
first_moe_layer = getattr(config, "first_k_dense_replace", None)
|
|
if (
|
|
isinstance(layer_count, bool)
|
|
or not isinstance(layer_count, int)
|
|
or layer_count <= 0
|
|
or isinstance(first_moe_layer, bool)
|
|
or not isinstance(first_moe_layer, int)
|
|
or first_moe_layer < 0
|
|
or first_moe_layer >= layer_count
|
|
):
|
|
raise KTArtifactError("model config has an invalid routed-expert layer range")
|
|
return tuple(f"model.layers.{index}.mlp.experts" for index in range(first_moe_layer, layer_count))
|
|
|
|
|
|
def _effective_runtime_shape(tensor: Any, name: str) -> tuple[int, ...]:
|
|
shape = getattr(tensor, "_kt_original_shape", None) if getattr(tensor, "_kt_zero_storage", False) else tensor.shape
|
|
try:
|
|
resolved = tuple(int(value) for value in shape)
|
|
except (TypeError, ValueError) as exc:
|
|
raise KTArtifactError(f"{name} has invalid routed-expert shape metadata") from exc
|
|
if not resolved or any(value <= 0 for value in resolved):
|
|
raise KTArtifactError(f"{name} has invalid routed-expert shape {resolved}")
|
|
return resolved
|
|
|
|
|
|
def _runtime_tensor_contract(path: str, module: Any) -> tuple[tuple[str, str, tuple[int, ...]], ...]:
|
|
entries = []
|
|
seen = set()
|
|
for kind, tensors in (
|
|
("parameter", module.named_parameters(recurse=True, remove_duplicate=False)),
|
|
("buffer", module.named_buffers(recurse=True, remove_duplicate=False)),
|
|
):
|
|
for name, tensor in tensors:
|
|
key = (kind, name)
|
|
if key in seen:
|
|
raise KTArtifactError(f"{path} exposes duplicate routed-expert {kind} {name!r}")
|
|
seen.add(key)
|
|
entries.append((kind, name, _effective_runtime_shape(tensor, f"{path}.{name}")))
|
|
if not any(kind == "parameter" for kind, _, _ in entries):
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} contains no parameters")
|
|
return tuple(sorted(entries))
|
|
|
|
|
|
def _validate_runtime_expert_structure(path: str, experts: Any, moe_config: Any, hidden_size: Any) -> None:
|
|
import torch
|
|
|
|
dimensions = (moe_config.expert_num, moe_config.intermediate_size, hidden_size)
|
|
if any(isinstance(value, bool) or not isinstance(value, int) or value <= 0 for value in dimensions):
|
|
raise KTArtifactError(f"{path} has invalid routed-expert dimensions {dimensions}")
|
|
expert_num, intermediate_size, hidden_size = dimensions
|
|
|
|
gate_up = getattr(experts, "gate_up_proj", None)
|
|
down = getattr(experts, "down_proj", None)
|
|
if isinstance(gate_up, torch.nn.Parameter) or isinstance(down, torch.nn.Parameter):
|
|
if not isinstance(gate_up, torch.nn.Parameter) or not isinstance(down, torch.nn.Parameter):
|
|
raise KTArtifactError(f"{path} must register both fused gate_up_proj and down_proj parameters")
|
|
expected = {
|
|
"gate_up_proj": (expert_num, 2 * intermediate_size, hidden_size),
|
|
"down_proj": (expert_num, hidden_size, intermediate_size),
|
|
}
|
|
for name, parameter in (("gate_up_proj", gate_up), ("down_proj", down)):
|
|
actual = _effective_runtime_shape(parameter, f"{path}.{name}")
|
|
if actual != expected[name]:
|
|
raise KTArtifactError(f"{path}.{name} shape mismatch: expected={expected[name]}, actual={actual}")
|
|
return
|
|
|
|
children = tuple(experts.named_children())
|
|
expected_names = tuple(str(index) for index in range(expert_num))
|
|
if tuple(name for name, _ in children) != expected_names:
|
|
raise KTArtifactError(
|
|
f"{path} expert inventory mismatch: expected={list(expected_names)}, "
|
|
f"actual={[name for name, _ in children]}"
|
|
)
|
|
gate_name, up_name, down_name = moe_config.weight_names
|
|
expected_shapes = {
|
|
gate_name: (intermediate_size, hidden_size),
|
|
up_name: (intermediate_size, hidden_size),
|
|
down_name: (hidden_size, intermediate_size),
|
|
}
|
|
for expert_name, expert in children:
|
|
for projection_name, expected_shape in expected_shapes.items():
|
|
projection = getattr(expert, projection_name, None)
|
|
weight = getattr(projection, "weight", None)
|
|
if not isinstance(weight, torch.nn.Parameter):
|
|
raise KTArtifactError(f"{path}.{expert_name}.{projection_name} does not expose a weight Parameter")
|
|
actual_shape = _effective_runtime_shape(weight, f"{path}.{expert_name}.{projection_name}.weight")
|
|
if actual_shape != expected_shape:
|
|
raise KTArtifactError(
|
|
f"{path}.{expert_name}.{projection_name}.weight shape mismatch: "
|
|
f"expected={expected_shape}, actual={actual_shape}"
|
|
)
|
|
|
|
|
|
def _enumerate_runtime_routed_modules(model: Any) -> tuple[tuple[str, Any], ...]:
|
|
if not is_kt_supported_moe_model(model):
|
|
return ()
|
|
try:
|
|
from .arch import _get_layers_prefix, _get_model_container_and_layers, get_moe_arch_config, get_moe_module
|
|
|
|
config = model.config
|
|
moe_config = get_moe_arch_config(config)
|
|
_, layers = _get_model_container_and_layers(model, purpose="routed-expert ownership")
|
|
layers_path = _get_layers_prefix(config)
|
|
registered_layers = model.get_submodule(layers_path)
|
|
except Exception as exc:
|
|
raise KTArtifactError(f"could not enumerate KT routed-expert layers: {exc}") from exc
|
|
if registered_layers is not layers:
|
|
raise KTArtifactError(f"model layer path {layers_path!r} does not resolve to the discovered layer container")
|
|
|
|
text_config = getattr(config, "text_config", config)
|
|
hidden_size = getattr(text_config, "hidden_size", None)
|
|
modules = []
|
|
identities = set()
|
|
for layer_index, layer in enumerate(layers):
|
|
moe_module = get_moe_module(layer, moe_config)
|
|
if moe_module is None:
|
|
continue
|
|
experts = getattr(moe_module, moe_config.experts_attr, None)
|
|
if experts is None or not hasattr(experts, "named_parameters"):
|
|
raise KTArtifactError(f"layer {layer_index} does not expose a registered routed-expert module")
|
|
path = f"{layers_path}.{layer_index}.{moe_config.moe_layer_attr}.{moe_config.experts_attr}"
|
|
try:
|
|
registered = model.get_submodule(path)
|
|
except (AttributeError, KeyError) as exc:
|
|
raise KTArtifactError(f"missing routed-expert subtree {path!r}") from exc
|
|
if registered is not experts:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} does not preserve module identity")
|
|
if getattr(experts, "_is_kt_routed_experts_wrapper", False):
|
|
experts = getattr(experts, experts._experts_attr)
|
|
if id(experts) in identities:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} shares a module with another layer")
|
|
identities.add(id(experts))
|
|
_validate_runtime_expert_structure(path, experts, moe_config, hidden_size)
|
|
modules.append((path, experts))
|
|
if not modules:
|
|
raise KTArtifactError("supported KT MoE model contains no routed-expert layers")
|
|
return tuple(modules)
|
|
|
|
|
|
def _validated_runtime_routed_modules(model: Any) -> tuple[tuple[str, Any], ...]:
|
|
metadata = (
|
|
getattr(model, _RUNTIME_MODULE_PATHS, None),
|
|
getattr(model, _RUNTIME_MODULE_REFS, None),
|
|
getattr(model, _RUNTIME_TENSOR_CONTRACTS, None),
|
|
)
|
|
if metadata == (None, None, None):
|
|
return ()
|
|
if any(value is None for value in metadata):
|
|
raise KTArtifactError("routed-expert runtime ownership metadata is incomplete")
|
|
paths, module_refs, contracts = metadata
|
|
if not isinstance(paths, tuple) or not isinstance(module_refs, tuple) or not isinstance(contracts, tuple):
|
|
raise KTArtifactError("routed-expert runtime ownership metadata has invalid types")
|
|
|
|
enumerated = _enumerate_runtime_routed_modules(model)
|
|
expected_paths = tuple(path for path, _ in enumerated)
|
|
if paths != expected_paths:
|
|
raise KTArtifactError("routed-expert runtime ownership paths changed")
|
|
if len(module_refs) != len(paths) or len(contracts) != len(paths):
|
|
raise KTArtifactError("routed-expert runtime ownership metadata has inconsistent lengths")
|
|
|
|
validated = []
|
|
for index, ((path, current), claimed, contract) in enumerate(zip(enumerated, module_refs, contracts)):
|
|
if current is not claimed:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} changed module identity")
|
|
if getattr(current, _RUNTIME_MODULE_MARKER, False) is not True:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} lost its runtime ownership marker")
|
|
if _runtime_tensor_contract(path, current) != contract:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} changed its tensor contract")
|
|
validated.append((paths[index], current))
|
|
return tuple(validated)
|
|
|
|
|
|
def claim_kt_routed_expert_subtrees(model: Any) -> tuple[str, ...]:
|
|
"""Claim routed-expert subtrees that KT will own outside the framework state dict."""
|
|
|
|
existing = (
|
|
getattr(model, _RUNTIME_MODULE_PATHS, None),
|
|
getattr(model, _RUNTIME_MODULE_REFS, None),
|
|
getattr(model, _RUNTIME_TENSOR_CONTRACTS, None),
|
|
)
|
|
if existing != (None, None, None):
|
|
return tuple(path for path, _ in _validated_runtime_routed_modules(model))
|
|
|
|
modules = _enumerate_runtime_routed_modules(model)
|
|
if not modules:
|
|
return ()
|
|
for path, module in modules:
|
|
if getattr(module, _RUNTIME_MODULE_MARKER, False):
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} has an unowned runtime marker")
|
|
|
|
paths = tuple(path for path, _ in modules)
|
|
refs = tuple(module for _, module in modules)
|
|
contracts = tuple(_runtime_tensor_contract(path, module) for path, module in modules)
|
|
marked_parameters = []
|
|
try:
|
|
for _, module in modules:
|
|
setattr(module, _RUNTIME_MODULE_MARKER, True)
|
|
for parameter in module.parameters(recurse=True):
|
|
setattr(parameter, _RUNTIME_PARAMETER_MARKER, True)
|
|
marked_parameters.append(parameter)
|
|
setattr(model, _RUNTIME_MODULE_PATHS, paths)
|
|
setattr(model, _RUNTIME_MODULE_REFS, refs)
|
|
setattr(model, _RUNTIME_TENSOR_CONTRACTS, contracts)
|
|
except BaseException:
|
|
for parameter in marked_parameters:
|
|
with contextlib.suppress(AttributeError):
|
|
delattr(parameter, _RUNTIME_PARAMETER_MARKER)
|
|
for _, module in modules:
|
|
with contextlib.suppress(AttributeError):
|
|
delattr(module, _RUNTIME_MODULE_MARKER)
|
|
for name in (_RUNTIME_MODULE_PATHS, _RUNTIME_MODULE_REFS, _RUNTIME_TENSOR_CONTRACTS):
|
|
with contextlib.suppress(AttributeError):
|
|
delattr(model, name)
|
|
raise
|
|
return paths
|
|
|
|
|
|
def is_kt_routed_expert_base_parameter(parameter: Any) -> bool:
|
|
"""Whether a live base parameter belongs to a claimed KT routed-expert subtree."""
|
|
|
|
return getattr(parameter, _RUNTIME_PARAMETER_MARKER, False) is True
|
|
|
|
|
|
def mark_kt_int8_routed_expert_base_parameters(
|
|
model: Any, plan: KTPretrainedLoadPlan | None
|
|
) -> tuple[str, ...]:
|
|
"""Mark native routed-expert tensors omitted from a validated load plan."""
|
|
|
|
if plan is None:
|
|
return ()
|
|
config = getattr(model, "config", None)
|
|
if getattr(config, "model_type", None) != "deepseek_v3":
|
|
return ()
|
|
paths = _deepseek_routed_paths(config)
|
|
expected_shapes = {
|
|
"gate_up_proj": (
|
|
getattr(config, "n_routed_experts", None),
|
|
2 * getattr(config, "moe_intermediate_size", 0),
|
|
getattr(config, "hidden_size", None),
|
|
),
|
|
"down_proj": (
|
|
getattr(config, "n_routed_experts", None),
|
|
getattr(config, "hidden_size", None),
|
|
getattr(config, "moe_intermediate_size", None),
|
|
),
|
|
}
|
|
if any(any(isinstance(value, bool) or not isinstance(value, int) or value <= 0 for value in shape) for shape in expected_shapes.values()):
|
|
raise KTArtifactError("model config has invalid routed-expert dimensions")
|
|
modules = []
|
|
parameters = []
|
|
for path in paths:
|
|
try:
|
|
experts = model.get_submodule(path)
|
|
except (AttributeError, KeyError) as exc:
|
|
raise KTArtifactError(f"missing routed-expert subtree {path!r}") from exc
|
|
actual = dict(experts.named_parameters(recurse=True))
|
|
if set(actual) != set(expected_shapes):
|
|
raise KTArtifactError(
|
|
f"{path} parameter contract mismatch: expected={sorted(expected_shapes)}, actual={sorted(actual)}"
|
|
)
|
|
for name, shape in expected_shapes.items():
|
|
if tuple(actual[name].shape) != shape:
|
|
raise KTArtifactError(
|
|
f"{path}.{name} shape mismatch: expected={shape}, actual={tuple(actual[name].shape)}"
|
|
)
|
|
parameters.append(actual[name])
|
|
modules.append(experts)
|
|
runtime_paths = tuple(path for path, _ in _enumerate_runtime_routed_modules(model))
|
|
if runtime_paths != paths:
|
|
raise KTArtifactError(
|
|
"validated INT8 routed-expert paths differ from the runtime ownership contract: "
|
|
f"artifact={paths}, runtime={runtime_paths}"
|
|
)
|
|
if claim_kt_routed_expert_subtrees(model) != paths:
|
|
raise KTArtifactError("INT8 routed-expert ownership claim returned inconsistent paths")
|
|
for module in modules:
|
|
setattr(module, _MODULE_MARKER, True)
|
|
for parameter in parameters:
|
|
setattr(parameter, _PARAMETER_MARKER, True)
|
|
setattr(model, _MODULE_PATHS, paths)
|
|
return paths
|
|
|
|
|
|
def is_kt_int8_routed_expert_base_parameter(parameter: Any) -> bool:
|
|
return getattr(parameter, _PARAMETER_MARKER, False) is True
|
|
|
|
|
|
def _validated_routed_modules(model: Any) -> tuple[tuple[str, Any], ...]:
|
|
paths = getattr(model, _MODULE_PATHS, ())
|
|
if not paths:
|
|
return ()
|
|
if tuple(paths) != _deepseek_routed_paths(model.config):
|
|
raise KTArtifactError("routed-expert ownership metadata changed")
|
|
modules = []
|
|
for path in paths:
|
|
try:
|
|
module = model.get_submodule(path)
|
|
except (AttributeError, KeyError) as exc:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} is no longer registered") from exc
|
|
if getattr(module, _MODULE_MARKER, False) is not True:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} lost its ownership marker")
|
|
modules.append((path, module))
|
|
return tuple(modules)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def project_kt_routed_experts_out_of_device_map(model: Any) -> Iterator[None]:
|
|
"""Temporarily project KT-owned routed experts to zero-sized meta tensors."""
|
|
|
|
modules = _validated_runtime_routed_modules(model)
|
|
if not modules:
|
|
yield
|
|
return
|
|
import torch
|
|
|
|
replacements = []
|
|
try:
|
|
for path, experts in modules:
|
|
for module in experts.modules():
|
|
for name, parameter in tuple(module._parameters.items()):
|
|
if parameter is None:
|
|
continue
|
|
if parameter.device.type != "meta":
|
|
raise KTArtifactError(f"{path}.{name} must be meta before device-map inference")
|
|
projected = torch.nn.Parameter(
|
|
torch.empty(0, dtype=parameter.dtype, device="meta"),
|
|
requires_grad=parameter.requires_grad,
|
|
)
|
|
module._parameters[name] = projected
|
|
replacements.append((module._parameters, name, parameter))
|
|
for name, buffer in tuple(module._buffers.items()):
|
|
if buffer is None:
|
|
continue
|
|
if buffer.device.type != "meta":
|
|
raise KTArtifactError(f"{path}.{name} must be meta before device-map inference")
|
|
module._buffers[name] = torch.empty(0, dtype=buffer.dtype, device="meta")
|
|
replacements.append((module._buffers, name, buffer))
|
|
yield
|
|
finally:
|
|
for registry, name, original in reversed(replacements):
|
|
registry[name] = original
|
|
|
|
|
|
def prepare_kt_non_expert_device_map(model: Any, device_map: Any) -> Any:
|
|
"""Remove virtual expert placements and reject host offload of real tensors."""
|
|
|
|
modules = _validated_runtime_routed_modules(model)
|
|
if not modules:
|
|
return device_map
|
|
if not isinstance(device_map, dict):
|
|
raise KTArtifactError("non-expert placement requires a resolved device-map dictionary")
|
|
import torch
|
|
|
|
paths = tuple(path for path, _ in modules)
|
|
resolved = {
|
|
name: device
|
|
for name, device in device_map.items()
|
|
if not any(name == path or name.startswith(f"{path}.") for path in paths)
|
|
}
|
|
if not resolved:
|
|
raise KTArtifactError("device map became empty after removing routed experts")
|
|
|
|
def is_host(device: Any) -> bool:
|
|
if device == "disk":
|
|
return True
|
|
if isinstance(device, int):
|
|
return False
|
|
try:
|
|
return torch.device(device).type in {"cpu", "meta"}
|
|
except (RuntimeError, TypeError):
|
|
return False
|
|
|
|
host_entries = {name: device for name, device in resolved.items() if is_host(device)}
|
|
if host_entries:
|
|
raise KTArtifactError(
|
|
"non-expert device map offloaded real tensors to CPU/disk: "
|
|
+ ", ".join(f"{name or '<root>'}={device}" for name, device in sorted(host_entries.items()))
|
|
)
|
|
return resolved
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def hide_kt_routed_experts_from_dispatch(model: Any) -> Iterator[None]:
|
|
"""Temporarily unregister KT-owned subtrees from parent dispatch hooks."""
|
|
|
|
modules = _validated_runtime_routed_modules(model)
|
|
if not modules:
|
|
yield
|
|
return
|
|
import torch
|
|
|
|
replacements = []
|
|
try:
|
|
for path, experts in modules:
|
|
parent_path, child_name = path.rsplit(".", 1)
|
|
parent = model.get_submodule(parent_path)
|
|
if parent._modules.get(child_name) is not experts:
|
|
raise KTArtifactError(f"routed-expert subtree {path!r} changed before dispatch")
|
|
parent._modules[child_name] = torch.nn.Module()
|
|
replacements.append((parent, child_name, experts))
|
|
yield
|
|
finally:
|
|
for parent, child_name, experts in reversed(replacements):
|
|
parent._modules[child_name] = experts
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def project_kt_int8_routed_experts_out_of_device_map(model: Any) -> Iterator[None]:
|
|
"""Backward-compatible alias for the generic routed-expert projection contract."""
|
|
|
|
_validated_routed_modules(model)
|
|
with project_kt_routed_experts_out_of_device_map(model):
|
|
yield
|
|
|
|
|
|
def prepare_kt_int8_non_expert_device_map(model: Any, device_map: Any) -> Any:
|
|
"""Backward-compatible alias for generic KT non-expert placement."""
|
|
|
|
_validated_routed_modules(model)
|
|
return prepare_kt_non_expert_device_map(model, device_map)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def hide_kt_int8_routed_experts_from_dispatch(model: Any) -> Iterator[None]:
|
|
"""Backward-compatible alias for generic KT routed-expert dispatch hiding."""
|
|
|
|
_validated_routed_modules(model)
|
|
with hide_kt_routed_experts_from_dispatch(model):
|
|
yield
|
|
|
|
|
|
def _find_wrappers(model: Any) -> list[Any]:
|
|
queue = [model]
|
|
visited: set[int] = set()
|
|
while queue:
|
|
candidate = queue.pop(0)
|
|
if candidate is None or id(candidate) in visited:
|
|
continue
|
|
visited.add(id(candidate))
|
|
wrappers = getattr(candidate, "_kt_wrappers", None)
|
|
if wrappers is not None:
|
|
return list(wrappers)
|
|
for attribute in ("base_model", "model", "module"):
|
|
child = getattr(candidate, attribute, None)
|
|
if child is not None and child is not candidate:
|
|
queue.append(child)
|
|
return []
|
|
|
|
|
|
def _find_plan(model: Any) -> KTPretrainedLoadPlan | None:
|
|
queue = [model]
|
|
visited: set[int] = set()
|
|
while queue:
|
|
candidate = queue.pop(0)
|
|
if candidate is None or id(candidate) in visited:
|
|
continue
|
|
visited.add(id(candidate))
|
|
plan = getattr(candidate, "_kt_pretrained_load_plan", None)
|
|
if isinstance(plan, KTPretrainedLoadPlan):
|
|
return plan
|
|
for attribute in ("base_model", "model", "module"):
|
|
child = getattr(candidate, attribute, None)
|
|
if child is not None and child is not candidate:
|
|
queue.append(child)
|
|
return None
|
|
|
|
|
|
def _runtime_expert_weight_format(
|
|
model: Any, plan: KTPretrainedLoadPlan | None
|
|
) -> str:
|
|
"""Resolve routed-expert precision from explicit KT ownership metadata."""
|
|
|
|
provenance: list[tuple[str, Any]] = []
|
|
queue = [model]
|
|
visited: set[int] = set()
|
|
while queue:
|
|
candidate = queue.pop(0)
|
|
if candidate is None or id(candidate) in visited:
|
|
continue
|
|
visited.add(id(candidate))
|
|
value = getattr(candidate, "_kt_expert_weight_format", None)
|
|
if value is not None:
|
|
provenance.append((type(candidate).__name__, value))
|
|
config = getattr(candidate, "config", None)
|
|
value = getattr(config, "kt_expert_weight_format", None)
|
|
if value is not None:
|
|
provenance.append((f"{type(candidate).__name__}.config", value))
|
|
for attribute in ("base_model", "model", "module"):
|
|
child = getattr(candidate, attribute, None)
|
|
if child is not None and child is not candidate:
|
|
queue.append(child)
|
|
|
|
for wrapper in _find_wrappers(model):
|
|
value = getattr(wrapper, "_kt_expert_weight_format", None)
|
|
if value is not None:
|
|
provenance.append((f"KT wrapper layer {getattr(wrapper, 'layer_idx', '?')}", value))
|
|
|
|
if plan is not None:
|
|
provenance.append(
|
|
(
|
|
"routed expert manifest",
|
|
plan.routed_manifest.get("expert_weight_format"),
|
|
)
|
|
)
|
|
|
|
normalized: list[tuple[str, str]] = []
|
|
for source, value in provenance:
|
|
if not isinstance(value, str) or value.strip().lower() not in _EXPERT_WEIGHT_FORMATS:
|
|
raise KTArtifactError(f"invalid {source} expert_weight_format {value!r}")
|
|
normalized.append((source, value.strip().lower()))
|
|
formats = {value for _, value in normalized}
|
|
if len(formats) > 1:
|
|
details = ", ".join(f"{source}={value}" for source, value in normalized)
|
|
raise KTArtifactError(f"conflicting KT expert weight provenance: {details}")
|
|
if formats:
|
|
return formats.pop()
|
|
if plan is not None:
|
|
raise KTArtifactError("validated pretrained plan lacks routed expert format provenance")
|
|
# Older BF16 wrappers predate explicit ownership metadata. Quantized
|
|
# runtimes never take this compatibility path.
|
|
return "bf16"
|
|
|
|
|
|
def _runtime_rawint4_quantization(model: Any) -> dict[str, Any]:
|
|
"""Resolve and strictly validate RAWINT4 checkpoint/runtime provenance."""
|
|
|
|
expected = {
|
|
"bits": 4,
|
|
"group_size": 32,
|
|
"signed": True,
|
|
"symmetric": True,
|
|
"zero_point": False,
|
|
"format": "pack-quantized",
|
|
"weight_layout": RAWINT4_WEIGHT_LAYOUT,
|
|
}
|
|
provenance: list[tuple[str, Any]] = []
|
|
queue = [model]
|
|
visited: set[int] = set()
|
|
while queue:
|
|
candidate = queue.pop(0)
|
|
if candidate is None or id(candidate) in visited:
|
|
continue
|
|
visited.add(id(candidate))
|
|
value = getattr(candidate, "_kt_rawint4_quantization", None)
|
|
if value is not None:
|
|
provenance.append((type(candidate).__name__, value))
|
|
for attribute in ("base_model", "model", "module"):
|
|
child = getattr(candidate, attribute, None)
|
|
if child is not None and child is not candidate:
|
|
queue.append(child)
|
|
for wrapper in _find_wrappers(model):
|
|
value = getattr(wrapper, "_kt_rawint4_quantization", None)
|
|
if value is not None:
|
|
provenance.append((f"KT wrapper layer {getattr(wrapper, 'layer_idx', '?')}", value))
|
|
if not provenance:
|
|
raise KTArtifactError("RAWINT4 runtime lacks quantization provenance")
|
|
for source, value in provenance:
|
|
if not isinstance(value, Mapping) or set(value) != set(expected):
|
|
raise KTArtifactError(f"invalid {source} RAWINT4 quantization provenance {value!r}")
|
|
for name, expected_value in expected.items():
|
|
observed = value[name]
|
|
if type(observed) is not type(expected_value) or observed != expected_value:
|
|
raise KTArtifactError(
|
|
f"invalid {source} RAWINT4 {name}: expected " f"{expected_value!r}, got {observed!r}"
|
|
)
|
|
return expected
|
|
|
|
|
|
def _dtype_name(dtype: Any) -> str:
|
|
values = {
|
|
"torch.bfloat16": "BF16",
|
|
"torch.float16": "F16",
|
|
"torch.float32": "F32",
|
|
"torch.float64": "F64",
|
|
}
|
|
try:
|
|
return values[str(dtype)]
|
|
except KeyError as exc:
|
|
raise KTArtifactError(f"unsupported fused expert LoRA dtype {dtype}") from exc
|
|
|
|
|
|
def _wrapper_uses_fused_lora(wrapper: Any) -> bool:
|
|
rank = getattr(wrapper, "_lora_rank", 0)
|
|
return bool(
|
|
isinstance(rank, int)
|
|
and not isinstance(rank, bool)
|
|
and rank > 0
|
|
and (
|
|
getattr(wrapper, "_use_fused_expert_lora", False)
|
|
or getattr(wrapper, "_fused_experts", False)
|
|
or getattr(wrapper, "_fused_expert_lora_params", None) is not None
|
|
)
|
|
)
|
|
|
|
|
|
def _fused_lora_contract(model: Any) -> dict[str, dict[str, Any]]:
|
|
contract: dict[str, dict[str, Any]] = {}
|
|
seen_layers: set[int] = set()
|
|
for wrapper in _find_wrappers(model):
|
|
fused = getattr(wrapper, "_fused_expert_lora_params", None)
|
|
if not fused and not _wrapper_uses_fused_lora(wrapper):
|
|
continue
|
|
layer_idx = getattr(wrapper, "layer_idx", None)
|
|
if isinstance(layer_idx, bool) or not isinstance(layer_idx, int) or layer_idx in seen_layers:
|
|
raise KTArtifactError(f"invalid or duplicate KT wrapper layer {layer_idx!r}")
|
|
seen_layers.add(layer_idx)
|
|
if fused and len(fused) != len(_FUSED_LORA_NAMES):
|
|
raise KTArtifactError(
|
|
f"layer {layer_idx}: expected six fused LoRA parameters, got {len(fused)}"
|
|
)
|
|
if fused:
|
|
for name, parameter in zip(_FUSED_LORA_NAMES, fused):
|
|
contract[f"layers.{layer_idx}.experts.{name}"] = {
|
|
"shape": list(parameter.shape),
|
|
"dtype": _dtype_name(parameter.dtype),
|
|
}
|
|
continue
|
|
moe_config = getattr(wrapper, "moe_config", None)
|
|
expert_num = getattr(moe_config, "expert_num", None)
|
|
intermediate_size = getattr(moe_config, "intermediate_size", None)
|
|
hidden_size = getattr(wrapper, "hidden_size", None)
|
|
rank = getattr(wrapper, "_lora_rank", None)
|
|
dimensions = (expert_num, intermediate_size, hidden_size, rank)
|
|
if not all(
|
|
isinstance(value, int) and not isinstance(value, bool) and value > 0
|
|
for value in dimensions
|
|
):
|
|
raise KTArtifactError(f"layer {layer_idx}: cannot derive its fused LoRA contract")
|
|
shapes = (
|
|
(expert_num, rank, hidden_size),
|
|
(expert_num, intermediate_size, rank),
|
|
(expert_num, rank, hidden_size),
|
|
(expert_num, intermediate_size, rank),
|
|
(expert_num, rank, intermediate_size),
|
|
(expert_num, hidden_size, rank),
|
|
)
|
|
for name, shape in zip(_FUSED_LORA_NAMES, shapes):
|
|
contract[f"layers.{layer_idx}.experts.{name}"] = {
|
|
"shape": list(shape),
|
|
"dtype": "BF16",
|
|
}
|
|
return dict(sorted(contract.items()))
|
|
|
|
|
|
def _validate_fused_file(path: Path, contract: Mapping[str, Mapping[str, Any]]) -> None:
|
|
if path.is_symlink() or not path.is_file():
|
|
raise KTArtifactError(f"missing regular fused expert LoRA file: {path}")
|
|
with safe_open(path, framework="pt", device="cpu") as handle:
|
|
actual = set(handle.keys())
|
|
expected = set(contract)
|
|
if actual != expected:
|
|
raise KTArtifactError(
|
|
f"invalid fused LoRA keys: missing={sorted(expected - actual)}, "
|
|
f"unexpected={sorted(actual - expected)}"
|
|
)
|
|
for key, expected_tensor in contract.items():
|
|
tensor = handle.get_slice(key)
|
|
if list(tensor.get_shape()) != expected_tensor["shape"]:
|
|
raise KTArtifactError(f"{key} shape does not match the runtime")
|
|
if tensor.get_dtype() != expected_tensor["dtype"]:
|
|
raise KTArtifactError(f"{key} dtype does not match the runtime")
|
|
|
|
|
|
def _artifact_record(path: Path, contract: Mapping[str, Any] | None = None) -> dict[str, Any]:
|
|
if path.is_symlink() or not path.is_file():
|
|
raise KTArtifactError(f"adapter artifact must be a regular file: {path}")
|
|
record: dict[str, Any] = {"size": path.stat().st_size, "sha256": _sha256_file(path)}
|
|
if contract is not None:
|
|
record.update({"tensor_count": len(contract), "tensors": dict(contract)})
|
|
return record
|
|
|
|
|
|
def _base_model_name(model: Any, plan: KTPretrainedLoadPlan | None) -> str | None:
|
|
if plan is not None:
|
|
return plan.source_model_name_or_path
|
|
queue = [model]
|
|
visited: set[int] = set()
|
|
while queue:
|
|
candidate = queue.pop(0)
|
|
if candidate is None or id(candidate) in visited:
|
|
continue
|
|
visited.add(id(candidate))
|
|
config = getattr(candidate, "config", None)
|
|
value = getattr(config, "_name_or_path", None) or getattr(config, "name_or_path", None)
|
|
if value:
|
|
return os.fspath(value)
|
|
for attribute in ("base_model", "model", "module"):
|
|
child = getattr(candidate, attribute, None)
|
|
if child is not None and child is not candidate:
|
|
queue.append(child)
|
|
return None
|
|
|
|
|
|
def _adapter_provenance(model: Any, plan: KTPretrainedLoadPlan | None) -> dict[str, Any]:
|
|
wrappers = [wrapper for wrapper in _find_wrappers(model) if _wrapper_uses_fused_lora(wrapper)]
|
|
ranks = {getattr(wrapper, "_lora_rank", None) for wrapper in wrappers}
|
|
if len(ranks) != 1 or isinstance(next(iter(ranks), None), bool) or not isinstance(next(iter(ranks), None), int):
|
|
raise KTArtifactError(f"fused wrappers have inconsistent LoRA ranks: {sorted(ranks, key=str)}")
|
|
alphas = {
|
|
getattr(wrapper, "_lora_alpha", getattr(getattr(wrapper, "wrapper", None), "lora_alpha", None))
|
|
for wrapper in wrappers
|
|
}
|
|
alpha = next(iter(alphas), None)
|
|
if (
|
|
len(alphas) != 1
|
|
or isinstance(alpha, bool)
|
|
or not isinstance(alpha, (int, float))
|
|
or not math.isfinite(float(alpha))
|
|
or float(alpha) <= 0
|
|
):
|
|
raise KTArtifactError(f"fused wrappers have invalid or inconsistent LoRA alpha: {sorted(alphas, key=str)}")
|
|
rank = next(iter(ranks))
|
|
if plan is not None and plan.lora_rank is not None and plan.lora_rank != rank:
|
|
raise KTArtifactError(
|
|
f"fused wrapper LoRA rank {rank} does not match the pretrained plan rank {plan.lora_rank}"
|
|
)
|
|
if plan is not None and plan.lora_alpha is not None and float(plan.lora_alpha) != float(alpha):
|
|
raise KTArtifactError(
|
|
f"fused wrapper LoRA alpha {float(alpha)} does not match the pretrained plan alpha {plan.lora_alpha}"
|
|
)
|
|
expert_weight_format = _runtime_expert_weight_format(model, plan)
|
|
if plan is not None and expert_weight_format not in {"int8", "mxfp4"}:
|
|
raise KTArtifactError(
|
|
"a KT non-expert load plan requires INT8 or MXFP4 routed expert provenance"
|
|
)
|
|
base_model_name = _base_model_name(model, plan)
|
|
payload: dict[str, Any] = {
|
|
"expert_weight_format": expert_weight_format,
|
|
"base": {"model_name_or_path": base_model_name},
|
|
"lora": {"rank": rank, "alpha": float(alpha)},
|
|
}
|
|
if expert_weight_format == "rawint4":
|
|
payload["expert_quantization"] = _runtime_rawint4_quantization(model)
|
|
if not isinstance(base_model_name, str) or not base_model_name:
|
|
raise KTArtifactError("RAWINT4 adapter provenance requires a local base checkpoint path")
|
|
payload["base"] = _rawint4_base_identity(base_model_name)
|
|
if plan is not None:
|
|
source = plan.manifest.get("source")
|
|
if not isinstance(source, Mapping):
|
|
raise KTArtifactError("validated non-expert manifest lost its source provenance")
|
|
payload["base"]["fingerprint"] = _require_sha256(
|
|
source.get("fingerprint"), "source.fingerprint", Path(plan.manifest_path)
|
|
)
|
|
cache_fingerprint = _require_sha256(
|
|
plan.manifest.get("fingerprint"), "fingerprint", Path(plan.manifest_path)
|
|
)
|
|
routed_path = Path(plan.routed_manifest_path)
|
|
payload["non_expert_cache"] = {
|
|
"path": plan.weight_path,
|
|
"fingerprint": cache_fingerprint,
|
|
}
|
|
routed_field = "native_experts" if expert_weight_format == "mxfp4" else "int8_experts"
|
|
payload[routed_field] = {
|
|
"path": plan.routed_weight_path,
|
|
"manifest": routed_path.name,
|
|
"manifest_sha256": _sha256_file(routed_path),
|
|
"fingerprint": plan.routed_manifest.get("fingerprint") or _sha256_file(routed_path),
|
|
}
|
|
return payload
|
|
|
|
|
|
def save_kt_adapter_artifacts(
|
|
model: Any, output_dir: str | os.PathLike[str]
|
|
) -> KTAdapterManifest | None:
|
|
"""Atomically publish KT adapter files and a ready manifest."""
|
|
|
|
from .lora import save_kt_moe_to_adapter
|
|
|
|
contract = _fused_lora_contract(model)
|
|
output = Path(output_dir).absolute()
|
|
if output.is_symlink():
|
|
raise KTArtifactError(f"adapter output must not be a symlink: {output}")
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
manifest_path = output / KT_ADAPTER_MANIFEST_NAME
|
|
# Invalidate an earlier ready marker before replacing bundle members.
|
|
_invalidate_ready_manifest(manifest_path)
|
|
if not contract:
|
|
save_kt_moe_to_adapter(model, str(output))
|
|
return None
|
|
adapter_config = output / "adapter_config.json"
|
|
standard = [name for name in _STANDARD_ADAPTER_NAMES if (output / name).is_file()]
|
|
if adapter_config.is_symlink() or not adapter_config.is_file():
|
|
raise KTArtifactError(f"KT adapter save requires {adapter_config}")
|
|
if len(standard) != 1:
|
|
raise KTArtifactError(f"KT adapter save requires exactly one standard adapter file, got {standard}")
|
|
plan = _find_plan(model)
|
|
if plan is not None:
|
|
current_cache = _read_json(Path(plan.manifest_path), "KT non-expert cache manifest")
|
|
current_routed = _read_json(Path(plan.routed_manifest_path), "routed INT8 manifest")
|
|
if current_cache != dict(plan.manifest) or current_routed != dict(plan.routed_manifest):
|
|
raise KTArtifactError("KT pretrained artifact provenance changed after model loading")
|
|
|
|
staging = Path(tempfile.mkdtemp(dir=output, prefix=".kt-adapter-stage."))
|
|
try:
|
|
shutil.copy2(output / standard[0], staging / standard[0])
|
|
save_kt_moe_to_adapter(model, str(staging))
|
|
fused = staging / FUSED_EXPERT_LORA_NAME
|
|
_validate_fused_file(fused, contract)
|
|
produced = []
|
|
for entry in staging.iterdir():
|
|
if entry.is_symlink() or not entry.is_file():
|
|
raise KTArtifactError(f"unsupported staged adapter entry: {entry}")
|
|
with entry.open("rb") as handle:
|
|
os.fsync(handle.fileno())
|
|
produced.append(entry.name)
|
|
if FUSED_EXPERT_LORA_NAME not in produced:
|
|
raise KTArtifactError(f"staging did not produce {FUSED_EXPERT_LORA_NAME}")
|
|
for name in sorted(produced):
|
|
os.replace(staging / name, output / name)
|
|
_fsync_directory(output)
|
|
artifacts = {
|
|
"adapter_config.json": _artifact_record(adapter_config),
|
|
}
|
|
for name in sorted(produced):
|
|
artifacts[name] = _artifact_record(
|
|
output / name, contract if name == FUSED_EXPERT_LORA_NAME else None
|
|
)
|
|
payload = {
|
|
"version": KT_ADAPTER_MANIFEST_VERSION,
|
|
"status": "ready",
|
|
**_adapter_provenance(model, plan),
|
|
"artifacts": artifacts,
|
|
}
|
|
_write_json_atomic(manifest_path, payload)
|
|
return KTAdapterManifest(
|
|
path=str(manifest_path),
|
|
payload=payload,
|
|
artifact_paths=tuple(str(output / name) for name in sorted(artifacts)),
|
|
)
|
|
finally:
|
|
shutil.rmtree(staging, ignore_errors=True)
|
|
|
|
|
|
def _validate_adapter_manifest(model: Any, adapter_path: Path) -> KTAdapterManifest:
|
|
manifest_path = adapter_path / KT_ADAPTER_MANIFEST_NAME
|
|
payload = _read_json(manifest_path, "KT adapter manifest")
|
|
if payload.get("version") != KT_ADAPTER_MANIFEST_VERSION or payload.get("status") != "ready":
|
|
raise KTArtifactError(f"{manifest_path}: expected version=1 and status='ready'")
|
|
plan = _find_plan(model)
|
|
expected_provenance = _adapter_provenance(model, plan)
|
|
saved_format = payload.get("expert_weight_format")
|
|
if (
|
|
saved_format is None
|
|
and "non_expert_cache" in payload
|
|
and "int8_experts" in payload
|
|
):
|
|
# LLaMA-Factory's legacy schema-v1 writer predated the explicit format
|
|
# field; its INT8 provenance fields are unambiguous.
|
|
saved_format = "int8"
|
|
if saved_format != expected_provenance["expert_weight_format"]:
|
|
raise KTArtifactError(f"{manifest_path}: expert_weight_format does not match the runtime")
|
|
if saved_format == "rawint4":
|
|
_validate_rawint4_base_identity(
|
|
payload.get("base"), expected_provenance["base"], manifest_path
|
|
)
|
|
elif payload.get("base") != expected_provenance["base"]:
|
|
# Keep the existing BF16/FP8/INT8 path-bound contract unchanged.
|
|
raise KTArtifactError(f"{manifest_path}: base does not match the runtime")
|
|
if payload.get("lora") != expected_provenance["lora"]:
|
|
raise KTArtifactError(f"{manifest_path}: lora does not match the runtime")
|
|
for field in ("non_expert_cache", "int8_experts", "native_experts", "expert_quantization"):
|
|
if payload.get(field) != expected_provenance.get(field):
|
|
raise KTArtifactError(f"{manifest_path}: {field} does not match the runtime")
|
|
artifacts = payload.get("artifacts")
|
|
if not isinstance(artifacts, dict) or FUSED_EXPERT_LORA_NAME not in artifacts:
|
|
raise KTArtifactError(f"{manifest_path}: artifacts must include fused expert LoRA")
|
|
standard = {name for name in artifacts if name in _STANDARD_ADAPTER_NAMES}
|
|
if len(standard) != 1 or "adapter_config.json" not in artifacts:
|
|
raise KTArtifactError(f"{manifest_path}: invalid standard adapter inventory")
|
|
paths = []
|
|
for name, record in artifacts.items():
|
|
if name != os.path.basename(name) or not isinstance(record, dict):
|
|
raise KTArtifactError(f"{manifest_path}: invalid artifact record {name!r}")
|
|
path = adapter_path / name
|
|
if record.get("size") != (path.stat().st_size if path.is_file() and not path.is_symlink() else None):
|
|
raise KTArtifactError(f"{manifest_path}: size mismatch for {name}")
|
|
digest = record.get("sha256")
|
|
if not isinstance(digest, str) or _sha256_file(path) != digest:
|
|
raise KTArtifactError(f"{manifest_path}: SHA256 mismatch for {name}")
|
|
paths.append(str(path))
|
|
contract = _fused_lora_contract(model)
|
|
fused_record = artifacts[FUSED_EXPERT_LORA_NAME]
|
|
if fused_record.get("tensor_count") != len(contract) or fused_record.get("tensors") != contract:
|
|
raise KTArtifactError(f"{manifest_path}: fused tensor contract does not match the runtime")
|
|
_validate_fused_file(adapter_path / FUSED_EXPERT_LORA_NAME, contract)
|
|
return KTAdapterManifest(str(manifest_path), payload, tuple(sorted(paths)))
|
|
|
|
|
|
def load_kt_adapter_artifacts(
|
|
model: Any, adapter_path: str | os.PathLike[str]
|
|
) -> KTAdapterManifest | None:
|
|
"""Restore KT state after PEFT load without allocating on non-owner ranks."""
|
|
|
|
from .lora import kt_adapt_peft_lora, load_kt_moe_from_adapter
|
|
|
|
root = _safe_root(adapter_path, "KT adapter")
|
|
if any(getattr(wrapper, "wrapper", None) is not None for wrapper in _find_wrappers(model)):
|
|
kt_adapt_peft_lora(model)
|
|
contract = _fused_lora_contract(model)
|
|
manifest_path = root / KT_ADAPTER_MANIFEST_NAME
|
|
manifest = None
|
|
if manifest_path.exists():
|
|
manifest = _validate_adapter_manifest(model, root)
|
|
elif contract:
|
|
raise KTArtifactError(f"fused adapter is missing {manifest_path}")
|
|
load_kt_moe_from_adapter(model, str(root))
|
|
return manifest
|
|
|
|
|
|
__all__ = [
|
|
"FUSED_EXPERT_LORA_NAME",
|
|
"KT_ADAPTER_MANIFEST_NAME",
|
|
"KT_NON_EXPERT_MANIFEST_NAME",
|
|
"KTAdapterManifest",
|
|
"KTArtifactError",
|
|
"KTNonExpertCache",
|
|
"KTPretrainedLoadPlan",
|
|
"claim_kt_routed_expert_subtrees",
|
|
"hide_kt_int8_routed_experts_from_dispatch",
|
|
"hide_kt_routed_experts_from_dispatch",
|
|
"is_kt_int8_routed_expert_base_parameter",
|
|
"is_kt_routed_expert_base_parameter",
|
|
"is_kt_routed_expert_parameter_name",
|
|
"is_kt_supported_moe_model",
|
|
"get_kt_fused_lora_exclude_modules",
|
|
"load_kt_adapter_artifacts",
|
|
"mark_kt_int8_routed_expert_base_parameters",
|
|
"prepare_kt_int8_non_expert_device_map",
|
|
"prepare_kt_non_expert_device_map",
|
|
"project_kt_int8_routed_experts_out_of_device_map",
|
|
"project_kt_routed_experts_out_of_device_map",
|
|
"resolve_kt_pretrained_artifacts",
|
|
"save_kt_adapter_artifacts",
|
|
"should_disable_kt_source_quantizer",
|
|
"validate_kt_prequantized_loading_info",
|
|
"validate_kt_pretrained_load",
|
|
"write_kt_non_expert_cache_manifest",
|
|
]
|