mirror of
https://github.com/kvcache-ai/ktransformers.git
synced 2026-10-17 05:11:19 +08:00
9544a8960d
* feat(sft): AMX MoE SFT backend with LoRA support Complete SFT (Supervised Fine-Tuning) backend for MoE models using AMX SIMD: Core C++ implementation: - sft_moe.hpp: Forward/backward with LoRA fused operations (~5500 lines) - moe-sft-tp.hpp: Tensor-parallel wrapper for multi-NUMA - amx/moe-sft-tp.hpp: AMX-specific TP implementation - avx_kernels.hpp: AVX512 SIMD kernels for LoRA GEMM - amx_kernels.hpp: AMX tile kernels for Panel5 rank-outer optimization - worker_pool: RDTSC profiling, Chrome trace output, SFT timer infrastructure - ext_bindings.cpp: SFT MOE pybind bindings (BF16/INT8/INT4 + SkipLoRA variants) Python sft/ submodule (kt_kernel.sft): - base.py: BaseSFTMoEWrapper with buffer management (template method pattern) - amx.py: AMXSFTMoEWrapper (weight loading, C++ task construction) - autograd.py: KTMoEFunction (torch.autograd.Function for distributed training) - layer.py: KTMoELayerWrapper (nn.Module replacing HF MoE layers) - arch.py: MOEArchConfig (Qwen3/DeepSeek/Mixtral architecture detection) - weights.py: Expert weight extraction and checkpoint loading - lora.py: PEFT LoRA adaptation (view buffers, grad buffers, save/load adapter) - wrapper.py: wrap_moe_layers_with_kt_wrapper, load_kt_model, build_kt_device_map - config.py: KTConfig dataclass (DeepSpeed-style opaque config passthrough) - dist_utils.py: Distributed gather/scatter, checkpoint-phase detection Design decisions: - Rank-0-only expert pattern: only rank 0 holds C++ wrapper and expert weights - DeepSpeed-style integration: accelerate keeps only KTransformersPlugin (framework interaction fields), all logic in kt_kernel.sft - Inference isolation: importing kt_kernel does not load sft/ submodule - Old field name compatibility: _get_kt_config() converts kt_xxx→xxx automatically Verified: Qwen3-235B-A22B 4GPU AMXBF16 training, loss converges normally. * refactor(sft): unify KTConfig field names with kt_ prefix, add share_cache_pool, remove dead code - KTConfig fields all use kt_ prefix matching dict keys — eliminates _OLD_TO_NEW mapping and prefix-stripping in wrapper.py - Add kt_share_cache_pool field, auto-enabled when gradient_checkpointing is on (via training_args.py), flows through to C++ cache allocation - Remove dead checkpoint detection code: in_ckpt_recompute, in_ckpt_first_forward vars (assigned but never read), fallback _is_in_checkpoint_first_forward() function, unused inspect import - Remove redundant env var fallbacks in wrapper.py for share_backward_bb and share_cache_pool (KTConfig.__post_init__ already handles env vars) - Simplify layer.py checkpoint logic to single _checkpoint_hook_mode() check Verified: Qwen3-235B 3-step training on sap4, loss matches baseline (1.2886 / 1.9824 / 1.377 vs 1.2886 / 1.9766 / 1.3809) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(sft): share_backward_bb default True, share_cache_pool auto-derived - kt_share_backward_bb defaults to True (always saves memory) - kt_share_cache_pool no longer reads from env var; defaults False, auto-set to True by trainer_config_process when gradient checkpointing is enabled Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add missing gpu_experts_mask=None to KTMoEWrapper call in SFT wrapper KTMoEWrapper.__new__() requires gpu_experts_mask as a positional argument, but the SFT wrapper omitted it, causing MoE layer wrapping to fail silently and FSDP2 to attempt broadcasting all expert weights (OOM/NCCL crash). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(sft): support transformers v5 fused expert format Fused experts (e.g. Qwen3MoeExperts) store weights as 3D Parameters (gate_up_proj [E,2I,H], down_proj [E,H,I]) instead of per-expert nn.Linear modules. PEFT cannot attach LoRA to these, so we create KT-managed LoRA buffers with kaiming init, nn.Parameter wrappers for the optimizer, and pre-assigned .grad for C++ backward. - arch.py: detect_fused_experts() detection - weights.py: fused format extraction and weight clearing - wrapper.py: detect fused at wrap time, store _fused_experts/_lora_rank - lora.py: _create_fused_expert_lora_buffers, save/load fused LoRA, get_kt_lora_params collects fused params, deduplicate wrapper finding - layer.py: handle v5 TopKRouter tuple output, remove dead code - autograd.py: sync_forward_sft/submit_forward_sft API rename Verified: v5 loss/expert-LoRA values match v4 baseline, v4 backward compat. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(sft): add Qwen3.5 MoE support + fused checkpoint loading - arch.py: add Qwen3_5Moe arch match, read config from text_config, _get_layers_prefix returns model.language_model.layers for Qwen3.5, _get_model_container_and_layers searches language_model attr - weights.py: load_experts_from_checkpoint_files detects fused format (gate_up_proj in weight_map) and splits into gate/up/down - wrapper.py: hidden_size fallback to text_config Verified: Qwen3.5-35B-A3B (256 experts, fused format) E2E pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [fix](sft): align Python API with C++ backend after v5 refactor - wrapper.py: pass gpu_experts_mask=None to KTMoEWrapper (required by C++ signature) - layer.py: rename submit_forward_sft/sync_forward_sft to submit_forward/sync_forward - autograd.py: rename sync_forward_sft to sync_forward The sft-v5 refactor (commits58d7eab,dd1da65) renamed Python-side method calls but the C++ backend (AMXSFTMoEWrapper) still exposes the original method names. This caused AttributeError on Qwen3.5-35B and other models. * align sft branch with main: revert worker_pool, strip sft_timer, fix inference defaults - Revert worker_pool.cpp/.h to main (remove RDTSC timer, Chrome Trace, sft_timer namespace, ITT API, extended do_work_stealing_job API) - Strip all sft_timer instrumentation from sft-only files (sft_moe.hpp, moe-sft-tp.hpp, avx_kernels.hpp) - Restore pin_memory=True in KExpertsCPUBuffer (inference path) - Restore fused tensor transpose logic in convert_cpu_weights.py (main layout) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * revert CMakeLists.txt to main: remove debug flags and cpptrace dep Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * clean up dev artifacts: remove SFT design docs, debug examples, bench scripts Remove files not needed in the merge: - docs/SFT+KTWrapper/ (6 Chinese design docs) - docs/sft_moe_amx/ (21 dev/debug docs) - 12 debug/test example scripts - 6 SFT-specific bench scripts and report Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * remove dev version stamps from ext_bindings, sft_moe, moe-sft-tp Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: JimmyPeilinLi <lipeilin@mail.nwpu.edu.cn>
98 lines
3.8 KiB
C++
98 lines
3.8 KiB
C++
#ifndef UTILS_HPP
|
|
#define UTILS_HPP
|
|
#include <immintrin.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
static inline void avx512_copy_32xbf16(__m512i* src, __m512i* dst) {
|
|
_mm512_storeu_si512(dst, _mm512_loadu_si512(src));
|
|
}
|
|
|
|
// FP32 to BF16 conversion (32 floats -> 32 bf16)
|
|
// This requires AVX512BF16 for the fast path, with a fallback for CPUs without it
|
|
static inline void avx512_32xfp32_to_32xbf16(__m512* src0, __m512* src1, __m512i* dst) {
|
|
#if defined(HAVE_AVX512BF16) || defined(__AVX512BF16__)
|
|
// Fast path: use native AVX512BF16 instruction
|
|
_mm512_storeu_si512(dst, __m512i(_mm512_cvtne2ps_pbh(*src1, *src0)));
|
|
#else
|
|
// Fallback: manual BF16 conversion using bit manipulation
|
|
// BF16 is the upper 16 bits of FP32 (with rounding)
|
|
__m512i i0 = _mm512_castps_si512(*src0);
|
|
__m512i i1 = _mm512_castps_si512(*src1);
|
|
|
|
// Round to nearest even: add 0x7FFF + ((val >> 16) & 1)
|
|
__m512i round0 =
|
|
_mm512_add_epi32(_mm512_set1_epi32(0x7FFF), _mm512_and_epi32(_mm512_srli_epi32(i0, 16), _mm512_set1_epi32(1)));
|
|
__m512i round1 =
|
|
_mm512_add_epi32(_mm512_set1_epi32(0x7FFF), _mm512_and_epi32(_mm512_srli_epi32(i1, 16), _mm512_set1_epi32(1)));
|
|
|
|
i0 = _mm512_add_epi32(i0, round0);
|
|
i1 = _mm512_add_epi32(i1, round1);
|
|
|
|
// Extract upper 16 bits (BF16)
|
|
i0 = _mm512_srli_epi32(i0, 16);
|
|
i1 = _mm512_srli_epi32(i1, 16);
|
|
|
|
// Pack 32-bit values to 16-bit
|
|
__m512i result = _mm512_packus_epi32(i0, i1);
|
|
// Fix the interleaving from packus
|
|
result = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), result);
|
|
|
|
_mm512_storeu_si512(dst, result);
|
|
#endif
|
|
}
|
|
|
|
// BF16 to FP32 conversion (32 bf16 -> 32 floats)
|
|
// This does NOT require AVX512BF16 - uses basic AVX512 bit manipulation
|
|
static inline void avx512_32xbf16_to_32xfp32(__m512i* src, __m512* dst0, __m512* dst1) {
|
|
_mm512_storeu_ps(dst0, _mm512_castsi512_ps(
|
|
_mm512_slli_epi32(_mm512_cvtepu16_epi32(_mm256_loadu_si256((const __m256i*)(src))), 16)));
|
|
_mm512_storeu_ps(dst1, _mm512_castsi512_ps(_mm512_slli_epi32(
|
|
_mm512_cvtepu16_epi32(_mm256_loadu_si256((const __m256i*)(src) + 1)), 16)));
|
|
}
|
|
|
|
// Vectorized exp(x) for AVX512 — range reduction + 5th order polynomial
|
|
// Accurate to ~1 ULP for single precision, sufficient for BF16 output
|
|
static inline __m512 avx512_exp_ps(__m512 x) {
|
|
const __m512 log2e = _mm512_set1_ps(1.4426950408889634f);
|
|
const __m512 ln2_hi = _mm512_set1_ps(0.6931381225585938f);
|
|
const __m512 ln2_lo = _mm512_set1_ps(9.058016417660564e-6f);
|
|
const __m512 one = _mm512_set1_ps(1.0f);
|
|
const __m512 c2 = _mm512_set1_ps(0.5f);
|
|
const __m512 c3 = _mm512_set1_ps(0.16666667f);
|
|
const __m512 c4 = _mm512_set1_ps(0.04166667f);
|
|
const __m512 c5 = _mm512_set1_ps(0.00833333f);
|
|
|
|
// Clamp to avoid overflow/underflow
|
|
x = _mm512_max_ps(x, _mm512_set1_ps(-87.33f));
|
|
x = _mm512_min_ps(x, _mm512_set1_ps(88.72f));
|
|
|
|
// Range reduction: n = round(x / ln2), r = x - n*ln2
|
|
__m512 n = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT);
|
|
__m512 r = _mm512_fnmadd_ps(n, ln2_hi, x);
|
|
r = _mm512_fnmadd_ps(n, ln2_lo, r);
|
|
|
|
// exp(r) via Horner: 1 + r*(1 + r*(1/2 + r*(1/6 + r*(1/24 + r/120))))
|
|
__m512 p = _mm512_fmadd_ps(c5, r, c4);
|
|
p = _mm512_fmadd_ps(p, r, c3);
|
|
p = _mm512_fmadd_ps(p, r, c2);
|
|
p = _mm512_fmadd_ps(p, r, one);
|
|
p = _mm512_fmadd_ps(p, r, one);
|
|
|
|
// Scale: exp(x) = exp(r) * 2^n
|
|
__m512i ni = _mm512_cvtps_epi32(n);
|
|
__m512i pow2n = _mm512_slli_epi32(_mm512_add_epi32(ni, _mm512_set1_epi32(127)), 23);
|
|
return _mm512_mul_ps(p, _mm512_castsi512_ps(pow2n));
|
|
}
|
|
|
|
static inline __m512 vector_abs_max(__m512 a, __m512 b) {
|
|
__m512 a_abs = _mm512_abs_ps(a);
|
|
__m512 b_abs = _mm512_abs_ps(b);
|
|
|
|
__mmask16 mask = _mm512_cmp_ps_mask(a_abs, b_abs, _CMP_GT_OS);
|
|
|
|
return _mm512_mask_blend_ps(mask, b_abs, a_abs);
|
|
}
|
|
|
|
#endif // UTILS_HPP
|