Files
yyj a5d7ad9047 feat(sft) Deepseek v4 flash LoRA (#2216)
* 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>
2026-09-30 21:29:43 +08:00

707 lines
29 KiB
Python

# KT-Kernel SFT configuration
# SPDX-License-Identifier: Apache-2.0
"""
KTConfig: kt-kernel's own configuration dataclass.
This is the kt-kernel equivalent of DeepSpeed's JSON config —
it holds all kt-kernel-specific settings and is passed through
KTransformersPlugin.kt_config (similar to DeepSpeedPlugin.hf_ds_config).
"""
from __future__ import annotations
import copy
import dataclasses
import inspect
import json
import logging
import os
import warnings
from collections.abc import Mapping
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Literal
from .backend import (
FP8_BACKEND,
INT8_BACKEND,
MXFP4_BACKEND,
RAWINT4_BACKEND,
normalize_sft_backend,
)
logger = logging.getLogger(__name__)
_CPU_TOPOLOGY_ROOT = Path("/sys/devices/system/cpu")
_ACTIVATION_POLICY_ENV = "ACCELERATE_KT_ACTIVATION_POLICY"
_LEGACY_REUSE_ENV = "KT_REUSE_CHECKPOINT_FORWARD"
_KNOWN_SFT_BACKENDS = {
"auto",
"int8",
"fp8",
"amxbf16",
"amxint8",
"amxfp8",
"amxint4",
"rawint4",
"mxfp4",
"amxint4_kgroup",
"amxbf16_skiplora",
"amxint8_skiplora",
"amxint4_skiplora",
}
ActivationRetention = Literal["retain", "recompute"]
ExpertWeightFormat = Literal["bf16", "int8", "fp8", "rawint4", "mxfp4"]
WeightLifecycle = Literal["persistent", "ephemeral"]
@dataclass(frozen=True)
class KTActivationPolicy:
"""Activation lifetime policy shared by every KT distributed rank."""
cpu: ActivationRetention = "recompute"
gpu: ActivationRetention = "recompute"
def __post_init__(self) -> None:
valid = {"retain", "recompute"}
if self.cpu not in valid:
raise ValueError(
f"activation_policy.cpu must be one of {sorted(valid)}, got {self.cpu!r}"
)
if self.gpu not in valid:
raise ValueError(
f"activation_policy.gpu must be one of {sorted(valid)}, got {self.gpu!r}"
)
if self.cpu == "recompute" and self.gpu == "retain":
raise NotImplementedError(
"activation_policy cpu=recompute, gpu=retain is not implemented"
)
@classmethod
def from_value(
cls,
value: "KTActivationPolicy | Mapping[str, str]",
) -> "KTActivationPolicy":
if isinstance(value, cls):
return value
if not isinstance(value, Mapping):
raise TypeError(
"kt_activation_policy must be a KTActivationPolicy or a mapping "
"with exactly the keys 'cpu' and 'gpu'"
)
expected = {"cpu", "gpu"}
actual = set(value)
if actual != expected:
missing = sorted(expected - actual)
unexpected = sorted(actual - expected)
details = []
if missing:
details.append(f"missing={missing}")
if unexpected:
details.append(f"unexpected={unexpected}")
raise ValueError(
"kt_activation_policy must contain exactly 'cpu' and 'gpu'"
+ (f" ({', '.join(details)})" if details else "")
)
return cls(cpu=value["cpu"], gpu=value["gpu"])
def _legacy_activation_policy_from_env(value: str) -> KTActivationPolicy:
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
cpu = "retain"
elif normalized in {"0", "false", "no", "off"}:
cpu = "recompute"
else:
raise ValueError(
f"{_LEGACY_REUSE_ENV} must be a boolean value, got {value!r}"
)
warnings.warn(
f"{_LEGACY_REUSE_ENV} is deprecated; configure kt_activation_policy "
"through the training frontend instead",
FutureWarning,
stacklevel=3,
)
return KTActivationPolicy(cpu=cpu, gpu="recompute")
def _activation_policy_from_env(value: str) -> KTActivationPolicy:
try:
policy = json.loads(value)
except json.JSONDecodeError as exc:
raise ValueError(
f"{_ACTIVATION_POLICY_ENV} must be a JSON object with exactly "
"the keys 'cpu' and 'gpu'"
) from exc
return KTActivationPolicy.from_value(policy)
def _env_int(key: str, default: int | None) -> int | None:
value = os.environ.get(key, None)
if value is None or value == "":
return default
return int(value)
def _env_float(key: str, default: float | None) -> float | None:
value = os.environ.get(key, None)
if value is None or value == "":
return default
return float(value)
def _env_bool(key: str, default: bool) -> bool:
value = os.environ.get(key, None)
if value is None or value == "":
return default
return value.lower() in ("1", "true", "yes")
def _available_cpu_ids() -> set[int]:
"""Return CPUs available to this process, respecting affinity/cpuset limits."""
try:
return set(os.sched_getaffinity(0))
except (AttributeError, OSError):
return set(range(os.cpu_count() or 1))
def _read_cpu_topology(cpu_id: int) -> tuple[int, int] | None:
topology = _CPU_TOPOLOGY_ROOT / f"cpu{cpu_id}" / "topology"
try:
package_id = int((topology / "physical_package_id").read_text().strip())
core_id = int((topology / "core_id").read_text().strip())
except (OSError, ValueError):
return None
return package_id, core_id
def detect_physical_cpu_count() -> int:
"""Count physical cores available to the current process.
Linux exposes a stable ``(physical_package_id, core_id)`` pair for every
logical CPU. Counting those pairs avoids assigning one OpenMP worker to
each SMT sibling. If topology is unavailable, fall back to the number of
affinity-visible logical CPUs.
"""
cpu_ids = _available_cpu_ids()
physical_cores = {
topology
for cpu_id in cpu_ids
if (topology := _read_cpu_topology(cpu_id)) is not None
}
return max(1, len(physical_cores) if physical_cores else len(cpu_ids))
def _set_torch_num_threads(num_threads: int) -> None:
try:
import torch
except ImportError:
return
torch.set_num_threads(num_threads)
def configure_omp_threads() -> int:
"""Configure OpenMP for KT SFT CPU tensor work.
``accelerate launch`` defaults GPU jobs to ``OMP_NUM_THREADS=1`` when the
caller did not choose a value. The authoritative CPU rank needs a physical
core budget for CPU tensor work; other distributed ranks keep one thread
to avoid creating a machine-sized OpenMP pool in every process.
``ACCELERATE_KT_OMP_NUM_THREADS`` is the unambiguous KT-specific override,
including when an intentional single-thread run is required. An existing
generic ``OMP_NUM_THREADS`` value greater than one is also preserved.
"""
kt_override = _env_int("ACCELERATE_KT_OMP_NUM_THREADS", None)
current_omp = _env_int("OMP_NUM_THREADS", None)
if kt_override is not None:
num_threads = kt_override
source = "ACCELERATE_KT_OMP_NUM_THREADS"
elif current_omp is not None and current_omp > 1:
num_threads = current_omp
source = "OMP_NUM_THREADS"
elif _env_int("WORLD_SIZE", 1) > 1 and _env_int("RANK", 0) != 0:
num_threads = 1
source = "non-owner distributed rank"
else:
num_threads = detect_physical_cpu_count()
source = "available physical cores"
if num_threads < 1:
raise ValueError(f"OpenMP thread count must be positive, got {num_threads}")
os.environ["OMP_NUM_THREADS"] = str(num_threads)
_set_torch_num_threads(num_threads)
logger.info("KT SFT configured OMP_NUM_THREADS=%d from %s", num_threads, source)
return num_threads
@dataclass
class KTConfig:
"""
KT-Kernel configuration for SFT training.
All field names use the ``kt_`` prefix so they match the dict keys used in
HfTrainerKTConfig / YAML configs. This means ``KTConfig(**dict)`` works
directly — no name-mapping or prefix-stripping needed.
Can be created from:
- Direct construction: KTConfig(kt_backend="AMXBF16", kt_weight_path="/path/...")
- Dict: KTConfig(**config_dict)
- Environment variables: KTConfig() reads ACCELERATE_KT_* env vars as defaults
"""
# Backend selection
kt_backend: str | None = None
kt_num_threads: int | None = None
kt_tp_enabled: bool | None = None
kt_threadpool_count: int | None = None
# Weight loading
kt_weight_path: str | None = None
kt_non_expert_weight_path: str | None = None
kt_expert_weight_format: ExpertWeightFormat | str | None = None
kt_weight_lifecycle: WeightLifecycle | str | None = None
kt_expert_checkpoint_path: str | None = (
None # HF expert checkpoint or KT Full checkpoint directory
)
kt_num_gpu_experts: int | None = None
kt_skip_expert_loading: bool | None = None
kt_share_backward_bb: bool | None = None # defaults off for RAWINT4/MXFP4
kt_share_cache_pool: bool | None = (
None # auto-set by trainer_config_process, not user-facing
)
kt_force_fused_expert_lora: bool | None = None
# Cache
kt_max_cache_depth: int | None = None
kt_model_max_length: int | None = None
kt_activation_policy: KTActivationPolicy | Mapping[str, str] | None = None
# LoRA
kt_lora_rank: int | None = None
kt_lora_alpha: float | None = None
kt_lora_dropout: float | None = None
# Training mode
kt_train_mode: str | None = None # "lora" | "full" | "hybrid"
kt_full_weight_grad: bool | None = (
None # auto-set True when train_mode in (full, hybrid)
)
# LoRA Experts (GPU-side extra experts)
kt_use_lora_experts: bool | None = None
kt_lora_expert_num: int | None = None
kt_lora_expert_intermediate_size: int | None = None
# Runtime state (set during wrapping, not by user)
kt_checkpoint_files: list[str] | None = None
kt_sharded_metadata: dict | None = None
# Custom wrapping
kt_wrap_fn: Callable[..., Any] | None = None
kt_wrap_kwargs: dict[str, Any] | None = None
@classmethod
def from_object(cls, obj: Any) -> "KTConfig":
"""Create KTConfig from a mapping or compatible public container."""
return cls._from_object(obj, seen=set())
@classmethod
def _from_object(cls, obj: Any, *, seen: set[int]) -> "KTConfig":
if isinstance(obj, cls):
return obj
if isinstance(obj, Mapping):
return cls(**cls._validated_mapping(obj))
object_id = id(obj)
if object_id in seen:
raise ValueError("Cyclic KT configuration container")
seen.add(object_id)
field_names = {field.name for field in dataclasses.fields(cls)}
attributes = vars(obj) if hasattr(obj, "__dict__") else {}
unknown = sorted(
name
for name in attributes
if name.startswith("kt_")
and name not in field_names
and name != "kt_config"
)
if unknown:
raise TypeError(f"Unknown KTConfig fields: {unknown}")
missing = object()
has_public_container = False
for container_name in ("kt_config", "config"):
if inspect.getattr_static(obj, container_name, missing) is missing:
continue
has_public_container = True
nested = getattr(obj, container_name, None)
if nested is None:
continue
if nested is obj:
raise ValueError(f"Cyclic KT configuration container: {container_name}")
if isinstance(nested, Mapping):
kwargs = cls._validated_mapping(nested)
for name in (
"kt_checkpoint_files",
"kt_sharded_metadata",
"kt_skip_expert_loading",
):
value = getattr(obj, name, None)
if value is not None:
kwargs[name] = value
return cls(**kwargs)
resolved = cls._from_object(nested, seen=seen)
overrides: dict[str, Any] = {}
for name in (
"kt_checkpoint_files",
"kt_sharded_metadata",
"kt_skip_expert_loading",
):
value = getattr(obj, name, None)
if value is not None and value is not getattr(resolved, name, None):
overrides[name] = value
if not overrides:
return resolved
result = copy.copy(resolved)
for name, value in overrides.items():
setattr(result, name, value)
return result
if has_public_container:
return cls()
raise TypeError(
"KT configuration must be a KTConfig, mapping, or public container "
"with a static 'kt_config' or 'config' attribute"
)
@classmethod
def _validated_mapping(cls, obj: Mapping[str, Any]) -> dict[str, Any]:
payload = dict(obj)
invalid_keys = sorted(
repr(name) for name in payload if not isinstance(name, str)
)
if invalid_keys:
raise TypeError(f"KTConfig field names must be strings: {invalid_keys}")
if "enabled" in payload:
enabled = payload.pop("enabled")
if enabled is not None and not isinstance(enabled, bool):
raise TypeError("KT framework field 'enabled' must be a bool or None")
field_names = {field.name for field in dataclasses.fields(cls)}
unknown = sorted(set(payload).difference(field_names))
if unknown:
raise TypeError(f"Unknown KTConfig fields: {unknown}")
return payload
def __post_init__(self):
configure_omp_threads()
explicit_backend = self.kt_backend is not None
env_backend = os.environ.get("ACCELERATE_KT_BACKEND")
if self.kt_expert_weight_format is None:
self.kt_expert_weight_format = os.environ.get(
"ACCELERATE_KT_EXPERT_WEIGHT_FORMAT"
)
if self.kt_expert_weight_format is not None:
self.kt_expert_weight_format = (
str(self.kt_expert_weight_format).strip().lower()
)
if self.kt_expert_weight_format not in {"bf16", "int8", "fp8", "rawint4", "mxfp4"}:
raise ValueError(
"kt_expert_weight_format must be one of "
"['bf16', 'fp8', 'int8', 'rawint4', 'mxfp4'], "
f"got {self.kt_expert_weight_format!r}"
)
if self.kt_weight_lifecycle is None:
self.kt_weight_lifecycle = os.environ.get(
"ACCELERATE_KT_WEIGHT_LIFECYCLE", "persistent"
)
self.kt_weight_lifecycle = str(self.kt_weight_lifecycle).strip().lower()
if self.kt_weight_lifecycle not in {"persistent", "ephemeral"}:
raise ValueError(
"kt_weight_lifecycle must be one of ['ephemeral', 'persistent'], "
f"got {self.kt_weight_lifecycle!r}"
)
forwarded_policy = os.environ.get(_ACTIVATION_POLICY_ENV)
forwarded_policy_is_explicit = (
forwarded_policy is not None and forwarded_policy.strip() != ""
)
legacy_reuse = os.environ.get(_LEGACY_REUSE_ENV)
legacy_reuse_is_explicit = (
legacy_reuse is not None and legacy_reuse.strip() != ""
)
if self.kt_activation_policy is not None and forwarded_policy_is_explicit:
raise ValueError(
f"kt_activation_policy conflicts with {_ACTIVATION_POLICY_ENV}; "
f"unset {_ACTIVATION_POLICY_ENV}"
)
if self.kt_activation_policy is not None and legacy_reuse_is_explicit:
raise ValueError(
f"kt_activation_policy conflicts with legacy {_LEGACY_REUSE_ENV}; "
f"unset {_LEGACY_REUSE_ENV}"
)
if forwarded_policy_is_explicit and legacy_reuse_is_explicit:
raise ValueError(
f"{_ACTIVATION_POLICY_ENV} conflicts with legacy "
f"{_LEGACY_REUSE_ENV}; unset {_LEGACY_REUSE_ENV}"
)
if self.kt_activation_policy is None:
if forwarded_policy_is_explicit:
self.kt_activation_policy = _activation_policy_from_env(
forwarded_policy
)
elif legacy_reuse_is_explicit:
self.kt_activation_policy = _legacy_activation_policy_from_env(
legacy_reuse
)
else:
self.kt_activation_policy = KTActivationPolicy()
else:
self.kt_activation_policy = KTActivationPolicy.from_value(
self.kt_activation_policy
)
if self.kt_backend is None:
if env_backend:
self.kt_backend = env_backend
elif self.kt_expert_weight_format in {"int8", "fp8", "rawint4", "mxfp4"}:
self.kt_backend = "auto"
else:
self.kt_backend = "AMXBF16"
backend_lower = str(self.kt_backend).lower()
if backend_lower not in _KNOWN_SFT_BACKENDS:
raise ValueError(
f"unknown kt_backend {self.kt_backend!r}; expected one of "
f"{sorted(_KNOWN_SFT_BACKENDS)}"
)
self.kt_backend = normalize_sft_backend(
self.kt_backend,
expert_weight_format=self.kt_expert_weight_format,
)
backend_lower = str(self.kt_backend).lower()
if self.kt_expert_weight_format is None:
if backend_lower == INT8_BACKEND.lower():
# Backward compatibility for the legacy environment-only entry.
self.kt_expert_weight_format = "int8"
elif backend_lower == FP8_BACKEND.lower():
self.kt_expert_weight_format = "fp8"
elif backend_lower == RAWINT4_BACKEND.lower():
self.kt_expert_weight_format = "rawint4"
elif backend_lower == MXFP4_BACKEND.lower():
self.kt_expert_weight_format = "mxfp4"
elif backend_lower == "amxbf16":
self.kt_expert_weight_format = "bf16"
expected_backend = {
"bf16": "amxbf16",
"int8": INT8_BACKEND.lower(),
"fp8": FP8_BACKEND.lower(),
"rawint4": RAWINT4_BACKEND.lower(),
"mxfp4": MXFP4_BACKEND.lower(),
}.get(self.kt_expert_weight_format)
if expected_backend is not None and backend_lower != expected_backend:
source = "kt_backend" if explicit_backend else "ACCELERATE_KT_BACKEND"
raise ValueError(
f"kt_expert_weight_format={self.kt_expert_weight_format!r} conflicts "
f"with {source}={self.kt_backend!r}"
)
if self.kt_num_threads is None:
self.kt_num_threads = _env_int("ACCELERATE_KT_NUM_THREADS", 1)
if self.kt_tp_enabled is None:
self.kt_tp_enabled = _env_bool("ACCELERATE_KT_TP_ENABLED", False)
if self.kt_threadpool_count is None:
self.kt_threadpool_count = _env_int("ACCELERATE_KT_THREADPOOL_COUNT", 1)
if self.kt_weight_path is None:
self.kt_weight_path = os.environ.get("ACCELERATE_KT_WEIGHT_PATH", None)
if self.kt_non_expert_weight_path is None:
self.kt_non_expert_weight_path = os.environ.get(
"ACCELERATE_KT_NON_EXPERT_WEIGHT_PATH", None
)
if self.kt_expert_checkpoint_path is None:
self.kt_expert_checkpoint_path = os.environ.get(
"ACCELERATE_KT_EXPERT_CHECKPOINT_PATH", None
)
if self.kt_num_gpu_experts is None:
self.kt_num_gpu_experts = _env_int("ACCELERATE_KT_NUM_GPU_EXPERTS", 0)
if self.kt_max_cache_depth is None:
self.kt_max_cache_depth = _env_int("ACCELERATE_KT_MAX_CACHE_DEPTH", 2)
if self.kt_share_backward_bb is None:
self.kt_share_backward_bb = _env_bool(
"ACCELERATE_KT_SHARE_BACKWARD_BB",
self.kt_expert_weight_format not in {"rawint4", "mxfp4"},
)
if self.kt_share_cache_pool is None:
self.kt_share_cache_pool = False
if self.kt_force_fused_expert_lora is None:
self.kt_force_fused_expert_lora = _env_bool(
"ACCELERATE_KT_FORCE_FUSED_EXPERT_LORA", False
)
if self.kt_use_lora_experts is None:
self.kt_use_lora_experts = _env_bool(
"ACCELERATE_KT_USE_LORA_EXPERTS", False
)
if self.kt_lora_expert_num is None:
self.kt_lora_expert_num = _env_int("ACCELERATE_KT_LORA_EXPERT_NUM", None)
if self.kt_lora_expert_intermediate_size is None:
self.kt_lora_expert_intermediate_size = _env_int(
"ACCELERATE_KT_LORA_EXPERT_INTERMEDIATE_SIZE", None
)
if self.kt_lora_rank is None:
self.kt_lora_rank = _env_int("ACCELERATE_KT_LORA_RANK", None)
if self.kt_lora_alpha is None:
self.kt_lora_alpha = _env_float("ACCELERATE_KT_LORA_ALPHA", None)
if self.kt_lora_alpha is None and self.kt_lora_rank is not None:
self.kt_lora_alpha = float(self.kt_lora_rank * 2)
if self.kt_lora_dropout is None:
self.kt_lora_dropout = _env_float("ACCELERATE_KT_LORA_DROPOUT", 0.0)
if not 0.0 <= self.kt_lora_dropout < 1.0:
raise ValueError(
f"kt_lora_dropout must be in [0, 1), got {self.kt_lora_dropout}"
)
if self.kt_train_mode is None:
self.kt_train_mode = os.environ.get("ACCELERATE_KT_TRAIN_MODE", "lora")
if self.kt_full_weight_grad is None:
self.kt_full_weight_grad = self.kt_train_mode in ("full", "hybrid")
if self.kt_model_max_length is None:
self.kt_model_max_length = _env_int("ACCELERATE_KT_MODEL_MAX_LENGTH", None)
if self.kt_skip_expert_loading is None:
if "ACCELERATE_KT_SKIP_EXPERT_LOADING" in os.environ:
self.kt_skip_expert_loading = _env_bool(
"ACCELERATE_KT_SKIP_EXPERT_LOADING", True
)
if self.kt_non_expert_weight_path and self.kt_expert_weight_format not in {"int8", "mxfp4"}:
raise ValueError(
"kt_non_expert_weight_path is supported only with "
"kt_expert_weight_format='int8' or 'mxfp4'"
)
if self.kt_expert_weight_format == "mxfp4":
if self.kt_skip_expert_loading is None:
self.kt_skip_expert_loading = True
if self.kt_train_mode != "lora" or self.kt_full_weight_grad or int(self.kt_lora_rank or 0) <= 0:
raise ValueError("MXFP4 SFT supports frozen-base LoRA only with kt_lora_rank > 0")
if self.kt_num_gpu_experts or self.kt_use_lora_experts:
raise ValueError("MXFP4 SFT requires all routed experts and their LoRA on CPU")
if self.kt_share_backward_bb:
raise ValueError("MXFP4 SFT is transpose-free; kt_share_backward_bb must be false")
if self.kt_lora_dropout != 0.0:
raise ValueError("MXFP4 SFT requires kt_lora_dropout=0")
if self.kt_weight_lifecycle != "persistent" or self.kt_expert_checkpoint_path:
raise ValueError("MXFP4 SFT requires a persistent native checkpoint at kt_weight_path")
if not self.kt_weight_path:
raise ValueError("MXFP4 SFT requires kt_weight_path")
if not self.kt_skip_expert_loading:
raise ValueError("MXFP4 SFT requires kt_skip_expert_loading=true")
if (int(self.kt_threadpool_count or 1) if self.kt_tp_enabled else 1) not in (1, 2):
raise ValueError("MXFP4 SFT supports CPU TP1/TP2 only")
if self.kt_expert_weight_format == "int8":
if str(self.kt_backend).lower() != INT8_BACKEND.lower():
raise ValueError(
"INT8 SFT requires kt_backend='auto' or kt_backend='INT8'"
)
if self.kt_train_mode != "lora" or bool(self.kt_full_weight_grad):
raise ValueError(
"INT8 SFT supports frozen-base LoRA only; Full and Hybrid are not supported"
)
if int(self.kt_num_gpu_experts or 0) != 0:
raise ValueError("INT8 SFT requires kt_num_gpu_experts=0")
if bool(self.kt_use_lora_experts):
raise ValueError(
"INT8 SFT does not support GPU LoRA experts; use pure expert LoRA"
)
if not bool(self.kt_share_backward_bb):
raise ValueError("INT8 SFT requires kt_share_backward_bb=true")
if self.kt_expert_checkpoint_path:
raise ValueError(
"INT8 SFT requires pre-quantized .kt weights; "
"kt_expert_checkpoint_path is not supported"
)
if not self.kt_weight_path:
raise ValueError(
"INT8 SFT requires kt_weight_path pointing to pre-quantized .kt weights"
)
if self.kt_expert_weight_format == "fp8":
if str(self.kt_backend).lower() != FP8_BACKEND.lower():
raise ValueError(
"FP8 SFT requires kt_backend='auto' or kt_backend='FP8'"
)
if self.kt_train_mode != "lora" or bool(self.kt_full_weight_grad):
raise ValueError(
"FP8 SFT supports frozen-base LoRA only; Full and Hybrid are not supported"
)
if not self.kt_lora_rank or int(self.kt_lora_rank) <= 0:
raise ValueError("FP8 SFT requires kt_lora_rank > 0")
if int(self.kt_num_gpu_experts or 0) != 0:
raise ValueError("FP8 SFT requires kt_num_gpu_experts=0")
if bool(self.kt_use_lora_experts):
raise ValueError(
"FP8 SFT does not support GPU LoRA experts; use pure expert LoRA"
)
if not bool(self.kt_share_backward_bb):
raise ValueError("FP8 SFT requires kt_share_backward_bb=true")
if self.kt_weight_lifecycle != "persistent":
raise ValueError("FP8 SFT requires kt_weight_lifecycle='persistent'")
if self.kt_expert_weight_format == "rawint4":
if str(self.kt_backend).lower() != RAWINT4_BACKEND.lower():
raise ValueError(
"RAWINT4 SFT requires kt_backend='auto' or kt_backend='RAWINT4'"
)
if self.kt_train_mode != "lora" or bool(self.kt_full_weight_grad):
raise ValueError(
"RAWINT4 SFT supports frozen-base LoRA only; Full and Hybrid are not supported"
)
if int(self.kt_lora_rank or 0) != 8:
raise ValueError("RAWINT4 SFT v1 requires kt_lora_rank=8")
if float(self.kt_lora_dropout or 0.0) != 0.0:
raise ValueError("RAWINT4 SFT v1 requires kt_lora_dropout=0")
effective_threadpool_count = (
int(self.kt_threadpool_count or 1) if self.kt_tp_enabled else 1
)
if effective_threadpool_count not in {1, 2}:
raise ValueError(
"RAWINT4 SFT v1 supports TP1/TP2 only; "
f"effective threadpool_count={effective_threadpool_count}"
)
if int(self.kt_num_gpu_experts or 0) != 0:
raise ValueError("RAWINT4 SFT requires kt_num_gpu_experts=0")
if bool(self.kt_use_lora_experts):
raise ValueError(
"RAWINT4 SFT does not support GPU LoRA experts; use pure expert LoRA"
)
if bool(self.kt_share_backward_bb):
raise ValueError(
"RAWINT4 SFT computes backward directly from packed weights; "
"kt_share_backward_bb must be false"
)
if not bool(self.kt_force_fused_expert_lora):
raise ValueError("RAWINT4 SFT requires kt_force_fused_expert_lora=true")
if self.kt_expert_checkpoint_path:
raise ValueError(
"RAWINT4 SFT reads packed experts from the source checkpoint; "
"kt_expert_checkpoint_path is not supported"
)
if self.kt_weight_lifecycle != "persistent":
raise ValueError(
"RAWINT4 SFT requires kt_weight_lifecycle='persistent'"
)
if (
self.kt_activation_policy.cpu == "retain"
and self.kt_activation_policy.gpu == "recompute"
):
raise ValueError(
"RAWINT4 SFT v1 supports activation_policy "
"cpu=retain,gpu=retain for ordinary forward/backward caching, "
"but checkpoint-forward reuse (cpu=retain,gpu=recompute) is not supported"
)
if self.kt_weight_lifecycle == "ephemeral":
if self.kt_expert_weight_format != "int8":
raise ValueError(
"kt_weight_lifecycle='ephemeral' is supported only for INT8 .kt weights"
)