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>
474 lines
21 KiB
Python
474 lines
21 KiB
Python
"""Selective loading of native DeepSeek V4 MXFP4 checkpoints for LoRA SFT."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import fcntl
|
|
import logging
|
|
import shutil
|
|
import stat
|
|
import math
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import torch
|
|
from safetensors import safe_open
|
|
from safetensors.torch import save_file
|
|
|
|
from .artifacts import (
|
|
KTArtifactError,
|
|
KTPretrainedLoadPlan,
|
|
KT_NON_EXPERT_INDEX_NAME,
|
|
KT_NON_EXPERT_MANIFEST_NAME,
|
|
_canonical_json_sha256,
|
|
_config_value,
|
|
_distributed_validation_context,
|
|
_fsync_directory,
|
|
_rawint4_shard_record,
|
|
_read_json,
|
|
_safe_root,
|
|
_sha256_file,
|
|
_synchronize_artifact_validation,
|
|
_write_json_atomic,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_KIND = "deepseek-v4-non-expert-bf16"
|
|
_PREPARATION_NAME = ".kt-v4-cache-owner.json"
|
|
_FP8_DTYPES = {"F8_E4M3", "F8_E4M3FN"}
|
|
|
|
|
|
def canonical_non_expert_key(key: str) -> str:
|
|
"""Map the native April checkpoint to Transformers' V4 module names."""
|
|
if key.startswith("mtp.") or ".experts." in key:
|
|
raise KTArtifactError(f"not a non-expert base-model tensor: {key}")
|
|
heads = {
|
|
"embed.weight": "model.embed_tokens.weight",
|
|
"head.weight": "lm_head.weight",
|
|
"norm.weight": "model.norm.weight",
|
|
"hc_head_fn": "model.hc_head.hc_fn",
|
|
"hc_head_base": "model.hc_head.hc_base",
|
|
"hc_head_scale": "model.hc_head.hc_scale",
|
|
}
|
|
if key in heads:
|
|
return heads[key]
|
|
if not key.startswith("layers."):
|
|
raise KTArtifactError(f"unsupported V4 checkpoint key: {key}")
|
|
key = "model." + key
|
|
replacements = (
|
|
(".attn.", ".self_attn."),
|
|
(".ffn.", ".mlp."),
|
|
(".indexer.compressor.", ".compressor.indexer."),
|
|
(".indexer.weights_proj.", ".compressor.indexer.scorer.weights_proj."),
|
|
(".indexer.wq_b.", ".compressor.indexer.q_b_proj."),
|
|
(".attn_norm.", ".input_layernorm."),
|
|
(".ffn_norm.", ".post_attention_layernorm."),
|
|
(".attn_sink", ".sinks"),
|
|
(".q_norm.", ".q_a_norm."),
|
|
(".norm.", ".kv_norm."),
|
|
(".wq_a.", ".q_a_proj."),
|
|
(".wq_b.", ".q_b_proj."),
|
|
(".wkv.", ".kv_proj."),
|
|
(".wgate.", ".gate_proj."),
|
|
(".wo_a.", ".o_a_proj."),
|
|
(".wo_b.", ".o_b_proj."),
|
|
(".shared_experts.w1.", ".shared_experts.gate_proj."),
|
|
(".shared_experts.w2.", ".shared_experts.down_proj."),
|
|
(".shared_experts.w3.", ".shared_experts.up_proj."),
|
|
(".gate.bias", ".gate.e_score_correction_bias"),
|
|
(".ape", ".position_bias"),
|
|
)
|
|
for old, new in replacements:
|
|
key = key.replace(old, new)
|
|
for native, target in (("hc_attn", "attn_hc"), ("hc_ffn", "ffn_hc")):
|
|
for suffix in ("fn", "base", "scale"):
|
|
key = key.replace(f".{native}_{suffix}", f".{target}.{suffix}")
|
|
return key
|
|
|
|
|
|
def dequantize_fp8_blocks(weight: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
|
|
"""Decode 128x128 E4M3 blocks; UE8M0 stores the multiplier, not its inverse."""
|
|
if weight.dtype != torch.float8_e4m3fn or scale.dtype != torch.float8_e8m0fnu:
|
|
raise KTArtifactError("expected E4M3 weights and UE8M0 scales")
|
|
if weight.ndim != 2 or tuple(scale.shape) != tuple(
|
|
math.ceil(d / 128) for d in weight.shape
|
|
):
|
|
raise KTArtifactError("invalid V4 non-expert FP8 weight/scale shape")
|
|
scales = scale.float()
|
|
if not torch.isfinite(scales).all() or not (scales > 0).all():
|
|
raise KTArtifactError("non-finite or non-positive FP8 scales")
|
|
result = torch.empty(weight.shape, dtype=torch.bfloat16)
|
|
for row in range(0, weight.shape[0], 128):
|
|
block = weight[row : row + 128].float()
|
|
multipliers = scales[row // 128].repeat_interleave(128)[: weight.shape[1]]
|
|
block.mul_(multipliers)
|
|
if not torch.isfinite(block).all():
|
|
raise KTArtifactError("non-finite FP8 non-expert weight")
|
|
result[row : row + 128].copy_(block)
|
|
if not torch.isfinite(result[row : row + 128]).all():
|
|
raise KTArtifactError("FP8 non-expert weight overflows BF16")
|
|
return result
|
|
|
|
|
|
def inspect_native_checkpoint(source: str | os.PathLike[str]) -> dict:
|
|
"""Validate expert headers without materializing the 148 GiB expert payload."""
|
|
root = _safe_root(source, "DeepSeek V4 checkpoint")
|
|
config = _read_json(root / "config.json", "DeepSeek V4 config")
|
|
if config.get("model_type") != "deepseek_v4":
|
|
raise KTArtifactError("native MXFP4 SFT currently requires DeepSeek V4")
|
|
quant = config.get("quantization_config", {})
|
|
if (
|
|
quant.get("quant_method") != "fp8"
|
|
or quant.get("fmt") != "e4m3"
|
|
or quant.get("weight_block_size") != [128, 128]
|
|
or quant.get("scale_fmt") != "ue8m0"
|
|
):
|
|
raise KTArtifactError("expected the native V4 FP8/UE8M0 non-expert checkpoint")
|
|
index = _read_json(root / KT_NON_EXPERT_INDEX_NAME, "native safetensors index")
|
|
weight_map = index.get("weight_map", {})
|
|
if not weight_map or any(
|
|
not isinstance(v, str) or Path(v).name != v for v in weight_map.values()
|
|
):
|
|
raise KTArtifactError("invalid native checkpoint shard inventory")
|
|
dimensions = [
|
|
config.get(k)
|
|
for k in (
|
|
"num_hidden_layers",
|
|
"n_routed_experts",
|
|
"hidden_size",
|
|
"moe_intermediate_size",
|
|
)
|
|
]
|
|
if any(type(value) is not int or value <= 0 for value in dimensions):
|
|
raise KTArtifactError("native V4 dimensions must be positive integers")
|
|
layers, experts, hidden, inter = dimensions
|
|
if min(layers, experts, hidden, inter) <= 0 or hidden % 32 or inter % 32:
|
|
raise KTArtifactError("invalid MXFP4 routed-expert dimensions")
|
|
expected = {}
|
|
for layer in range(layers):
|
|
for expert in range(experts):
|
|
for proj, (n, k) in {
|
|
"w1": (inter, hidden),
|
|
"w3": (inter, hidden),
|
|
"w2": (hidden, inter),
|
|
}.items():
|
|
prefix = f"layers.{layer}.ffn.experts.{expert}.{proj}"
|
|
expected[prefix + ".weight"] = ([n, k // 2], {"I8", "U8"})
|
|
expected[prefix + ".scale"] = ([n, k // 32], {"F8_E8M0"})
|
|
inventory, shard_records = {}, []
|
|
for name in sorted(set(weight_map.values())):
|
|
path = root / name
|
|
# Header + sampled payload identity is also used by the native INT4 contract.
|
|
shard_records.append(_rawint4_shard_record(path))
|
|
with safe_open(path, framework="pt", device="cpu") as handle:
|
|
for key in handle.keys():
|
|
if weight_map.get(key) != name or key in inventory:
|
|
raise KTArtifactError(f"native shard/index mismatch: {key}")
|
|
tensor = handle.get_slice(key)
|
|
shape, dtype = tensor.get_shape(), tensor.get_dtype()
|
|
inventory[key] = {"shape": shape, "dtype": dtype}
|
|
if key in expected:
|
|
expected_shape, expected_dtypes = expected[key]
|
|
if shape != expected_shape or dtype not in expected_dtypes:
|
|
raise KTArtifactError(
|
|
f"invalid native MXFP4 tensor {key}: {shape}, {dtype}"
|
|
)
|
|
elif ".experts." in key and not key.startswith("mtp."):
|
|
raise KTArtifactError(f"unexpected routed-expert tensor: {key}")
|
|
if set(inventory) != set(weight_map) or not set(expected).issubset(inventory):
|
|
raise KTArtifactError("incomplete native MXFP4 checkpoint")
|
|
identity = {
|
|
"config_sha256": _sha256_file(root / "config.json"),
|
|
"index_sha256": _sha256_file(root / KT_NON_EXPERT_INDEX_NAME),
|
|
"shards": shard_records,
|
|
}
|
|
return {
|
|
"model_name_or_path": str(root),
|
|
"fingerprint": _canonical_json_sha256(identity),
|
|
"identity": identity,
|
|
"config": config,
|
|
"inventory": inventory,
|
|
"weight_map": weight_map,
|
|
}
|
|
|
|
|
|
def _expected_cache_inventory(source: dict) -> dict:
|
|
result = {}
|
|
for key, spec in source["inventory"].items():
|
|
if key.startswith("mtp.") or ".experts." in key:
|
|
continue
|
|
if key.endswith(".scale"):
|
|
weight = key.removesuffix(".scale") + ".weight"
|
|
if source["inventory"].get(weight, {}).get("dtype") not in _FP8_DTYPES:
|
|
raise KTArtifactError(f"orphan non-expert FP8 scale: {key}")
|
|
continue
|
|
dtype = spec["dtype"]
|
|
if dtype in _FP8_DTYPES:
|
|
scale = key.removesuffix(".weight") + ".scale"
|
|
if source["inventory"].get(scale, {}).get("dtype") != "F8_E8M0":
|
|
raise KTArtifactError(f"missing native FP8 scale: {key}")
|
|
dtype = "BF16"
|
|
if dtype not in {"BF16", "F32", "I64"}:
|
|
raise KTArtifactError(f"unsupported non-expert dtype {dtype}: {key}")
|
|
target = canonical_non_expert_key(key)
|
|
if target in result:
|
|
raise KTArtifactError(f"duplicate converted key: {target}")
|
|
result[target] = {"shape": spec["shape"], "dtype": dtype}
|
|
return result
|
|
|
|
|
|
def _convert_non_expert_cache(source: dict, output: Path) -> dict:
|
|
"""Write only inside a private staging directory owned by the cache lock."""
|
|
source_path = source["model_name_or_path"]
|
|
expected = _expected_cache_inventory(source)
|
|
weight_map, records = {}, []
|
|
root = Path(source["model_name_or_path"])
|
|
for shard in sorted(set(source["weight_map"].values())):
|
|
tensors = {}
|
|
with safe_open(root / shard, framework="pt", device="cpu") as handle:
|
|
for key in handle.keys():
|
|
if (
|
|
key.startswith("mtp.")
|
|
or ".experts." in key
|
|
or key.endswith(".scale")
|
|
):
|
|
continue
|
|
target = canonical_non_expert_key(key)
|
|
tensor = handle.get_tensor(key)
|
|
if source["inventory"][key]["dtype"] in _FP8_DTYPES:
|
|
scale_key = key.removesuffix(".weight") + ".scale"
|
|
scale_shard = source["weight_map"][scale_key]
|
|
with safe_open(
|
|
root / scale_shard, framework="pt", device="cpu"
|
|
) as scales:
|
|
tensor = dequantize_fp8_blocks(
|
|
tensor, scales.get_tensor(scale_key)
|
|
)
|
|
elif tensor.is_floating_point() and not torch.isfinite(tensor).all():
|
|
raise KTArtifactError(f"non-finite source tensor: {key}")
|
|
tensors[target] = tensor.contiguous()
|
|
if not tensors:
|
|
continue
|
|
name = f"non-expert-{len(records):03d}.safetensors"
|
|
save_file(tensors, output / name, metadata={"format": "pt"})
|
|
records.append(
|
|
{
|
|
"name": name,
|
|
"size": (output / name).stat().st_size,
|
|
"sha256": _sha256_file(output / name),
|
|
}
|
|
)
|
|
weight_map.update({key: name for key in tensors})
|
|
print(f"converted {shard}: {len(tensors)} non-expert tensors", flush=True)
|
|
if set(weight_map) != set(expected):
|
|
raise KTArtifactError("converted cache is missing non-expert tensors")
|
|
_write_json_atomic(
|
|
output / KT_NON_EXPERT_INDEX_NAME, {"metadata": {}, "weight_map": weight_map}
|
|
)
|
|
if source["identity"] != inspect_native_checkpoint(source_path)["identity"]:
|
|
raise KTArtifactError("source checkpoint changed during conversion")
|
|
payload = {
|
|
"kind": _KIND,
|
|
"version": 1,
|
|
"status": "ready",
|
|
"source": {
|
|
key: source[key]
|
|
for key in ("model_name_or_path", "fingerprint", "identity")
|
|
},
|
|
"expert_weight_format": "mxfp4",
|
|
"files": records,
|
|
"tensors": expected,
|
|
"index_sha256": _sha256_file(output / KT_NON_EXPERT_INDEX_NAME),
|
|
}
|
|
payload["fingerprint"] = _canonical_json_sha256(payload)
|
|
_write_json_atomic(output / KT_NON_EXPERT_MANIFEST_NAME, payload)
|
|
return payload
|
|
|
|
|
|
def _validate_non_expert_cache(source: dict, root: Path, *, verify_payload: bool = True) -> dict:
|
|
manifest_path = root / KT_NON_EXPERT_MANIFEST_NAME
|
|
manifest = _read_json(manifest_path, "V4 non-expert cache manifest")
|
|
if (manifest.get("kind"), manifest.get("version"), manifest.get("status")) != (
|
|
_KIND,
|
|
1,
|
|
"ready",
|
|
):
|
|
raise KTArtifactError("invalid V4 cache ready manifest")
|
|
body = {key: value for key, value in manifest.items() if key != "fingerprint"}
|
|
if manifest.get("fingerprint") != _canonical_json_sha256(body):
|
|
raise KTArtifactError("V4 cache manifest fingerprint mismatch")
|
|
if manifest.get("source") != {
|
|
key: source[key]
|
|
for key in ("model_name_or_path", "fingerprint", "identity")
|
|
}:
|
|
raise KTArtifactError("native V4 checkpoint changed after cache conversion")
|
|
expected = _expected_cache_inventory(source)
|
|
if manifest.get("tensors") != expected:
|
|
raise KTArtifactError("V4 cache tensor inventory differs from its source")
|
|
index_path = root / KT_NON_EXPERT_INDEX_NAME
|
|
if manifest.get("index_sha256") != _sha256_file(index_path):
|
|
raise KTArtifactError("V4 cache index hash mismatch")
|
|
index = _read_json(index_path, "V4 cache index")["weight_map"]
|
|
observed, files = {}, []
|
|
for record in manifest["files"]:
|
|
name = record["name"]
|
|
if Path(name).name != name or name in files:
|
|
raise KTArtifactError("invalid V4 cache shard name")
|
|
path = root / name
|
|
if (
|
|
path.is_symlink()
|
|
or not path.is_file()
|
|
or path.stat().st_size != record["size"]
|
|
):
|
|
raise KTArtifactError(f"invalid V4 cache shard: {path}")
|
|
if verify_payload and _sha256_file(path) != record["sha256"]:
|
|
raise KTArtifactError(f"V4 cache payload hash mismatch: {path}")
|
|
with safe_open(path, framework="pt", device="cpu") as handle:
|
|
for key in handle.keys():
|
|
tensor = handle.get_slice(key)
|
|
if key in observed or index.get(key) != name:
|
|
raise KTArtifactError(
|
|
f"duplicate or unindexed cache tensor: {key}"
|
|
)
|
|
observed[key] = {
|
|
"shape": tensor.get_shape(),
|
|
"dtype": tensor.get_dtype(),
|
|
}
|
|
files.append(name)
|
|
if (
|
|
observed != expected
|
|
or set(index) != set(expected)
|
|
or set(index.values()) != set(files)
|
|
):
|
|
raise KTArtifactError("V4 cache payload inventory mismatch")
|
|
return manifest
|
|
|
|
|
|
def _ensure_non_expert_cache(source: dict, output_path: str, *, verify_payload: bool = True) -> dict:
|
|
if not output_path:
|
|
raise KTArtifactError("Set kt_non_expert_weight_path to a writable non-expert cache directory")
|
|
output = Path(output_path).expanduser().absolute()
|
|
source_root = Path(source["model_name_or_path"])
|
|
resolved_output = output.resolve()
|
|
if resolved_output.is_relative_to(source_root) or source_root.is_relative_to(resolved_output):
|
|
raise KTArtifactError("V4 non-expert cache must not overlap the source checkpoint")
|
|
if output.is_symlink():
|
|
raise KTArtifactError(f"V4 cache must be a real directory: {output}")
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
# Never unlink the lock: all processes must keep locking the same inode.
|
|
lock_path = output.with_name(f".{output.name}.kt-v4-cache.lock")
|
|
flags = os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW
|
|
descriptor = os.open(lock_path, flags, 0o600)
|
|
with os.fdopen(descriptor, "r+") as lock:
|
|
if not stat.S_ISREG(os.fstat(lock.fileno()).st_mode):
|
|
raise KTArtifactError(f"V4 cache lock must be a regular file: {lock_path}")
|
|
logger.info("Waiting for V4 non-expert cache lock: %s", lock_path)
|
|
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
if output.is_symlink() or (output.exists() and not output.is_dir()):
|
|
raise KTArtifactError(f"V4 cache must be a real directory: {output}")
|
|
if output.exists() and any(output.iterdir()):
|
|
# Existing caches are immutable. Never silently replace damaged or unrelated data.
|
|
return _validate_non_expert_cache(source, output, verify_payload=verify_payload)
|
|
|
|
staging = output.with_name(f".{output.name}.kt-v4-cache.partial")
|
|
owner = {"kind": _KIND, "target": str(output), "source": source["fingerprint"]}
|
|
if staging.is_symlink():
|
|
raise KTArtifactError(f"V4 cache staging directory must not be a symlink: {staging}")
|
|
if staging.exists():
|
|
if not staging.is_dir() or _read_json(staging / _PREPARATION_NAME, "V4 cache owner") != owner:
|
|
raise KTArtifactError(f"Refusing to replace an unowned V4 staging directory: {staging}")
|
|
logger.info("Removing interrupted V4 cache preparation: %s", staging)
|
|
shutil.rmtree(staging)
|
|
|
|
sizes = {"BF16": 2, "F32": 4, "I64": 8}
|
|
payload_bytes = sum(math.prod(v["shape"]) * sizes[v["dtype"]] for v in _expected_cache_inventory(source).values())
|
|
required_bytes = payload_bytes + max(16 * 1024**2, payload_bytes // 100)
|
|
free_bytes = shutil.disk_usage(output.parent).free
|
|
if free_bytes < required_bytes:
|
|
raise KTArtifactError(
|
|
f"Insufficient space for V4 non-expert cache at {output}: "
|
|
f"need at least {required_bytes} bytes, available {free_bytes} bytes"
|
|
)
|
|
staging.mkdir(mode=0o700)
|
|
_write_json_atomic(staging / _PREPARATION_NAME, owner)
|
|
logger.info("Preparing V4 non-expert cache at %s (%d tensor bytes)", output, payload_bytes)
|
|
try:
|
|
_convert_non_expert_cache(source, staging)
|
|
manifest = _validate_non_expert_cache(source, staging, verify_payload=verify_payload)
|
|
# rename replaces a pre-existing empty directory, never a non-empty cache.
|
|
os.replace(staging, output)
|
|
_fsync_directory(output.parent)
|
|
except Exception:
|
|
# We created this directory while holding the lock; no unrelated paths are removed.
|
|
if staging.exists() and not staging.is_symlink():
|
|
shutil.rmtree(staging)
|
|
raise
|
|
logger.info("V4 non-expert cache is ready: %s", output)
|
|
return manifest
|
|
|
|
|
|
def prepare_non_expert_cache(source_path: str, output_path: str) -> dict:
|
|
"""Atomically prepare or fully validate/reuse a native V4 non-expert cache."""
|
|
return _ensure_non_expert_cache(inspect_native_checkpoint(source_path), output_path)
|
|
|
|
|
|
def resolve_native_load_plan(kt_config, source_path, explicit_quantization_config=None):
|
|
if explicit_quantization_config is not None:
|
|
raise KTArtifactError("native MXFP4 cannot use an explicit framework quantizer")
|
|
dist, rank, world_size = _distributed_validation_context()
|
|
error, signature, plan = None, None, None
|
|
try:
|
|
source = inspect_native_checkpoint(source_path)
|
|
native_root = _safe_root(
|
|
_config_value(kt_config, "kt_weight_path"), "native MXFP4 experts"
|
|
)
|
|
if str(native_root) != source["model_name_or_path"]:
|
|
raise KTArtifactError(
|
|
"MXFP4 experts and non-expert cache must use the same native base"
|
|
)
|
|
manifest = _ensure_non_expert_cache(
|
|
source, _config_value(kt_config, "kt_non_expert_weight_path"), verify_payload=rank == 0
|
|
)
|
|
root = _safe_root(
|
|
Path(_config_value(kt_config, "kt_non_expert_weight_path")).expanduser().absolute(),
|
|
"V4 non-expert cache",
|
|
)
|
|
expected = _expected_cache_inventory(source)
|
|
files = [record["name"] for record in manifest["files"]]
|
|
manifest_path = root / KT_NON_EXPERT_MANIFEST_NAME
|
|
signature = (manifest["fingerprint"], str(root), str(native_root))
|
|
plan = KTPretrainedLoadPlan(
|
|
source_model_name_or_path=str(native_root),
|
|
weight_path=str(root),
|
|
checkpoint_files=tuple(str(root / name) for name in files),
|
|
weight_keys=frozenset(expected),
|
|
manifest=manifest,
|
|
manifest_path=str(manifest_path),
|
|
routed_weight_path=str(native_root),
|
|
routed_manifest=manifest,
|
|
routed_manifest_path=str(manifest_path),
|
|
lora_rank=_config_value(kt_config, "kt_lora_rank"),
|
|
lora_alpha=_config_value(kt_config, "kt_lora_alpha"),
|
|
)
|
|
except Exception as exc:
|
|
error = (
|
|
exc
|
|
if isinstance(exc, KTArtifactError)
|
|
else KTArtifactError(f"native V4 artifact validation failed: {exc}")
|
|
)
|
|
_synchronize_artifact_validation(dist, rank, world_size, error, signature)
|
|
return plan
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--source", required=True)
|
|
parser.add_argument("--output", required=True)
|
|
args = parser.parse_args()
|
|
prepare_non_expert_cache(args.source, args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|