Files
wehub-resource-sync ec436095dd
Book-CI / test (macos-latest) (push) Has been cancelled
Book-CI / test (ubuntu-latest) (push) Has been cancelled
Book-CI / test (windows-latest) (push) Has been cancelled
Release Fake Tag / publish (push) Has been cancelled
Deploy / deploy (macos-latest) (push) Has been cancelled
Deploy / deploy (ubuntu-latest) (push) Has been cancelled
Deploy / deploy (windows-latest) (push) Has been cancelled
Release to PyPI / Build & publish sglang-kt (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.11) (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.12) (push) Has been cancelled
Release to PyPI / Publish kt-kernel to PyPI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:03 +08:00

5490 lines
252 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @Description : AMX MoE SFT (Supervised Fine-Tuning) implementation with LoRA support.
* @Author : lpl, Claude
* @Date : 2025-12-31
* @Version : 0.1.0
* @Copyright (c) 2024 by KVCache.AI, All Rights Reserved.
**/
#ifndef CPUINFER_OPERATOR_AMX_SFT_MOE_H
#define CPUINFER_OPERATOR_AMX_SFT_MOE_H
#include <algorithm>
#include <cassert>
#include <cerrno>
#include <chrono>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iterator>
#include <mutex>
#include <stdexcept>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../../cpu_backend/worker_pool.h"
#include "ggml.h"
#include "la/amx_kernels.hpp"
#include "la/avx_kernels.hpp"
#include "moe.hpp"
// =====================================================
// BUG-010: NaN Diagnostic Helper Functions
// =====================================================
struct NaNCheckResult {
int nan_count = 0;
int inf_count = 0;
int first_nan_idx = -1;
float first_nan_input_val = 0.0f;
};
struct Bf16Stats {
double abs_mean = 0.0;
double abs_max = 0.0;
double norm = 0.0;
};
inline Bf16Stats compute_bf16_stats(const ggml_bf16_t* buf, size_t size) {
Bf16Stats stats;
if (size == 0 || buf == nullptr) {
return stats;
}
double sum_abs = 0.0;
double sum_sq = 0.0;
double max_abs = 0.0;
for (size_t i = 0; i < size; i++) {
float v = GGML_BF16_TO_FP32(buf[i]);
double dv = static_cast<double>(v);
double a = std::fabs(dv);
sum_abs += a;
sum_sq += dv * dv;
if (a > max_abs || std::isnan(a)) {
max_abs = a;
}
}
stats.abs_mean = sum_abs / static_cast<double>(size);
stats.abs_max = max_abs;
stats.norm = std::sqrt(sum_sq);
return stats;
}
// ANSI color codes for terminal output
#define ANSI_COLOR_RED "\033[1;31m"
#define ANSI_COLOR_YELLOW "\033[1;33m"
#define ANSI_COLOR_GREEN "\033[1;32m"
#define ANSI_COLOR_RESET "\033[0m"
#define ANSI_BG_YELLOW "\033[43m"
#define ANSI_BG_RED "\033[41m"
#define ANSI_BG_BLUE "\033[44m"
// Robust NaN/Inf check (v != v is true only for NaN)
inline bool is_nan_value(float v) { return v != v; }
inline bool is_inf_value(float v) {
return !is_nan_value(v) &&
(v == std::numeric_limits<float>::infinity() || v == -std::numeric_limits<float>::infinity());
}
// Threshold for "large value" warning (yellow)
constexpr double NAN_CHECK_LARGE_THRESHOLD = 1e4;
// Check BF16 buffer for NaN/Inf (using robust v != v check)
inline NaNCheckResult check_bf16_buffer_for_nan(const ggml_bf16_t* buf, int size, const char* label = nullptr) {
NaNCheckResult result;
for (int i = 0; i < size; i++) {
float val = GGML_BF16_TO_FP32(buf[i]);
// Use val != val for robust NaN detection
if (val != val) {
result.nan_count++;
if (result.first_nan_idx < 0) {
result.first_nan_idx = i;
result.first_nan_input_val = val;
}
}
if (!(val != val) && is_inf_value(val)) {
result.inf_count++;
if (result.first_nan_idx < 0) {
result.first_nan_idx = i;
}
}
}
if (label && (result.nan_count > 0 || result.inf_count > 0)) {
printf(ANSI_COLOR_RED "[NaN TRACE] %s: nan_count=%d, inf_count=%d, first_idx=%d" ANSI_COLOR_RESET "\n", label,
result.nan_count, result.inf_count, result.first_nan_idx);
}
return result;
}
// Check FP32 buffer for NaN/Inf (using robust v != v check)
inline NaNCheckResult check_fp32_buffer_for_nan(const float* buf, int size, const char* label = nullptr) {
NaNCheckResult result;
for (int i = 0; i < size; i++) {
float val = buf[i];
// Use val != val for robust NaN detection
if (val != val) {
result.nan_count++;
if (result.first_nan_idx < 0) {
result.first_nan_idx = i;
result.first_nan_input_val = val;
}
}
if (!(val != val) && is_inf_value(val)) {
result.inf_count++;
if (result.first_nan_idx < 0) {
result.first_nan_idx = i;
}
}
}
if (label && (result.nan_count > 0 || result.inf_count > 0)) {
printf(ANSI_COLOR_RED "[NaN TRACE] %s: nan_count=%d, inf_count=%d, first_idx=%d" ANSI_COLOR_RESET "\n", label,
result.nan_count, result.inf_count, result.first_nan_idx);
}
return result;
}
// Check if NaN checking is enabled via environment variable
inline bool is_nan_check_enabled() {
return false;
static int enabled = -1;
if (enabled < 0) {
const char* env = getenv("SFT_MOE_NAN_CHECK");
enabled = (env && env[0] != '0') ? 1 : 0;
}
return enabled == 1;
}
// =====================================================
// Pool Memory Logger — writes per-call alloc/free events to file
// Enable: set SFT_POOL_LOG=1 (or any non-zero)
// Output: sft_pool_log.txt in current directory (append mode)
// Disable: return false; at the top of is_pool_log_enabled()
// =====================================================
inline bool is_pool_log_enabled() {
// return false;
static int enabled = -1;
if (enabled < 0) {
const char* env = getenv("SFT_POOL_LOG");
enabled = (env && env[0] != '0') ? 1 : 0;
}
return enabled == 1;
}
inline FILE* get_pool_log_file() {
static FILE* f = nullptr;
if (f == nullptr) {
const char* path = getenv("SFT_POOL_LOG_FILE");
if (!path) path = "sft_pool_log.txt";
f = fopen(path, "a");
if (f) {
fprintf(f,
"# event | layer | numa | qlen | cache_stack_top | "
"fwd_work_bytes | cache_pool_bytes | bwd_pool_bytes | "
"alloc_request_bytes | detail\n");
fflush(f);
}
}
return f;
}
// Printf-style pool log: writes one line per event
// event: "fwd_alloc", "fwd_cache_alloc", "bwd_alloc", "cache_free", "fwd_enter", "bwd_enter", etc.
#define SFT_POOL_LOG(event, layer, numa, qlen, cache_top, fwd_bytes, cache_bytes, bwd_bytes, req_bytes, ...) \
do { \
if (is_pool_log_enabled()) { \
FILE* _pf = get_pool_log_file(); \
if (_pf) { \
fprintf(_pf, \
"%-16s | L%02d | N%d | q%-5d | cst=%-2d | " \
"fwd=%10zu | cache=%10zu | bwd=%10zu | req=%10zu | ", \
event, layer, numa, qlen, cache_top, (size_t)(fwd_bytes), (size_t)(cache_bytes), (size_t)(bwd_bytes), \
(size_t)(req_bytes)); \
fprintf(_pf, __VA_ARGS__); \
fprintf(_pf, "\n"); \
fflush(_pf); \
} \
} \
} while (0)
// =====================================================
// Type trait to detect if kernel supports standard mat_mul API
// Only these kernels have the standard amx::mat_mul(m,n,k,ba,bb,bc,ith,nth) overload
// KGroup kernels use mat_mul_kgroup() with different BufferB interface
// =====================================================
template <typename T>
struct supports_standard_mat_mul : std::false_type {};
template <>
struct supports_standard_mat_mul<amx::GemmKernel224BF> : std::true_type {};
template <>
struct supports_standard_mat_mul<amx::GemmKernel224Int8> : std::true_type {};
template <>
struct supports_standard_mat_mul<amx::GemmKernel224Int4> : std::true_type {};
template <>
struct supports_standard_mat_mul<amx::GemmKernel224Int4_1> : std::true_type {};
template <typename T>
inline constexpr bool supports_standard_mat_mul_v = supports_standard_mat_mul<T>::value;
// =====================================================
// Type trait: kernel has direct BB→BB transposed repack (from_bb_transposed)
// INT4 lacks this, so it falls back to to_mat + from_mat_transposed.
// =====================================================
template <typename T>
struct has_bb_transposed_repack : std::false_type {};
template <>
struct has_bb_transposed_repack<amx::GemmKernel224BF> : std::true_type {};
template <>
struct has_bb_transposed_repack<amx::GemmKernel224Int8> : std::true_type {};
template <typename T>
inline constexpr bool has_bb_transposed_repack_v = has_bb_transposed_repack<T>::value;
/**
* @brief Forward cache structure for gradient checkpointing.
*
* Stores intermediate values from forward pass needed for backward computation.
* Supports multiple cache slots for gradient checkpointing (multiple forwards before backward).
*/
struct ForwardCache {
// Intermediate values (need to be copied as next layer's forward will overwrite)
ggml_bf16_t* input_cache = nullptr; // [qlen, hidden_size]
ggml_bf16_t* gate_output_cache = nullptr; // [tokens_total, intermediate_size]
ggml_bf16_t* up_output_cache = nullptr; // [tokens_total, intermediate_size]
ggml_bf16_t* intermediate_cache = nullptr; // [tokens_total, intermediate_size] (after activation)
ggml_bf16_t* down_output_cache = nullptr; // [tokens_total, hidden_size] (for grad_weights)
float* down_lora_u_cache = nullptr; // [tokens_total, lora_rank] FP32, reused by backward grad_B
// Routing information
std::vector<int64_t> expert_ids_cache;
std::vector<float> weights_cache;
std::vector<int> m_local_num_cache;
std::vector<std::vector<int>> m_local_pos_cache;
std::vector<int> m_expert_id_map_cache;
int qlen_cache = 0;
int k_cache = 0;
int activated_expert_cache = 0;
bool valid = false;
};
/**
* @brief Singleton holding shared forward/backward working pools (one per NUMA node).
*
* In this training path, each NUMA partition executes layer forward/backward sequentially,
* so seqlen-dependent working buffers can be reused across all MoE layers on that partition.
* The shared pools are process-lifetime (freed on static destruction).
*/
struct SFTSharedPools {
struct PerNuma {
void* fwd_work = nullptr;
size_t fwd_work_bytes = 0;
void* bwd_work = nullptr;
size_t bwd_work_bytes = 0;
void* bwd_bb = nullptr;
size_t bwd_bb_bytes = 0;
int bwd_bb_owner_layer = -1; // layer_idx that last repacked into this pool
void* cache = nullptr;
size_t cache_bytes = 0;
};
std::vector<PerNuma> pools;
std::mutex mu;
static SFTSharedPools& instance() {
static SFTSharedPools inst;
return inst;
}
void ensure_numa_count(int n) {
if ((int)pools.size() < n) pools.resize(n);
}
static void* acquire(void*& ptr, size_t& cur_bytes, size_t required, size_t align) {
required = (required + align - 1) / align * align;
if (required <= cur_bytes) return ptr;
if (ptr) {
free(ptr);
ptr = nullptr;
cur_bytes = 0;
}
int rc = posix_memalign(&ptr, align, required);
if (rc != 0 || !ptr) throw std::runtime_error("SFTSharedPools: posix_memalign failed");
cur_bytes = required;
return ptr;
}
~SFTSharedPools() {
for (auto& p : pools) {
if (p.fwd_work) {
free(p.fwd_work);
p.fwd_work = nullptr;
}
if (p.bwd_work) {
free(p.bwd_work);
p.bwd_work = nullptr;
}
if (p.bwd_bb) {
free(p.bwd_bb);
p.bwd_bb = nullptr;
}
if (p.cache) {
free(p.cache);
p.cache = nullptr;
}
}
}
private:
SFTSharedPools() = default;
};
/**
* @brief AMX SFT MoE implementation with LoRA support.
*
* Inherits from AMX_MOE_TP and adds:
* - LoRA computation for gate/up/down projections
* - Forward cache for gradient checkpointing
* - Backward pass implementation
*
* @tparam T The GEMM kernel type (e.g., GemmKernel224BF, GemmKernel224Int8)
* @tparam BaseMOE The base MOE class template (default: AMX_MOE_TP, can be AMX_AWQ_MOE_TP or AMX_K2_MOE_TP)
* @tparam SkipLoRA If true, skip all LoRA computation in backward pass,
* only compute base weight contribution to grad_input. (default: false)
*/
template <class T, template <class> class BaseMOE = AMX_MOE_TP, bool SkipLoRA = false>
class AMX_SFT_MOE_TP : public BaseMOE<T> {
public:
static constexpr bool kSkipLoRA = SkipLoRA;
protected:
using Base = BaseMOE<T>;
using Base::config_;
using Base::down_ba_;
using Base::down_bb_;
using Base::down_bc_;
using Base::gate_bb_;
using Base::gate_bc_;
using Base::gate_up_ba_;
using Base::m_expert_id_map_;
using Base::m_local_down_output_;
using Base::m_local_down_output_ptr_;
using Base::m_local_gate_output_;
using Base::m_local_gate_output_ptr_;
using Base::m_local_input_;
using Base::m_local_input_ptr_;
using Base::m_local_num_;
using Base::m_local_pos_;
using Base::m_local_up_output_;
using Base::m_local_up_output_ptr_;
using Base::tp_part_idx;
using Base::up_bb_;
using Base::up_bc_;
private:
static constexpr size_t kAmxAlignment = 64;
static inline size_t round_up(size_t x, size_t align) { return (x + align - 1) / align * align; }
static inline void* alloc_aligned(size_t align, size_t bytes) {
if (bytes == 0) return nullptr;
void* ptr = nullptr;
int rc = posix_memalign(&ptr, align, bytes);
if (rc != 0 || !ptr) {
errno = rc; // posix_memalign returns error code instead of setting errno
perror("posix_memalign");
throw std::runtime_error("posix_memalign failed");
}
return ptr;
}
void alloc_or_resize_forward_pool(size_t required_bytes) {
auto& shared = SFTSharedPools::instance();
std::lock_guard<std::mutex> guard(shared.mu);
shared.ensure_numa_count(tp_part_idx + 1);
auto& p = shared.pools[tp_part_idx];
forward_pool_ = SFTSharedPools::acquire(p.fwd_work, p.fwd_work_bytes, required_bytes, kAmxAlignment);
forward_pool_bytes_ = p.fwd_work_bytes;
}
void alloc_or_resize_cache_pool(size_t required_bytes) {
required_bytes = round_up(required_bytes, kAmxAlignment);
if (required_bytes == 0) return;
if (config_.share_cache_pool) {
// Shared mode: all layers share one cache pool via SFTSharedPools.
// Safe only with gradient checkpoint (one layer at a time).
auto& shared = SFTSharedPools::instance();
std::lock_guard<std::mutex> guard(shared.mu);
shared.ensure_numa_count(tp_part_idx + 1);
auto& p = shared.pools[tp_part_idx];
cache_pool_ = SFTSharedPools::acquire(p.cache, p.cache_bytes, required_bytes, kAmxAlignment);
cache_pool_bytes_ = p.cache_bytes;
cache_locally_owned_ = false;
} else {
// Per-layer mode: each layer has its own cache pool.
if (required_bytes <= cache_pool_bytes_) return;
if (cache_pool_ && cache_locally_owned_) {
free(cache_pool_);
cache_pool_ = nullptr;
cache_pool_bytes_ = 0;
}
cache_pool_ = alloc_aligned(kAmxAlignment, required_bytes);
cache_pool_bytes_ = required_bytes;
cache_locally_owned_ = true;
}
}
// SFT configuration
MOESFTConfig sft_config_;
// LoRA configuration (from MOESFTConfig)
int lora_rank_;
float lora_scaling_;
// LoRA weight pointers (directly pointing to Python tensors)
ggml_bf16_t* gate_lora_a_; // [expert_num, lora_rank, hidden_size]
ggml_bf16_t* gate_lora_b_; // [expert_num, intermediate_size, lora_rank]
ggml_bf16_t* up_lora_a_;
ggml_bf16_t* up_lora_b_;
ggml_bf16_t* down_lora_a_;
ggml_bf16_t* down_lora_b_;
ggml_bf16_t* gate_lora_b_transposed_ = nullptr; // [expert_num, lora_rank, intermediate_size]
ggml_bf16_t* up_lora_b_transposed_ = nullptr; // [expert_num, lora_rank, intermediate_size]
ggml_bf16_t* down_lora_b_transposed_ = nullptr; // [expert_num, lora_rank, hidden_size]
// LoRA intermediate buffer (using shared_mem_buffer pool allocation)
// For lora_A @ x results
ggml_bf16_t* lora_intermediate_; // [max_len * k, lora_rank] - kept for compatibility but not used
void* lora_intermediate_pool_;
size_t lora_intermediate_pool_bytes_;
// Forward cache stack (for gradient checkpointing)
std::vector<ForwardCache> cache_stack_;
int cache_stack_top_ = 0; // Stack top pointer
int max_cache_depth_;
// Last backward expert token distribution (for load balancing analysis)
std::vector<int> last_backward_expert_tokens_;
// Experts that had non-zero contributions in last backward (for selective zeroing)
std::vector<int> last_backward_active_experts_;
bool grad_outputs_initialized_ = false;
// Cache buffer pools
void* cache_input_pool_ = nullptr;
void* cache_gate_output_pool_ = nullptr;
void* cache_up_output_pool_ = nullptr;
void* cache_intermediate_pool_ = nullptr;
void* cache_down_output_pool_ = nullptr; // For grad_weights computation
void* cache_down_lora_u_pool_ = nullptr; // For down LoRA backward grad_B reuse
size_t cache_slot_bytes_input_;
size_t cache_slot_bytes_intermediate_;
size_t cache_slot_bytes_down_lora_u_;
// Forward pooled buffers (shared across layers via SFTSharedPools singleton)
void* forward_pool_ = nullptr;
size_t forward_pool_bytes_ = 0;
// Cache pool (per-instance or shared via SFTSharedPools when share_cache_pool=true)
void* cache_pool_ = nullptr;
size_t cache_pool_bytes_ = 0;
bool cache_locally_owned_ = true; // false when shared via SFTSharedPools
// Gradient intermediate buffers
ggml_bf16_t* grad_intermediate_ = nullptr; // [max_len * k, intermediate_size]
ggml_bf16_t* grad_gate_output_ = nullptr; // [max_len * k, intermediate_size]
ggml_bf16_t* grad_up_output_ = nullptr; // [max_len * k, intermediate_size]
void* grad_intermediate_pool_ = nullptr;
void* grad_gate_output_pool_ = nullptr;
void* grad_up_output_pool_ = nullptr;
// Buffer sizes for dynamic allocation
size_t grad_buffer_bytes_ = 0;
size_t cache_down_output_bytes_ = 0;
// Precomputed offsets for cache operations (avoid repeated heap allocation)
std::vector<size_t> cache_offsets_;
// =====================================================
// AMX-optimized LoRA GEMM buffers (performance optimization)
// =====================================================
// Padded lora_rank for AMX alignment (must be multiple of K_STEP=32)
int padded_lora_rank_;
// LoRA weight BufferB for AMX GEMM
// Step 1 weights: lora_A matrices [padded_lora_rank, hidden_size or intermediate_size]
std::vector<std::shared_ptr<typename T::BufferB>> gate_lora_a_bb_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferB>> up_lora_a_bb_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferB>> down_lora_a_bb_; // [expert_num]
// Step 2 weights: lora_B matrices [output_dim, padded_lora_rank]
std::vector<std::shared_ptr<typename T::BufferB>> gate_lora_b_bb_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferB>> up_lora_b_bb_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferB>> down_lora_b_bb_; // [expert_num]
// Transposed weights for backward GEMM
std::vector<std::shared_ptr<typename T::BufferB>> gate_lora_a_t_bb_; // [expert_num] [hidden_size, padded_lora_rank]
std::vector<std::shared_ptr<typename T::BufferB>> up_lora_a_t_bb_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferB>>
gate_lora_b_t_bb_; // [expert_num] [padded_lora_rank, intermediate_size]
std::vector<std::shared_ptr<typename T::BufferB>> up_lora_b_t_bb_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferB>>
down_lora_a_t_bb_; // [expert_num] [intermediate_size, padded_lora_rank]
std::vector<std::shared_ptr<typename T::BufferB>> down_lora_b_t_bb_; // [expert_num] [padded_lora_rank, hidden_size]
// LoRA intermediate BufferA and BufferC
// For step 1 output / step 2 input: [num_tokens, padded_lora_rank]
// Gate and Up need SEPARATE buffers to avoid race condition in parallel execution
std::vector<std::shared_ptr<typename T::BufferA>> lora_gate_intermediate_ba_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferA>> lora_up_intermediate_ba_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferC>> lora_gate_intermediate_bc_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferC>> lora_up_intermediate_bc_; // [expert_num]
// LoRA step 2 output BufferC (for accumulation before adding to main output)
std::vector<std::shared_ptr<typename T::BufferC>> lora_gate_out_bc_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferC>> lora_up_out_bc_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferC>> lora_down_out_bc_; // [expert_num]
// LoRA intermediate output pointers (for step 1 -> step 2)
// Gate and Up need SEPARATE pointers to avoid race condition in parallel execution
std::vector<ggml_bf16_t*> lora_gate_intermediate_ptr_; // [expert_num]
std::vector<ggml_bf16_t*> lora_up_intermediate_ptr_; // [expert_num]
// LoRA buffer pools
void* lora_bb_pool_ = nullptr; // All LoRA weight BufferB
void* lora_ba_pool_ = nullptr; // LoRA intermediate BufferA
void* lora_bc_inter_pool_ = nullptr; // LoRA step 1 output BufferC
void* lora_bc_out_pool_ = nullptr; // LoRA step 2 output BufferC
void* lora_intermediate_bf16_pool_ = nullptr; // BF16 intermediate for step 1->step 2
// Buffer pool sizes
size_t lora_bb_pool_bytes_ = 0;
size_t lora_ba_pool_bytes_ = 0;
size_t lora_bc_inter_pool_bytes_ = 0;
size_t lora_bc_out_pool_bytes_ = 0;
size_t lora_intermediate_bf16_pool_bytes_ = 0;
// =====================================================
// Backward pass AMX buffers
// =====================================================
// BufferA for grad_output (scattered to per-expert)
std::vector<std::shared_ptr<typename T::BufferA>> grad_output_ba_; // [expert_num]
// BufferC for backward GEMM outputs
std::vector<std::shared_ptr<typename T::BufferC>> grad_intermediate_bc_; // [expert_num]
std::vector<std::shared_ptr<typename T::BufferC>> grad_gate_up_bc_; // [expert_num]
// BF16 buffer for scattered grad_output (before quantization to BufferA)
std::vector<ggml_bf16_t*> grad_output_bf16_ptr_; // [expert_num]
// Backward buffer pools
void* backward_ba_pool_ = nullptr;
void* backward_bc_pool_ = nullptr;
void* grad_output_bf16_pool_ = nullptr;
void* backward_pool_ = nullptr;
size_t backward_pool_bytes_ = 0;
// Backward buffer pool sizes
size_t backward_ba_pool_bytes_ = 0;
size_t backward_bc_pool_bytes_ = 0;
size_t grad_output_bf16_pool_bytes_ = 0;
// LoRA gradient computation pools (FP32, used in bwd_down_lora_precompute and grad computation)
float* lora_grad_out_pool_ = nullptr; // [max_len * num_experts_per_tok * hidden_size]
float* lora_inter_proj_pool_ = nullptr; // [max_len * num_experts_per_tok * lora_rank]
float* lora_grad_times_b_pool_ = nullptr; // [max_len * num_experts_per_tok * lora_rank]
float* down_lora_grad_b_accum_pool_ = nullptr; // [expert_num * hidden_size * lora_rank]
float* down_lora_grad_a_accum_pool_ = nullptr; // [expert_num * intermediate_size * lora_rank]
size_t lora_grad_out_pool_bytes_ = 0;
size_t lora_inter_proj_pool_bytes_ = 0;
size_t lora_grad_times_b_pool_bytes_ = 0;
size_t down_lora_grad_b_accum_pool_bytes_ = 0;
size_t down_lora_grad_a_accum_pool_bytes_ = 0;
std::unique_ptr<std::mutex[]> down_lora_grad_mutexes_;
std::vector<uint8_t> down_lora_grad_accum_initialized_;
// =====================================================
// Backward pass BufferB for transposed base weights
// =====================================================
// For backward GEMM, we need transposed versions of the base weights:
// - Forward gate/up: input @ W^T uses gate_bb_[intermediate_size, hidden_size]
// - Backward gate/up: grad @ W uses BufferB[hidden_size, intermediate_size]
// - Forward down: intermediate @ W^T uses down_bb_[hidden_size, intermediate_size]
// - Backward down: grad_output @ W uses BufferB[intermediate_size, hidden_size]
std::vector<std::shared_ptr<typename T::BufferB>> gate_backward_bb_; // [hidden_size, intermediate_size]
std::vector<std::shared_ptr<typename T::BufferB>> up_backward_bb_; // [hidden_size, intermediate_size]
std::vector<std::shared_ptr<typename T::BufferB>> down_backward_bb_; // [intermediate_size, hidden_size]
// Backward BufferB pool
void* backward_bb_pool_ = nullptr;
size_t backward_bb_pool_bytes_ = 0;
// Flag to track if backward weights have been prepared
bool backward_weights_prepared_ = false;
// true = per-instance alloc, false = shared pool or nullptr
bool backward_bb_locally_owned_ = false;
// Flag to track if LoRA weights have been converted to BufferB format
bool lora_weights_prepared_ = false;
bool lora_backward_weights_prepared_ = false;
bool lora_b_transposed_ = false; // For transpose_lora_b_weights (used in forward)
bool lora_a_bb_prepared_ = false; // For gate_lora_a_bb_ and up_lora_a_bb_ (used in backward)
private:
void alloc_or_resize_backward_pool(size_t required_bytes) {
auto& shared = SFTSharedPools::instance();
std::lock_guard<std::mutex> guard(shared.mu);
shared.ensure_numa_count(tp_part_idx + 1);
auto& p = shared.pools[tp_part_idx];
backward_pool_ = SFTSharedPools::acquire(p.bwd_work, p.bwd_work_bytes, required_bytes, kAmxAlignment);
backward_pool_bytes_ = p.bwd_work_bytes;
}
void alloc_or_resize_backward_bb(size_t required_bytes) {
auto& shared = SFTSharedPools::instance();
std::lock_guard<std::mutex> guard(shared.mu);
shared.ensure_numa_count(tp_part_idx + 1);
auto& p = shared.pools[tp_part_idx];
backward_bb_pool_ = SFTSharedPools::acquire(p.bwd_bb, p.bwd_bb_bytes, required_bytes, kAmxAlignment);
backward_bb_pool_bytes_ = p.bwd_bb_bytes;
}
public:
AMX_SFT_MOE_TP(MOESFTConfig config, int tp_part_idx = 0)
: Base(static_cast<GeneralMOEConfig>(config), tp_part_idx), sft_config_(config) {
printf(
"Creating AMX_SFT_MOE_TP layer=%d tp_part=%d at numa %d skiplora %s share_backward_bb %s share_cache_pool %s\n",
config.layer_idx, tp_part_idx, numa_node_of_cpu(sched_getcpu()), SkipLoRA ? "true" : "false",
config.share_backward_bb ? "true" : "false", config.share_cache_pool ? "true" : "false");
// Initialize LoRA configuration
lora_rank_ = config.lora_rank;
lora_scaling_ = config.lora_scaling();
max_cache_depth_ = config.max_cache_depth;
// Get LoRA weight pointers
gate_lora_a_ = (ggml_bf16_t*)config.gate_lora_a;
gate_lora_b_ = (ggml_bf16_t*)config.gate_lora_b;
up_lora_a_ = (ggml_bf16_t*)config.up_lora_a;
up_lora_b_ = (ggml_bf16_t*)config.up_lora_b;
down_lora_a_ = (ggml_bf16_t*)config.down_lora_a;
down_lora_b_ = (ggml_bf16_t*)config.down_lora_b;
down_lora_grad_mutexes_ = std::make_unique<std::mutex[]>(config.expert_num);
down_lora_grad_accum_initialized_.assign(config.expert_num, 0);
// Allocate pre-transposed LoRA B weight buffers (once, in constructor)
alloc_transposed_lora_weights();
// Initialize all buffers in a single alloc() to avoid memory overlap
// (Bug #15: SharedMemBuffer assigns all alloc() calls from same base address)
init_all_buffers();
}
// Constructor to satisfy MOE_TP_PART concept (takes GeneralMOEConfig)
AMX_SFT_MOE_TP(GeneralMOEConfig config, int tp_part_idx) : AMX_SFT_MOE_TP(MOESFTConfig(config), tp_part_idx) {}
~AMX_SFT_MOE_TP() {
// forward_pool_ → shared (singleton-owned, process-lifetime), do NOT free
// backward_pool_ → shared (singleton-owned, process-lifetime), do NOT free
// Cache pool: only free if locally owned (not shared via SFTSharedPools)
if (cache_locally_owned_ && cache_pool_) free(cache_pool_);
// Persistent buffers (allocated in constructor)
if (lora_bb_pool_) free(lora_bb_pool_);
if (backward_bb_locally_owned_ && backward_bb_pool_) free(backward_bb_pool_);
// Pre-transposed LoRA weights
free_transposed_lora_weights();
}
/**
* @brief Allocate forward-phase buffers.
* Called at the start of forward_sft.
* - LoRA working buffers: always allocated (needed for forward LoRA computation)
* - Cache buffers: only allocated when save_for_backward=true
*
* @param alloc_cache Whether to allocate cache buffers (for backward pass)
*/
void alloc_forward_buffers(bool alloc_cache) {
// 1. Working buffers → shared pool (across all layers on same NUMA)
size_t work_required = 0;
work_required += round_up(lora_ba_pool_bytes_, kAmxAlignment);
work_required += round_up(lora_bc_inter_pool_bytes_, kAmxAlignment);
work_required += round_up(lora_bc_out_pool_bytes_, kAmxAlignment);
work_required += round_up(lora_intermediate_bf16_pool_bytes_, kAmxAlignment);
alloc_or_resize_forward_pool(work_required);
SFT_POOL_LOG("fwd_work", config_.layer_idx, tp_part_idx, 0, cache_stack_top_, forward_pool_bytes_,
cache_pool_bytes_, backward_pool_bytes_, work_required, "shared_pool alloc_cache=%d",
(int)alloc_cache);
auto* work_base = static_cast<uint8_t*>(forward_pool_);
size_t offset = 0;
auto assign = [&](void** ptr, size_t bytes) {
if (bytes == 0) {
*ptr = nullptr;
return;
}
*ptr = work_base + offset;
offset += round_up(bytes, kAmxAlignment);
};
// LoRA working buffers (always needed for forward, even for inference)
assign(&lora_ba_pool_, lora_ba_pool_bytes_);
assign(&lora_bc_inter_pool_, lora_bc_inter_pool_bytes_);
assign(&lora_bc_out_pool_, lora_bc_out_pool_bytes_);
assign(&lora_intermediate_bf16_pool_, lora_intermediate_bf16_pool_bytes_);
// 2. Cache buffers → per-instance pool
if (alloc_cache) {
const size_t cache_input_bytes = cache_slot_bytes_input_ * max_cache_depth_;
const size_t cache_intermediate_bytes = cache_slot_bytes_intermediate_ * max_cache_depth_;
const size_t cache_down_lora_u_bytes = cache_slot_bytes_down_lora_u_ * max_cache_depth_;
size_t cache_required = 0;
cache_required += round_up(cache_input_bytes, kAmxAlignment);
cache_required += round_up(cache_intermediate_bytes, kAmxAlignment) * 3;
cache_required += round_up(cache_down_lora_u_bytes, kAmxAlignment);
cache_required += round_up(cache_down_output_bytes_, kAmxAlignment);
alloc_or_resize_cache_pool(cache_required);
SFT_POOL_LOG("fwd_cache", config_.layer_idx, tp_part_idx, 0, cache_stack_top_, forward_pool_bytes_,
cache_pool_bytes_, backward_pool_bytes_, cache_required, "cache_pool alloc");
auto* cache_base = static_cast<uint8_t*>(cache_pool_);
size_t cache_offset = 0;
auto cache_assign = [&](void** ptr, size_t bytes) {
if (bytes == 0) {
*ptr = nullptr;
return;
}
*ptr = cache_base + cache_offset;
cache_offset += round_up(bytes, kAmxAlignment);
};
cache_assign(&cache_input_pool_, cache_input_bytes);
cache_assign(&cache_gate_output_pool_, cache_intermediate_bytes);
cache_assign(&cache_up_output_pool_, cache_intermediate_bytes);
cache_assign(&cache_intermediate_pool_, cache_intermediate_bytes);
cache_assign(&cache_down_lora_u_pool_, cache_down_lora_u_bytes);
cache_assign(&cache_down_output_pool_, cache_down_output_bytes_);
// Initialize cache stack pointers (use size_t to prevent int overflow)
for (int i = 0; i < max_cache_depth_; i++) {
const size_t si = i;
const size_t ml = config_.max_len;
const size_t k_tok = config_.num_experts_per_tok;
const size_t H = config_.hidden_size;
const size_t I = config_.intermediate_size;
cache_stack_[i].input_cache = (ggml_bf16_t*)cache_input_pool_ + si * ml * H;
cache_stack_[i].gate_output_cache = (ggml_bf16_t*)cache_gate_output_pool_ + si * ml * k_tok * I;
cache_stack_[i].up_output_cache = (ggml_bf16_t*)cache_up_output_pool_ + si * ml * k_tok * I;
cache_stack_[i].intermediate_cache = (ggml_bf16_t*)cache_intermediate_pool_ + si * ml * k_tok * I;
cache_stack_[i].down_lora_u_cache = (float*)cache_down_lora_u_pool_ + si * ml * k_tok * lora_rank_;
cache_stack_[i].down_output_cache = (ggml_bf16_t*)cache_down_output_pool_ + si * ml * k_tok * H;
}
} else {
cache_input_pool_ = nullptr;
cache_gate_output_pool_ = nullptr;
cache_up_output_pool_ = nullptr;
cache_intermediate_pool_ = nullptr;
cache_down_lora_u_pool_ = nullptr;
cache_down_output_pool_ = nullptr;
}
}
/**
* @brief Free LoRA working buffers (for inference mode).
* Called at the end of forward_sft when save_for_backward=false.
*/
void free_lora_working_buffers() {
// Intentionally keep pooled buffers to avoid frequent alloc/free in inference loops.
}
/**
* @brief Allocate backward-phase buffers.
* Called at the start of backward.
* Includes: gradient buffers + backward working buffers
*/
void alloc_backward_buffers() {
// Allocate backward-phase buffers from a single resizable pool (like forward_pool_).
size_t required = 0;
required += round_up(grad_buffer_bytes_, kAmxAlignment) * 3; // grad_intermediate, grad_gate_output, grad_up_output
required += round_up(backward_ba_pool_bytes_, kAmxAlignment);
required += round_up(backward_bc_pool_bytes_, kAmxAlignment);
required += round_up(grad_output_bf16_pool_bytes_, kAmxAlignment);
required += round_up(lora_grad_out_pool_bytes_, kAmxAlignment);
required += round_up(lora_inter_proj_pool_bytes_, kAmxAlignment);
required += round_up(lora_grad_times_b_pool_bytes_, kAmxAlignment);
required += round_up(down_lora_grad_b_accum_pool_bytes_, kAmxAlignment);
required += round_up(down_lora_grad_a_accum_pool_bytes_, kAmxAlignment);
alloc_or_resize_backward_pool(required);
SFT_POOL_LOG("bwd_alloc", config_.layer_idx, tp_part_idx, 0, cache_stack_top_, forward_pool_bytes_,
cache_pool_bytes_, backward_pool_bytes_, required, "backward_pool alloc");
auto* base = static_cast<uint8_t*>(backward_pool_);
size_t offset = 0;
auto assign = [&](void** ptr, size_t bytes) {
if (bytes == 0) {
*ptr = nullptr;
return;
}
*ptr = base + offset;
offset += round_up(bytes, kAmxAlignment);
};
assign(&grad_intermediate_pool_, grad_buffer_bytes_);
assign(&grad_gate_output_pool_, grad_buffer_bytes_);
assign(&grad_up_output_pool_, grad_buffer_bytes_);
grad_intermediate_ = (ggml_bf16_t*)grad_intermediate_pool_;
grad_gate_output_ = (ggml_bf16_t*)grad_gate_output_pool_;
grad_up_output_ = (ggml_bf16_t*)grad_up_output_pool_;
assign(&backward_ba_pool_, backward_ba_pool_bytes_);
assign(&backward_bc_pool_, backward_bc_pool_bytes_);
assign(&grad_output_bf16_pool_, grad_output_bf16_pool_bytes_);
assign((void**)&lora_grad_out_pool_, lora_grad_out_pool_bytes_);
assign((void**)&lora_inter_proj_pool_, lora_inter_proj_pool_bytes_);
assign((void**)&lora_grad_times_b_pool_, lora_grad_times_b_pool_bytes_);
assign((void**)&down_lora_grad_b_accum_pool_, down_lora_grad_b_accum_pool_bytes_);
assign((void**)&down_lora_grad_a_accum_pool_, down_lora_grad_a_accum_pool_bytes_);
}
/**
* @brief Free seqlen-dependent buffers after backward.
* Called at the end of backward.
*/
void free_seqlen_buffers() {
SFT_POOL_LOG("cache_free", config_.layer_idx, tp_part_idx, 0, cache_stack_top_, forward_pool_bytes_,
cache_pool_bytes_, backward_pool_bytes_, cache_pool_bytes_, "freeing cache_pool");
// Hard check: all cache entries must have been popped before freeing.
// A non-zero cache_stack_top_ means backward didn't consume all pushes,
// and freeing would leave dangling pointers in the cache stack.
if (cache_stack_top_ != 0) {
fprintf(stderr,
"[KT-MOE BUG] free_seqlen_buffers called with cache_stack_top_=%d "
"(expected 0) on layer %d numa %d. Skipping cache free.\n",
cache_stack_top_, config_.layer_idx, tp_part_idx);
return; // Do NOT free — better to leak than corrupt
}
if (cache_locally_owned_ && cache_pool_) {
free(cache_pool_);
}
cache_pool_ = nullptr;
cache_pool_bytes_ = 0;
cache_input_pool_ = nullptr;
cache_gate_output_pool_ = nullptr;
cache_up_output_pool_ = nullptr;
cache_intermediate_pool_ = nullptr;
cache_down_lora_u_pool_ = nullptr;
cache_down_output_pool_ = nullptr;
}
/**
* @brief Set LoRA parameters after construction (Bug #007 fix).
*
* This is needed because TP_MOE base class uses GeneralMOEConfig which
* doesn't have lora_rank/lora_alpha fields, causing object slicing.
* The TP_MOE_SFT wrapper calls this method to propagate correct values.
*
* @param rank LoRA rank (typically 8 or 16)
* @param alpha LoRA alpha for scaling (lora_scaling = alpha / rank)
*/
void set_lora_params(int rank, float alpha) {
lora_rank_ = rank;
lora_scaling_ = alpha / rank;
}
/**
* @brief SFT Forward pass with optional caching for backward.
*
* Computes: output = Σ weights[i] * down_proj(silu(gate_proj(x) + gate_lora(x)) * (up_proj(x) + up_lora(x))) +
* down_lora(...)
*
* @param qlen Number of tokens
* @param k Number of experts per token
* @param expert_ids Expert indices [qlen, k]
* @param weights Expert weights [qlen, k]
* @param input Input tensor [qlen, hidden_size]
* @param output Output tensor [qlen, hidden_size]
* @param save_for_backward Whether to save intermediate values for backward pass
*/
void forward_sft(int qlen, int k, const int64_t* expert_ids, const float* weights, const void* input, void* output,
bool save_for_backward) {
uint64_t _fwd_start_cycles = __rdtsc();
SFT_POOL_LOG("fwd_enter", config_.layer_idx, tp_part_idx, qlen, cache_stack_top_, forward_pool_bytes_,
cache_pool_bytes_, backward_pool_bytes_, 0, "save_bwd=%d", (int)save_for_backward);
// =====================================================
// Bounds Check: Verify qlen doesn't exceed max_len
// =====================================================
if (is_nan_check_enabled() && qlen > config_.max_len) {
printf(ANSI_BG_RED "[OVERFLOW L%d] qlen=%d EXCEEDS max_len=%d! Buffer overflow will occur!" ANSI_COLOR_RESET "\n",
config_.layer_idx, qlen, config_.max_len);
}
// NaN Check: Input
if (is_nan_check_enabled()) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Input", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)input, qlen * config_.hidden_size, label);
}
// ★ Allocate forward-phase buffers ★
// LoRA working buffers are always needed for forward (even for inference)
// Cache buffers are only needed when save_for_backward=true
alloc_forward_buffers(save_for_backward);
auto pool = config_.pool->get_subpool(tp_part_idx);
// Lazy preparation: transpose LoRA B weights for AVX512 fused_add kernel
if (!lora_b_transposed_ && gate_lora_b_ != nullptr) {
transpose_lora_b_weights();
lora_b_transposed_ = true;
}
// Step 1: Expert routing (reuse base class logic)
int activated_expert = 0;
std::fill(m_local_num_.begin(), m_local_num_.end(), 0);
for (int i = 0; i < qlen; i++) {
for (int j = 0; j < k; j++) {
if (expert_ids[i * k + j] < config_.num_gpu_experts || expert_ids[i * k + j] >= config_.expert_num) {
continue;
}
m_local_pos_[i][j] = m_local_num_[expert_ids[i * k + j]]++;
}
}
for (int i = 0; i < config_.expert_num; i++) {
if (m_local_num_[i] > 0) {
m_expert_id_map_[activated_expert] = i;
activated_expert++;
}
}
// Step 2: Buffer pool allocation (reuse base class logic)
size_t offset = 0;
void* gate_up_ba_pool_ptr = Base::gate_up_ba_pool_;
void* gate_bc_pool_ptr = Base::gate_bc_pool_;
void* up_bc_pool_ptr = Base::up_bc_pool_;
void* down_ba_pool_ptr = Base::down_ba_pool_;
void* down_bc_pool_ptr = Base::down_bc_pool_;
constexpr size_t M_STEP = T::M_STEP;
auto align64 = [](size_t v) { return (v + 63) & (~(size_t)63); };
for (int i = 0; i < config_.expert_num; i++) {
m_local_input_ptr_[i] = m_local_input_ + offset * config_.hidden_size;
m_local_gate_output_ptr_[i] = m_local_gate_output_ + offset * config_.intermediate_size;
m_local_up_output_ptr_[i] = m_local_up_output_ + offset * config_.intermediate_size;
m_local_down_output_ptr_[i] = m_local_down_output_ + offset * config_.hidden_size;
offset += m_local_num_[i];
if (m_local_num_[i] == 0) {
continue;
}
size_t max_m = (m_local_num_[i] + M_STEP - 1) / M_STEP * M_STEP;
gate_up_ba_[i]->max_m = max_m;
gate_up_ba_[i]->set_data(gate_up_ba_pool_ptr);
gate_up_ba_pool_ptr =
(void*)((uintptr_t)gate_up_ba_pool_ptr + align64(Base::buffer_a_required_size(max_m, config_.hidden_size)));
gate_bc_[i]->max_m = max_m;
gate_bc_[i]->set_data(gate_bc_pool_ptr);
gate_bc_pool_ptr = (void*)((uintptr_t)gate_bc_pool_ptr +
align64(Base::buffer_c_required_size(max_m, config_.intermediate_size)));
up_bc_[i]->max_m = max_m;
up_bc_[i]->set_data(up_bc_pool_ptr);
up_bc_pool_ptr =
(void*)((uintptr_t)up_bc_pool_ptr + align64(Base::buffer_c_required_size(max_m, config_.intermediate_size)));
down_ba_[i]->max_m = max_m;
down_ba_[i]->set_data(down_ba_pool_ptr);
down_ba_pool_ptr = (void*)((uintptr_t)down_ba_pool_ptr +
align64(Base::buffer_a_required_size(max_m, config_.intermediate_size)));
down_bc_[i]->max_m = max_m;
down_bc_[i]->set_data(down_bc_pool_ptr);
down_bc_pool_ptr =
(void*)((uintptr_t)down_bc_pool_ptr + align64(Base::buffer_c_required_size(max_m, config_.hidden_size)));
}
// =====================================================
// Bounds Check: Verify base class pool allocation didn't overflow
// =====================================================
if (is_nan_check_enabled()) {
char* gate_up_ba_pool_end = (char*)Base::gate_up_ba_pool_ + Base::gate_up_ba_pool_bytes_;
char* gate_bc_pool_end = (char*)Base::gate_bc_pool_ + Base::gate_bc_pool_bytes_;
char* up_bc_pool_end = (char*)Base::up_bc_pool_ + Base::up_bc_pool_bytes_;
char* down_ba_pool_end = (char*)Base::down_ba_pool_ + Base::down_ba_pool_bytes_;
char* down_bc_pool_end = (char*)Base::down_bc_pool_ + Base::down_bc_pool_bytes_;
bool overflow = false;
if ((char*)gate_up_ba_pool_ptr > gate_up_ba_pool_end) {
size_t used = (char*)gate_up_ba_pool_ptr - (char*)Base::gate_up_ba_pool_;
printf(ANSI_BG_RED
"[OVERFLOW L%d] gate_up_ba_pool: used=%zu, allocated=%zu, OVERFLOW by %zu bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, used, Base::gate_up_ba_pool_bytes_, used - Base::gate_up_ba_pool_bytes_);
overflow = true;
}
if ((char*)gate_bc_pool_ptr > gate_bc_pool_end) {
size_t used = (char*)gate_bc_pool_ptr - (char*)Base::gate_bc_pool_;
printf(ANSI_BG_RED
"[OVERFLOW L%d] gate_bc_pool: used=%zu, allocated=%zu, OVERFLOW by %zu bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, used, Base::gate_bc_pool_bytes_, used - Base::gate_bc_pool_bytes_);
overflow = true;
}
if ((char*)up_bc_pool_ptr > up_bc_pool_end) {
size_t used = (char*)up_bc_pool_ptr - (char*)Base::up_bc_pool_;
printf(ANSI_BG_RED "[OVERFLOW L%d] up_bc_pool: used=%zu, allocated=%zu, OVERFLOW by %zu bytes" ANSI_COLOR_RESET
"\n",
config_.layer_idx, used, Base::up_bc_pool_bytes_, used - Base::up_bc_pool_bytes_);
overflow = true;
}
if ((char*)down_ba_pool_ptr > down_ba_pool_end) {
size_t used = (char*)down_ba_pool_ptr - (char*)Base::down_ba_pool_;
printf(ANSI_BG_RED
"[OVERFLOW L%d] down_ba_pool: used=%zu, allocated=%zu, OVERFLOW by %zu bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, used, Base::down_ba_pool_bytes_, used - Base::down_ba_pool_bytes_);
overflow = true;
}
if ((char*)down_bc_pool_ptr > down_bc_pool_end) {
size_t used = (char*)down_bc_pool_ptr - (char*)Base::down_bc_pool_;
printf(ANSI_BG_RED
"[OVERFLOW L%d] down_bc_pool: used=%zu, allocated=%zu, OVERFLOW by %zu bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, used, Base::down_bc_pool_bytes_, used - Base::down_bc_pool_bytes_);
overflow = true;
}
if (overflow) {
printf("[OVERFLOW DEBUG L%d] qlen=%d, k=%d, max_len=%d, pool_count=%zu, activated_expert=%d\n",
config_.layer_idx, qlen, k, config_.max_len, Base::pool_count_, activated_expert);
printf("[OVERFLOW DEBUG L%d] Total tokens processed: %zu (offset after loop)\n", config_.layer_idx, offset);
}
}
// Step 3: Copy input to expert buffers
auto direct_or_pool = [&](int count, auto&& fn) {
if (qlen < 10) {
for (int i = 0; i < count; i++) {
fn(i);
}
} else {
pool->do_work_stealing_job(count, nullptr, fn, nullptr);
}
};
direct_or_pool(qlen, [&](int i) {
for (int j = 0; j < k; j++) {
if (expert_ids[i * k + j] < config_.num_gpu_experts || expert_ids[i * k + j] >= config_.expert_num) {
continue;
}
memcpy(m_local_input_ptr_[expert_ids[i * k + j]] + m_local_pos_[i][j] * config_.hidden_size,
(ggml_bf16_t*)input + i * config_.hidden_size, sizeof(ggml_bf16_t) * config_.hidden_size);
}
});
// NaN Check: Step 3 - Packed input
if (is_nan_check_enabled()) {
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
if (m_local_num_[expert_idx] > 0) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step3 packed_input expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_input_ptr_[expert_idx], m_local_num_[expert_idx] * config_.hidden_size,
label);
}
}
}
// Step 4: Quantize input
direct_or_pool(activated_expert, [this](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
gate_up_ba_[expert_idx]->from_mat(m_local_num_[expert_idx], m_local_input_ptr_[expert_idx], 0, 1);
});
// Step 5: Gate + Up GEMM (base projection)
int nth = T::recommended_nth(config_.intermediate_size);
pool->do_work_stealing_job(
nth * activated_expert * 2, [](int _) { T::config(); },
[this, nth, qlen](int task_id2) {
int task_id = task_id2 / 2;
bool do_up = task_id2 % 2;
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
this->do_gate_up_gemm(do_up, expert_idx, ith, nth, qlen);
if (do_up) {
up_bc_[expert_idx]->to_mat(m_local_num_[expert_idx], m_local_up_output_ptr_[expert_idx], ith, nth);
} else {
gate_bc_[expert_idx]->to_mat(m_local_num_[expert_idx], m_local_gate_output_ptr_[expert_idx], ith, nth);
}
},
nullptr);
// NaN Check: Step 5 - Gate/Up GEMM output (before LoRA)
if (is_nan_check_enabled()) {
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
if (m_local_num_[expert_idx] > 0) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step5 gate_base_output expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_gate_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.intermediate_size, label);
snprintf(label, sizeof(label), "[FWD L%d] Step5 up_base_output expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_up_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.intermediate_size, label);
}
}
}
// Step 5.5: Gate + Up LoRA (AVX512 BF16 - no BufferB conversion needed)
if (!SkipLoRA) {
compute_lora_gate_up(qlen, activated_expert);
}
// NaN Check: Step 5.5 - Gate/Up output (after LoRA)
if (is_nan_check_enabled()) {
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
if (m_local_num_[expert_idx] > 0) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step5.5 gate_after_lora expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_gate_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.intermediate_size, label);
snprintf(label, sizeof(label), "[FWD L%d] Step5.5 up_after_lora expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_up_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.intermediate_size, label);
}
}
}
// Save gate/up outputs before activation (for backward)
if (save_for_backward) {
// If a cache entry already exists (checkpoint recompute scenario),
// overwrite it instead of pushing a new one. This keeps the cache
// consistent with the current forward's buffer state (max_m, routing)
// and avoids cache stack overflow from duplicate pushes.
ForwardCache& cache = (cache_stack_top_ > 0) ? cache_stack_[cache_stack_top_ - 1] : push_cache();
save_to_cache(cache, qlen, k, expert_ids, weights, activated_expert, input);
// NaN Check: Forward Cache - input, gate_output, up_output
if (is_nan_check_enabled()) {
auto check_cache_bf16 = [&](const char* name, const ggml_bf16_t* ptr, size_t elems) {
if (ptr == nullptr || elems == 0) return;
double sum_sq = 0.0, sum_abs = 0.0, max_abs = 0.0;
int nan_count = 0, inf_count = 0;
for (size_t i = 0; i < elems; i++) {
float v = GGML_BF16_TO_FP32(ptr[i]);
if (v != v) nan_count++;
if (!(v != v) && is_inf_value(v)) inf_count++;
double dv = static_cast<double>(v);
double a = std::fabs(dv);
sum_sq += dv * dv;
sum_abs += a;
if (a > max_abs || a != a) max_abs = a;
}
double norm = std::sqrt(sum_sq);
double abs_mean = sum_abs / static_cast<double>(elems);
bool has_nan_inf = (nan_count > 0 || inf_count > 0);
bool computed_nan = (norm != norm) || (abs_mean != abs_mean);
const char* bg = (has_nan_inf || computed_nan) ? ANSI_BG_RED : ANSI_BG_BLUE;
printf(
"%s[CACHE SAVE L%d] %s: norm=%.6e abs_mean=%.6e abs_max=%.6e nan=%d inf=%d (total=%zu)" ANSI_COLOR_RESET
"\n",
bg, config_.layer_idx, name, norm, abs_mean, max_abs, nan_count, inf_count, elems);
};
size_t total_tokens = 0;
for (int i = 0; i < activated_expert; i++) {
total_tokens += m_local_num_[m_expert_id_map_[i]];
}
check_cache_bf16("input_cache", cache.input_cache, qlen * config_.hidden_size);
check_cache_bf16("gate_output_cache", cache.gate_output_cache, total_tokens * config_.intermediate_size);
check_cache_bf16("up_output_cache", cache.up_output_cache, total_tokens * config_.intermediate_size);
}
}
// Step 6: Activation (silu(gate) * up)
{ Base::apply_activation(activated_expert, nth, qlen); }
// NaN Check: Step 6 - Activation output (silu(gate) * up)
if (is_nan_check_enabled()) {
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
if (m_local_num_[expert_idx] > 0) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step6 activation_output expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_gate_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.intermediate_size, label);
}
}
}
// Save intermediate AFTER activation for backward_down (Bug #17c fix)
if (save_for_backward) {
ForwardCache& cache = cache_stack_[cache_stack_top_ - 1]; // Get the cache we just pushed
save_intermediate_to_cache(cache, activated_expert);
// NaN Check: Forward Cache - intermediate_cache
if (is_nan_check_enabled()) {
size_t total_tokens = 0;
for (int i = 0; i < activated_expert; i++) {
total_tokens += m_local_num_[m_expert_id_map_[i]];
}
size_t elems = total_tokens * config_.intermediate_size;
if (cache.intermediate_cache != nullptr && elems > 0) {
double sum_sq = 0.0, sum_abs = 0.0, max_abs = 0.0;
int nan_count = 0, inf_count = 0;
for (size_t i = 0; i < elems; i++) {
float v = GGML_BF16_TO_FP32(cache.intermediate_cache[i]);
if (v != v) nan_count++;
if (!(v != v) && is_inf_value(v)) inf_count++;
double dv = static_cast<double>(v);
double a = std::fabs(dv);
sum_sq += dv * dv;
sum_abs += a;
if (a > max_abs || a != a) max_abs = a;
}
double norm = std::sqrt(sum_sq);
double abs_mean = sum_abs / static_cast<double>(elems);
bool has_nan_inf = (nan_count > 0 || inf_count > 0);
bool computed_nan = (norm != norm) || (abs_mean != abs_mean);
const char* bg = (has_nan_inf || computed_nan) ? ANSI_BG_RED : ANSI_BG_BLUE;
printf(
"%s[CACHE SAVE L%d] intermediate_cache: norm=%.6e abs_mean=%.6e abs_max=%.6e nan=%d inf=%d "
"(total=%zu)" ANSI_COLOR_RESET "\n",
bg, config_.layer_idx, norm, abs_mean, max_abs, nan_count, inf_count, elems);
}
}
}
// Step 7: Quantize intermediate for down projection
pool->do_work_stealing_job(
activated_expert, nullptr,
[this](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
down_ba_[expert_idx]->from_mat(m_local_num_[expert_idx], m_local_gate_output_ptr_[expert_idx], 0, 1);
},
nullptr);
// Step 8: Down GEMM
nth = T::recommended_nth(config_.hidden_size);
pool->do_work_stealing_job(
nth * activated_expert, [](int _) { T::config(); },
[this, nth, qlen](int task_id) {
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
this->do_down_gemm(expert_idx, ith, nth, qlen);
down_bc_[expert_idx]->to_mat(m_local_num_[expert_idx], m_local_down_output_ptr_[expert_idx], ith, nth);
},
nullptr);
// NaN Check: Step 8 - Down GEMM output (before LoRA)
if (is_nan_check_enabled()) {
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
if (m_local_num_[expert_idx] > 0) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step8 down_base_output expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_down_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.hidden_size, label);
}
}
}
// Step 8.5: Down LoRA (AVX512 BF16 - no BufferB conversion needed)
if (down_lora_a_ != nullptr && down_lora_b_ != nullptr) {
ForwardCache* cache_ptr = save_for_backward ? &cache_stack_[cache_stack_top_ - 1] : nullptr;
compute_lora_down(qlen, activated_expert, cache_ptr);
}
// NaN Check: Step 8.5 - Down output (after LoRA)
if (is_nan_check_enabled()) {
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
if (m_local_num_[expert_idx] > 0) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step8.5 down_after_lora expert=%d tokens=%d", config_.layer_idx,
expert_idx, m_local_num_[expert_idx]);
check_bf16_buffer_for_nan(m_local_down_output_ptr_[expert_idx],
m_local_num_[expert_idx] * config_.hidden_size, label);
}
}
}
// Save down_output for grad_weights computation
if (save_for_backward) {
ForwardCache& cache = cache_stack_[cache_stack_top_ - 1]; // Get the cache we just pushed
save_down_output_to_cache(cache, activated_expert);
}
// Step 9: Weighted merge
pool->do_work_stealing_job(
qlen, nullptr,
[this, output, k, expert_ids, weights](int i) {
for (int e = 0; e < config_.hidden_size; e += 32) {
__m512 x0 = _mm512_setzero_ps();
__m512 x1 = _mm512_setzero_ps();
for (int j = 0; j < k; j++) {
if (expert_ids[i * k + j] < config_.num_gpu_experts || expert_ids[i * k + j] >= config_.expert_num) {
continue;
}
__m512 weight = _mm512_set1_ps(weights[i * k + j]);
__m512 down_output0, down_output1;
avx512_32xbf16_to_32xfp32((__m512i*)(m_local_down_output_ptr_[expert_ids[i * k + j]] +
m_local_pos_[i][j] * config_.hidden_size + e),
&down_output0, &down_output1);
x0 = _mm512_fmadd_ps(down_output0, weight, x0);
x1 = _mm512_fmadd_ps(down_output1, weight, x1);
}
auto f32out = (__m512*)((float*)output + i * config_.hidden_size + e);
f32out[0] = x0;
f32out[1] = x1;
}
},
nullptr);
// NaN Check: Step 9 - Final output (after weighted merge)
if (is_nan_check_enabled()) {
char label[128];
snprintf(label, sizeof(label), "[FWD L%d] Step9 final_output", config_.layer_idx);
check_fp32_buffer_for_nan((const float*)output, qlen * config_.hidden_size, label);
}
// ★ Inference mode cleanup ★
// LoRA working buffers are pooled (kept) to avoid frequent alloc/free overhead.
if (!save_for_backward) {
free_lora_working_buffers();
}
}
/**
* @brief Backward pass for SFT.
*
* Computes gradients for LoRA weights using cached intermediate values.
* When SkipLoRA template parameter is true, skips all LoRA computation
* and only computes base weight contribution to grad_input.
*
* @param grad_output Gradient of loss w.r.t. output [qlen, hidden_size] (BF16)
* @param grad_input Gradient of loss w.r.t. input [qlen, hidden_size] (BF16, output)
* @param grad_gate_lora_a Gradient for gate LoRA A [expert_num, lora_rank, hidden_size] (BF16, ignored if
* SkipLoRA=true)
* @param grad_gate_lora_b Gradient for gate LoRA B [expert_num, intermediate_size, lora_rank] (ignored if
* SkipLoRA=true)
* @param grad_up_lora_a Gradient for up LoRA A (BF16, ignored if SkipLoRA=true)
* @param grad_up_lora_b Gradient for up LoRA B (BF16, ignored if SkipLoRA=true)
* @param grad_down_lora_a Gradient for down LoRA A (BF16, ignored if SkipLoRA=true)
* @param grad_down_lora_b Gradient for down LoRA B (BF16, ignored if SkipLoRA=true)
* @param grad_weights Gradient for routing weights [qlen, k] (FP32, output)
*/
void backward(const void* grad_output, void* grad_input, void* grad_gate_lora_a, void* grad_gate_lora_b,
void* grad_up_lora_a, void* grad_up_lora_b, void* grad_down_lora_a, void* grad_down_lora_b,
void* grad_weights, int full_intermediate_size = 0, float* fp32_grad_down_lora_b = nullptr,
float* fp32_grad_gate_lora_a = nullptr, float* fp32_grad_up_lora_a = nullptr) {
// If full_intermediate_size not provided, use local (non-TP mode)
if (full_intermediate_size == 0) full_intermediate_size = config_.intermediate_size;
SFT_POOL_LOG("bwd_enter", config_.layer_idx, tp_part_idx, 0, cache_stack_top_, forward_pool_bytes_,
cache_pool_bytes_, backward_pool_bytes_, 0, "backward entry");
// Pop cache from stack
ForwardCache cache = pop_cache();
if (!cache.valid) {
throw std::runtime_error("No valid forward cache for backward");
}
int qlen = cache.qlen_cache;
int k = cache.k_cache;
int activated_expert = cache.activated_expert_cache;
constexpr int kSmallBwdDirectQlen = 0;
constexpr int kSmallBwdDirectMaxTasks = 16;
// NaN Check: grad_output input
if (is_nan_check_enabled()) {
char label[128];
snprintf(label, sizeof(label), "[BWD L%d] Input grad_output", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_output, qlen * config_.hidden_size, label);
}
// NaN Check: Forward Cache (read from cache)
if (is_nan_check_enabled()) {
auto check_cache_bf16 = [&](const char* name, const ggml_bf16_t* ptr, size_t elems) {
if (ptr == nullptr) {
printf(ANSI_BG_RED "[CACHE READ L%d] %s: NULL pointer!" ANSI_COLOR_RESET "\n", config_.layer_idx, name);
return;
}
if (elems == 0) {
printf(ANSI_BG_BLUE "[CACHE READ L%d] %s: empty (elems=0)" ANSI_COLOR_RESET "\n", config_.layer_idx, name);
return;
}
double sum_sq = 0.0, sum_abs = 0.0, max_abs = 0.0;
int nan_count = 0, inf_count = 0;
for (size_t i = 0; i < elems; i++) {
float v = GGML_BF16_TO_FP32(ptr[i]);
// Use v != v for robust NaN detection
if (v != v) nan_count++;
if (!is_nan_value(v) && is_inf_value(v)) inf_count++;
double dv = static_cast<double>(v);
double a = std::fabs(dv);
sum_sq += dv * dv;
sum_abs += a;
if (a > max_abs || a != a) max_abs = a;
}
double norm = std::sqrt(sum_sq);
double abs_mean = sum_abs / static_cast<double>(elems);
bool has_nan_inf = (nan_count > 0 || inf_count > 0);
// Also check if computed values are NaN/Inf
bool computed_nan = (norm != norm) || (abs_mean != abs_mean) || (max_abs != max_abs);
bool has_large = (!is_nan_value(max_abs) && !is_inf_value(max_abs) && max_abs > NAN_CHECK_LARGE_THRESHOLD);
const char* bg = (has_nan_inf || computed_nan) ? ANSI_BG_RED : ANSI_BG_BLUE;
printf("%s[CACHE READ L%d] %s: norm=%.6e abs_mean=%.6e abs_max=%.6e nan=%d inf=%d (total=%zu)" ANSI_COLOR_RESET
"\n",
bg, config_.layer_idx, name, norm, abs_mean, max_abs, nan_count, inf_count, elems);
};
// Compute total tokens
size_t total_tokens = 0;
for (int i = 0; i < activated_expert; i++) {
total_tokens += cache.m_local_num_cache[cache.m_expert_id_map_cache[i]];
}
check_cache_bf16("input_cache", cache.input_cache, qlen * config_.hidden_size);
check_cache_bf16("gate_output_cache", cache.gate_output_cache, total_tokens * config_.intermediate_size);
check_cache_bf16("up_output_cache", cache.up_output_cache, total_tokens * config_.intermediate_size);
check_cache_bf16("intermediate_cache", cache.intermediate_cache, total_tokens * config_.intermediate_size);
check_cache_bf16("down_output_cache", cache.down_output_cache, total_tokens * config_.hidden_size);
}
// ★ Allocate backward-phase buffers ★
alloc_backward_buffers();
// ★ share_backward_bb: check if async repack already prepared this layer ★
if (config_.share_backward_bb) {
auto& shared = SFTSharedPools::instance();
shared.ensure_numa_count(tp_part_idx + 1);
if (shared.pools[tp_part_idx].bwd_bb_owner_layer != config_.layer_idx) {
// Pool was overwritten by another layer or not yet repacked — sync fallback
prepare_backward_bb_for_async();
}
}
// auto print_lora_stats = [&](const char* name, const ggml_bf16_t* ptr, size_t elems) {
// if (ptr == nullptr) {
// printf("KT MoE param stats (layer %d, %s): null\n", config_.layer_idx, name);
// return;
// }
// Bf16Stats stats = compute_bf16_stats(ptr, elems);
// printf("cpp KT MoE param stats (layer %d, %s): abs_mean=%.6e abs_max=%.6e norm=%.6e\n", config_.layer_idx,
// name,
// stats.abs_mean, stats.abs_max, stats.norm);
// };
// size_t gate_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
// size_t gate_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
// size_t up_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
// size_t up_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
// size_t down_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.intermediate_size;
// size_t down_b_elems = static_cast<size_t>(config_.expert_num) * config_.hidden_size * lora_rank_;
// print_lora_stats("gate_lora_a", gate_lora_a_, gate_a_elems);
// print_lora_stats("gate_lora_b", gate_lora_b_, gate_b_elems);
// print_lora_stats("up_lora_a", up_lora_a_, up_a_elems);
// print_lora_stats("up_lora_b", up_lora_b_, up_b_elems);
// print_lora_stats("down_lora_a", down_lora_a_, down_a_elems);
// print_lora_stats("down_lora_b", down_lora_b_, down_b_elems);
// Restore routing information
m_local_num_ = cache.m_local_num_cache;
m_local_pos_ = cache.m_local_pos_cache;
m_expert_id_map_ = cache.m_expert_id_map_cache;
// Recompute pointer offsets
size_t offset = 0;
for (int i = 0; i < config_.expert_num; i++) {
m_local_input_ptr_[i] = m_local_input_ + offset * config_.hidden_size;
m_local_gate_output_ptr_[i] = m_local_gate_output_ + offset * config_.intermediate_size;
m_local_up_output_ptr_[i] = m_local_up_output_ + offset * config_.intermediate_size;
m_local_down_output_ptr_[i] = m_local_down_output_ + offset * config_.hidden_size;
offset += m_local_num_[i];
}
// Restore input data from cache into m_local_input_ (shared_mem_buffer may have been
// overwritten by subsequent layers' forward passes). This is needed for gate/up LoRA
// gradient computation which reads from m_local_input_ptr_.
auto pool_local = config_.pool->get_subpool(tp_part_idx);
auto restore_input = [&](int i) {
for (int j = 0; j < k; j++) {
int eid = cache.expert_ids_cache[i * k + j];
if (eid < config_.num_gpu_experts || eid >= config_.expert_num) {
continue;
}
if (m_local_num_[eid] == 0) continue;
int pos = cache.m_local_pos_cache[i][j];
memcpy(m_local_input_ptr_[eid] + pos * config_.hidden_size,
(const ggml_bf16_t*)cache.input_cache + i * config_.hidden_size,
sizeof(ggml_bf16_t) * config_.hidden_size);
}
};
if (qlen <= kSmallBwdDirectQlen && qlen <= kSmallBwdDirectMaxTasks) {
for (int i = 0; i < qlen; i++) {
restore_input(i);
}
} else {
pool_local->do_work_stealing_job(qlen, nullptr, restore_input, nullptr);
}
// Step 1: Down projection backward
if constexpr (supports_standard_mat_mul_v<T>) {
backward_down_amx(cache, grad_output, grad_down_lora_a, grad_down_lora_b, full_intermediate_size,
fp32_grad_down_lora_b);
} else {
// backward_down(cache, grad_output, grad_down_lora_a, grad_down_lora_b);
}
// // Compute total tokens for debug
// size_t total_tokens = 0;
// for (int i = 0; i < activated_expert; i++) {
// total_tokens += m_local_num_[m_expert_id_map_[i]];
// }
// printf("[BACKWARD DEBUG] qlen=%d, k=%d, activated_expert=%d, total_tokens=%zu\n", qlen, k, activated_expert,
// total_tokens);
// printf("[BACKWARD DEBUG] grad_output norm: %f\n",
// compute_bf16_norm((const ggml_bf16_t*)grad_output, qlen * config_.hidden_size));
// NaN Check: Step 1 - After backward_down
if (is_nan_check_enabled()) {
char label[128];
// Check grad_intermediate
size_t grad_inter_size = 0;
for (int i = 0; i < activated_expert; i++) {
grad_inter_size += m_local_num_[m_expert_id_map_[i]];
}
grad_inter_size *= config_.intermediate_size;
snprintf(label, sizeof(label), "[BWD L%d] Step1 grad_intermediate", config_.layer_idx);
check_bf16_buffer_for_nan(grad_intermediate_, grad_inter_size, label);
// Check grad_down_lora_a
if (grad_down_lora_a != nullptr) {
size_t down_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.intermediate_size;
snprintf(label, sizeof(label), "[BWD L%d] Step1 grad_down_lora_a", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_down_lora_a, down_a_elems, label);
}
// Check grad_down_lora_b
if (grad_down_lora_b != nullptr) {
size_t down_b_elems = static_cast<size_t>(config_.expert_num) * config_.hidden_size * lora_rank_;
snprintf(label, sizeof(label), "[BWD L%d] Step1 grad_down_lora_b", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_down_lora_b, down_b_elems, label);
}
}
// // DEBUG: Check m_local_input_ptr_ after backward_down (should be populated from cache)
// {
// bool has_nan = false, has_large = false;
// float max_val = 0.0f;
// int activated_expert_dbg = cache.activated_expert_cache;
// for (int task_id = 0; task_id < activated_expert_dbg && !has_nan; task_id++) {
// int expert_idx = m_expert_id_map_[task_id];
// int m = m_local_num_[expert_idx];
// if (m == 0) continue;
// ggml_bf16_t* input_ptr = m_local_input_ptr_[expert_idx];
// for (int i = 0; i < m * config_.hidden_size && !has_nan; i++) {
// float v = GGML_BF16_TO_FP32(input_ptr[i]);
// if (std::isnan(v) || std::isinf(v)) has_nan = true;
// float av = std::abs(v);
// if (av > max_val) max_val = av;
// if (av > 1e10f) has_large = true;
// }
// }
// if (has_nan || has_large) {
// printf("[NaN DEBUG L%d] m_local_input AFTER backward_down: has_nan=%d has_large=%d max=%.6e\n",
// config_.layer_idx, has_nan, has_large, max_val);
// }
// }
// // DEBUG: Check for NaN after backward_down
// {
// size_t grad_inter_size = qlen * k * config_.intermediate_size;
// bool has_nan = false;
// for (size_t i = 0; i < grad_inter_size && !has_nan; i++) {
// float val = GGML_BF16_TO_FP32(grad_intermediate_[i]);
// if (std::isnan(val) || std::isinf(val)) has_nan = true;
// }
// if (has_nan) {
// printf("[NaN DEBUG L%d] NaN detected in grad_intermediate after backward_down!\n", config_.layer_idx);
// }
// }
backward_activation(cache);
// NaN Check: Step 2 - After backward_activation
if (is_nan_check_enabled()) {
char label[128];
size_t grad_size = 0;
for (int i = 0; i < activated_expert; i++) {
grad_size += m_local_num_[m_expert_id_map_[i]];
}
grad_size *= config_.intermediate_size;
snprintf(label, sizeof(label), "[BWD L%d] Step2 grad_gate_output", config_.layer_idx);
check_bf16_buffer_for_nan(grad_gate_output_, grad_size, label);
snprintf(label, sizeof(label), "[BWD L%d] Step2 grad_up_output", config_.layer_idx);
check_bf16_buffer_for_nan(grad_up_output_, grad_size, label);
}
// // DEBUG: Check m_local_input_ptr_ BEFORE backward_gate_up (after backward_activation)
// {
// bool has_nan = false, has_large = false;
// float max_val = 0.0f;
// int activated_expert_dbg = cache.activated_expert_cache;
// for (int task_id = 0; task_id < activated_expert_dbg && !has_nan; task_id++) {
// int expert_idx = m_expert_id_map_[task_id];
// int m = m_local_num_[expert_idx];
// if (m == 0) continue;
// ggml_bf16_t* input_ptr = m_local_input_ptr_[expert_idx];
// for (int i = 0; i < m * config_.hidden_size && !has_nan; i++) {
// float v = GGML_BF16_TO_FP32(input_ptr[i]);
// if (std::isnan(v) || std::isinf(v)) has_nan = true;
// float av = std::abs(v);
// if (av > max_val) max_val = av;
// if (av > 1e10f) has_large = true;
// }
// }
// if (has_nan || has_large) {
// printf("[NaN DEBUG L%d] m_local_input BEFORE backward_gate_up: has_nan=%d has_large=%d max=%.6e\n",
// config_.layer_idx, has_nan, has_large, max_val);
// }
// }
if constexpr (supports_standard_mat_mul_v<T>) {
backward_gate_up_amx(cache, grad_input, grad_gate_lora_a, grad_gate_lora_b, grad_up_lora_a, grad_up_lora_b,
full_intermediate_size, fp32_grad_gate_lora_a, fp32_grad_up_lora_a);
} else {
// backward_gate_up(cache, grad_input, grad_gate_lora_a, grad_gate_lora_b, grad_up_lora_a, grad_up_lora_b);
}
// NaN Check: Step 3 - After backward_gate_up
if (is_nan_check_enabled()) {
char label[128];
// Check grad_input
snprintf(label, sizeof(label), "[BWD L%d] Step3 grad_input", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_input, qlen * config_.hidden_size, label);
// Check grad_gate_lora_a
if (grad_gate_lora_a != nullptr) {
size_t gate_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
snprintf(label, sizeof(label), "[BWD L%d] Step3 grad_gate_lora_a", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_gate_lora_a, gate_a_elems, label);
}
// Check grad_gate_lora_b
if (grad_gate_lora_b != nullptr) {
size_t gate_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
snprintf(label, sizeof(label), "[BWD L%d] Step3 grad_gate_lora_b", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_gate_lora_b, gate_b_elems, label);
}
// Check grad_up_lora_a
if (grad_up_lora_a != nullptr) {
size_t up_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
snprintf(label, sizeof(label), "[BWD L%d] Step3 grad_up_lora_a", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_up_lora_a, up_a_elems, label);
}
// Check grad_up_lora_b
if (grad_up_lora_b != nullptr) {
size_t up_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
snprintf(label, sizeof(label), "[BWD L%d] Step3 grad_up_lora_b", config_.layer_idx);
check_bf16_buffer_for_nan((const ggml_bf16_t*)grad_up_lora_b, up_b_elems, label);
}
}
// Step 4: Compute grad_weights (gradient for routing weights)
// grad_weights[token_idx, expert_pos] = dot(grad_output[token_idx], down_output[token, expert])
if (grad_weights != nullptr) {
auto pool = config_.pool->get_subpool(tp_part_idx);
float* grad_w = (float*)grad_weights;
const ggml_bf16_t* grad_out = (const ggml_bf16_t*)grad_output;
// Compute offset mapping for down_output_cache (same layout as other caches)
std::vector<size_t> expert_cache_offset(config_.expert_num, 0);
size_t offset = 0;
for (int i = 0; i < activated_expert; i++) {
int expert_idx = cache.m_expert_id_map_cache[i];
expert_cache_offset[expert_idx] = offset;
offset += cache.m_local_num_cache[expert_idx];
}
// Compute grad_weights for each token-expert pair
auto compute_grad_weight = [&](int token_idx) {
for (int j = 0; j < k; j++) {
int64_t expert_idx = cache.expert_ids_cache[token_idx * k + j];
if (expert_idx < config_.num_gpu_experts || expert_idx >= config_.expert_num) {
continue; // Skip GPU experts or invalid experts
}
int local_pos = cache.m_local_pos_cache[token_idx][j];
size_t down_offset = expert_cache_offset[expert_idx] + local_pos;
// dot(grad_output[token_idx], down_output_cache[down_offset])
const ggml_bf16_t* grad_out_ptr = grad_out + token_idx * config_.hidden_size;
const ggml_bf16_t* down_out_ptr = cache.down_output_cache + down_offset * config_.hidden_size;
__m512 acc0 = _mm512_setzero_ps();
__m512 acc1 = _mm512_setzero_ps();
for (int h = 0; h + 32 <= config_.hidden_size; h += 32) {
__m512 g0, g1, d0, d1;
avx512_32xbf16_to_32xfp32((__m512i*)(grad_out_ptr + h), &g0, &g1);
avx512_32xbf16_to_32xfp32((__m512i*)(down_out_ptr + h), &d0, &d1);
acc0 = _mm512_fmadd_ps(g0, d0, acc0);
acc1 = _mm512_fmadd_ps(g1, d1, acc1);
}
grad_w[token_idx * k + j] = _mm512_reduce_add_ps(acc0) + _mm512_reduce_add_ps(acc1);
}
};
if (qlen <= kSmallBwdDirectQlen && qlen <= kSmallBwdDirectMaxTasks) {
for (int token_idx = 0; token_idx < qlen; token_idx++) {
compute_grad_weight(token_idx);
}
} else {
pool->do_work_stealing_job(qlen, nullptr, compute_grad_weight, nullptr);
}
}
// NaN Check: Step 4 - After grad_weights computation
if (is_nan_check_enabled() && grad_weights != nullptr) {
char label[128];
snprintf(label, sizeof(label), "[BWD L%d] Step4 grad_weights", config_.layer_idx);
check_fp32_buffer_for_nan((const float*)grad_weights, qlen * k, label);
}
// NaN Check & Norm: Final output gradients summary
if (is_nan_check_enabled()) {
auto print_grad_stats = [&](const char* name, const ggml_bf16_t* ptr, size_t elems) {
if (ptr == nullptr) {
printf(ANSI_COLOR_RED "[BWD L%d OUTPUT] %s: NULL pointer!" ANSI_COLOR_RESET "\n", config_.layer_idx, name);
return;
}
if (elems == 0) {
printf(ANSI_COLOR_YELLOW "[BWD L%d OUTPUT] %s: empty (elems=0)" ANSI_COLOR_RESET "\n", config_.layer_idx,
name);
return;
}
// Compute stats and NaN check in one pass - DO NOT skip NaN/Inf
double sum_sq = 0.0, sum_abs = 0.0, max_abs = 0.0;
int nan_count = 0, inf_count = 0;
for (size_t i = 0; i < elems; i++) {
float v = GGML_BF16_TO_FP32(ptr[i]);
// Use v != v for robust NaN detection
if (v != v) {
nan_count++;
}
if (!(v != v) && is_inf_value(v)) {
inf_count++;
}
double dv = static_cast<double>(v);
double a = std::fabs(dv);
sum_sq += dv * dv;
sum_abs += a;
if (a > max_abs || a != a) max_abs = a;
}
double norm = std::sqrt(sum_sq);
double abs_mean = sum_abs / static_cast<double>(elems);
bool has_nan_inf = (nan_count > 0 || inf_count > 0);
// Also check if computed values are NaN
bool computed_nan = (norm != norm) || (abs_mean != abs_mean);
bool has_large = (!(max_abs != max_abs) && !is_inf_value(max_abs) && max_abs > NAN_CHECK_LARGE_THRESHOLD);
const char* color = (has_nan_inf || computed_nan) ? ANSI_COLOR_RED : (has_large ? ANSI_COLOR_YELLOW : "");
const char* reset = (has_nan_inf || computed_nan || has_large) ? ANSI_COLOR_RESET : "";
printf("%s[BWD L%d OUTPUT] %s: norm=%.6e abs_mean=%.6e abs_max=%.6e nan=%d inf=%d (total=%zu)%s\n", color,
config_.layer_idx, name, norm, abs_mean, max_abs, nan_count, inf_count, elems, reset);
};
auto print_grad_stats_fp32 = [&](const char* name, const float* ptr, size_t elems) {
if (ptr == nullptr) {
printf(ANSI_COLOR_RED "[BWD L%d OUTPUT] %s: NULL pointer!" ANSI_COLOR_RESET "\n", config_.layer_idx, name);
return;
}
if (elems == 0) {
printf(ANSI_COLOR_YELLOW "[BWD L%d OUTPUT] %s: empty (elems=0)" ANSI_COLOR_RESET "\n", config_.layer_idx,
name);
return;
}
// DO NOT skip NaN/Inf - include them in computation
double sum_sq = 0.0, sum_abs = 0.0, max_abs = 0.0;
int nan_count = 0, inf_count = 0;
for (size_t i = 0; i < elems; i++) {
float fv = ptr[i];
// Use fv != fv for robust NaN detection
if (fv != fv) {
nan_count++;
}
if (!(fv != fv) && is_inf_value(fv)) {
inf_count++;
}
double v = static_cast<double>(fv);
double a = std::fabs(v);
sum_sq += v * v;
sum_abs += a;
if (a > max_abs || a != a) max_abs = a;
}
double norm = std::sqrt(sum_sq);
double abs_mean = sum_abs / static_cast<double>(elems);
bool has_nan_inf = (nan_count > 0 || inf_count > 0);
// Also check if computed values are NaN
bool computed_nan = (norm != norm) || (abs_mean != abs_mean);
bool has_large = (!(max_abs != max_abs) && !is_inf_value(max_abs) && max_abs > NAN_CHECK_LARGE_THRESHOLD);
const char* color = (has_nan_inf || computed_nan) ? ANSI_COLOR_RED : (has_large ? ANSI_COLOR_YELLOW : "");
const char* reset = (has_nan_inf || computed_nan || has_large) ? ANSI_COLOR_RESET : "";
printf("%s[BWD L%d OUTPUT] %s: norm=%.6e abs_mean=%.6e abs_max=%.6e nan=%d inf=%d (total=%zu)%s\n", color,
config_.layer_idx, name, norm, abs_mean, max_abs, nan_count, inf_count, elems, reset);
};
// grad_input
print_grad_stats("grad_input", (const ggml_bf16_t*)grad_input, qlen * config_.hidden_size);
// LoRA gradient sizes
size_t gate_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
size_t gate_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
size_t up_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
size_t up_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
size_t down_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.intermediate_size;
size_t down_b_elems = static_cast<size_t>(config_.expert_num) * config_.hidden_size * lora_rank_;
// Gate LoRA gradients
print_grad_stats("grad_gate_lora_a", (const ggml_bf16_t*)grad_gate_lora_a, gate_a_elems);
print_grad_stats("grad_gate_lora_b", (const ggml_bf16_t*)grad_gate_lora_b, gate_b_elems);
// Up LoRA gradients
print_grad_stats("grad_up_lora_a", (const ggml_bf16_t*)grad_up_lora_a, up_a_elems);
print_grad_stats("grad_up_lora_b", (const ggml_bf16_t*)grad_up_lora_b, up_b_elems);
// Down LoRA gradients
print_grad_stats("grad_down_lora_a", (const ggml_bf16_t*)grad_down_lora_a, down_a_elems);
print_grad_stats("grad_down_lora_b", (const ggml_bf16_t*)grad_down_lora_b, down_b_elems);
// Routing weights gradient
print_grad_stats_fp32("grad_weights", (const float*)grad_weights, qlen * k);
}
// ★ Cache pool is NOT freed here — kept for reuse across steps.
// alloc_or_resize_cache_pool() is grow-only, so same-seqlen steps
// reuse the existing allocation without malloc/free overhead.
// Previously: free_seqlen_buffers() was called here, costing ~3.6ms per TP.
// Mark cache as invalid
cache.valid = false;
}
/**
* @brief Get qlen from the top of the forward cache stack.
*
* Bug #22 fix: This is needed by TP_MOE_SFT::backward() to allocate
* separate grad_input buffers for each NUMA node before calling backward.
*/
int get_cache_qlen() const {
if (cache_stack_top_ > 0 && cache_stack_[cache_stack_top_ - 1].valid) {
return cache_stack_[cache_stack_top_ - 1].qlen_cache;
}
return 0; // No valid cache
}
int get_cache_activated_expert_count() const {
return (cache_stack_top_ > 0 && cache_stack_[cache_stack_top_ - 1].valid)
? cache_stack_[cache_stack_top_ - 1].activated_expert_cache
: 0;
}
const int* get_cache_expert_id_map() const {
return (cache_stack_top_ > 0 && cache_stack_[cache_stack_top_ - 1].valid)
? cache_stack_[cache_stack_top_ - 1].m_expert_id_map_cache.data()
: nullptr;
}
/**
* @brief Get expert token distribution from last backward for load balancing analysis.
* @return Vector of token counts per activated expert
*/
const std::vector<int>& get_expert_token_distribution() const { return last_backward_expert_tokens_; }
/**
* @brief Update LoRA weight pointers (call when Python tensors are reallocated).
*/
void update_lora_weights(void* gate_lora_a, void* gate_lora_b, void* up_lora_a, void* up_lora_b, void* down_lora_a,
void* down_lora_b) {
gate_lora_a_ = (ggml_bf16_t*)gate_lora_a;
gate_lora_b_ = (ggml_bf16_t*)gate_lora_b;
up_lora_a_ = (ggml_bf16_t*)up_lora_a;
up_lora_b_ = (ggml_bf16_t*)up_lora_b;
down_lora_a_ = (ggml_bf16_t*)down_lora_a;
down_lora_b_ = (ggml_bf16_t*)down_lora_b;
// NaN Check and Norm printing for LoRA weights
if (is_nan_check_enabled()) {
auto print_lora_stats = [&](const char* name, const ggml_bf16_t* ptr, size_t elems) {
if (ptr == nullptr) {
printf("[LoRA L%d] %s: null\n", config_.layer_idx, name);
return;
}
if (elems == 0) {
printf(ANSI_COLOR_YELLOW "[LoRA L%d] %s: empty (elems=0)" ANSI_COLOR_RESET "\n", config_.layer_idx, name);
return;
}
// DO NOT skip NaN/Inf - include them in computation
double sum_sq = 0.0, sum_abs = 0.0, max_abs = 0.0;
int nan_count = 0, inf_count = 0;
for (size_t i = 0; i < elems; i++) {
float v = GGML_BF16_TO_FP32(ptr[i]);
// Use v != v for robust NaN detection
if (v != v) {
nan_count++;
}
if (!(v != v) && is_inf_value(v)) {
inf_count++;
}
double dv = static_cast<double>(v);
double a = std::fabs(dv);
sum_sq += dv * dv;
sum_abs += a;
if (a > max_abs || a != a) max_abs = a;
}
double norm = std::sqrt(sum_sq);
double abs_mean = sum_abs / static_cast<double>(elems);
bool has_nan_inf = (nan_count > 0 || inf_count > 0);
// Also check if computed values are NaN
bool computed_nan = (norm != norm) || (abs_mean != abs_mean);
bool has_large = (!(max_abs != max_abs) && !is_inf_value(max_abs) && max_abs > NAN_CHECK_LARGE_THRESHOLD);
const char* color = (has_nan_inf || computed_nan) ? ANSI_COLOR_RED : (has_large ? ANSI_COLOR_YELLOW : "");
const char* reset = (has_nan_inf || computed_nan || has_large) ? ANSI_COLOR_RESET : "";
printf("%s[LoRA L%d] %s: norm=%.6e abs_mean=%.6e abs_max=%.6e nan=%d inf=%d (total=%zu)%s\n", color,
config_.layer_idx, name, norm, abs_mean, max_abs, nan_count, inf_count, elems, reset);
};
size_t gate_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
size_t gate_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
size_t up_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
size_t up_b_elems = static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_;
size_t down_a_elems = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.intermediate_size;
size_t down_b_elems = static_cast<size_t>(config_.expert_num) * config_.hidden_size * lora_rank_;
print_lora_stats("gate_lora_a", gate_lora_a_, gate_a_elems);
print_lora_stats("gate_lora_b", gate_lora_b_, gate_b_elems);
print_lora_stats("up_lora_a", up_lora_a_, up_a_elems);
print_lora_stats("up_lora_b", up_lora_b_, up_b_elems);
print_lora_stats("down_lora_a", down_lora_a_, down_a_elems);
print_lora_stats("down_lora_b", down_lora_b_, down_b_elems);
}
// Mark weights as needing re-conversion (lazy preparation in forward/backward)
lora_weights_prepared_ = false;
lora_backward_weights_prepared_ = false;
lora_b_transposed_ = false; // Will be prepared lazily in forward_sft
lora_a_bb_prepared_ = false; // Will be prepared lazily in backward_gate_up_amx
}
/**
* @brief Allocate buffers for pre-transposed LoRA B weights.
*
* Pre-transposed weights enable contiguous memory access for 16 outputs at a time,
* providing ~5x speedup for small LoRA ranks (8-16).
*/
void alloc_transposed_lora_weights() {
if (lora_rank_ <= 0) return;
if (gate_lora_b_transposed_ != nullptr) return; // Already allocated
size_t gate_up_b_size = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.intermediate_size;
size_t down_b_size = static_cast<size_t>(config_.expert_num) * lora_rank_ * config_.hidden_size;
// Allocate all transposed buffers at once
gate_lora_b_transposed_ = (ggml_bf16_t*)aligned_alloc(64, gate_up_b_size * sizeof(ggml_bf16_t));
up_lora_b_transposed_ = (ggml_bf16_t*)aligned_alloc(64, gate_up_b_size * sizeof(ggml_bf16_t));
down_lora_b_transposed_ = (ggml_bf16_t*)aligned_alloc(64, down_b_size * sizeof(ggml_bf16_t));
}
/**
* @brief Free pre-transposed LoRA weight buffers.
*/
void free_transposed_lora_weights() {
if (gate_lora_b_transposed_) {
free(gate_lora_b_transposed_);
gate_lora_b_transposed_ = nullptr;
}
if (up_lora_b_transposed_) {
free(up_lora_b_transposed_);
up_lora_b_transposed_ = nullptr;
}
if (down_lora_b_transposed_) {
free(down_lora_b_transposed_);
down_lora_b_transposed_ = nullptr;
}
}
/**
* @brief Transpose LoRA B weights for optimized AVX512 fused_add.
*
* Transposes weight from [output_dim][rank] to [rank][output_dim] for each expert.
*/
void transpose_lora_b_weights() {
if (lora_rank_ <= 0) return;
if (gate_lora_b_transposed_ == nullptr) return; // Not allocated yet
auto pool = config_.pool->get_subpool(tp_part_idx);
// Parallel transpose for all experts and all LoRA B matrices
pool->do_work_stealing_job(
config_.expert_num * 3, nullptr,
[this](int task_id) {
int expert_idx = task_id / 3;
int lora_type = task_id % 3;
switch (lora_type) {
case 0: // gate_lora_b: [intermediate_size][rank] -> [rank][intermediate_size]
if (gate_lora_b_ && gate_lora_b_transposed_) {
size_t src_offset = static_cast<size_t>(expert_idx) * config_.intermediate_size * lora_rank_;
size_t dst_offset = static_cast<size_t>(expert_idx) * lora_rank_ * config_.intermediate_size;
avx::transpose_lora_weight(gate_lora_b_ + src_offset, gate_lora_b_transposed_ + dst_offset,
config_.intermediate_size, lora_rank_);
}
break;
case 1: // up_lora_b: [intermediate_size][rank] -> [rank][intermediate_size]
if (up_lora_b_ && up_lora_b_transposed_) {
size_t src_offset = static_cast<size_t>(expert_idx) * config_.intermediate_size * lora_rank_;
size_t dst_offset = static_cast<size_t>(expert_idx) * lora_rank_ * config_.intermediate_size;
avx::transpose_lora_weight(up_lora_b_ + src_offset, up_lora_b_transposed_ + dst_offset,
config_.intermediate_size, lora_rank_);
}
break;
case 2: // down_lora_b: [hidden_size][rank] -> [rank][hidden_size]
if (down_lora_b_ && down_lora_b_transposed_) {
size_t src_offset = static_cast<size_t>(expert_idx) * config_.hidden_size * lora_rank_;
size_t dst_offset = static_cast<size_t>(expert_idx) * lora_rank_ * config_.hidden_size;
avx::transpose_lora_weight(down_lora_b_ + src_offset, down_lora_b_transposed_ + dst_offset,
config_.hidden_size, lora_rank_);
}
break;
}
},
nullptr);
}
/**
* @brief Prepare LoRA weights for AMX GEMM.
*
* Converts BF16 LoRA weights from Python tensors to AMX BufferB format.
* This includes padding to K_STEP multiples for AMX alignment.
* Must be called before forward_sft() if lora_weights_prepared_ is false.
*/
void prepare_lora_weights() {
// Only prepare weights for kernels that support standard mat_mul
if constexpr (!supports_standard_mat_mul_v<T>) {
return; // KGroup kernels use for-loop implementation
}
if (lora_weights_prepared_) {
return;
}
if (gate_lora_a_ == nullptr) {
return; // No LoRA weights to prepare
}
auto pool = config_.pool->get_subpool(tp_part_idx);
// Parallel conversion of forward LoRA weights to BufferB format
// 6 matrices per expert: gate/up/down (A, B) - only for forward pass
pool->do_work_stealing_job(
config_.expert_num * 6, nullptr,
[this](int task_id) {
int expert_idx = task_id / 6;
int lora_type = task_id % 6;
switch (lora_type) {
case 0: // gate_lora_a [lora_rank, hidden_size] -> [padded_lora_rank, hidden_size]
convert_lora_a_to_buffer_b(gate_lora_a_, gate_lora_a_bb_[expert_idx], expert_idx, lora_rank_,
config_.hidden_size, padded_lora_rank_, config_.hidden_size);
break;
case 1: // up_lora_a [lora_rank, hidden_size]
convert_lora_a_to_buffer_b(up_lora_a_, up_lora_a_bb_[expert_idx], expert_idx, lora_rank_,
config_.hidden_size, padded_lora_rank_, config_.hidden_size);
break;
case 2: // gate_lora_b [intermediate_size, lora_rank] -> [intermediate_size, padded_lora_rank]
convert_lora_b_to_buffer_b(gate_lora_b_, gate_lora_b_bb_[expert_idx], expert_idx,
config_.intermediate_size, lora_rank_, config_.intermediate_size,
padded_lora_rank_);
break;
case 3: // up_lora_b [intermediate_size, lora_rank]
convert_lora_b_to_buffer_b(up_lora_b_, up_lora_b_bb_[expert_idx], expert_idx, config_.intermediate_size,
lora_rank_, config_.intermediate_size, padded_lora_rank_);
break;
case 4: // down_lora_a [lora_rank, intermediate_size] -> [padded_lora_rank, intermediate_size]
convert_lora_a_to_buffer_b(down_lora_a_, down_lora_a_bb_[expert_idx], expert_idx, lora_rank_,
config_.intermediate_size, padded_lora_rank_, config_.intermediate_size);
break;
case 5: // down_lora_b [hidden_size, lora_rank] -> [hidden_size, padded_lora_rank]
convert_lora_b_to_buffer_b(down_lora_b_, down_lora_b_bb_[expert_idx], expert_idx, config_.hidden_size,
lora_rank_, config_.hidden_size, padded_lora_rank_);
break;
}
},
nullptr);
lora_weights_prepared_ = true;
}
/**
* @brief Prepare transposed LoRA weights needed by the backward pass.
*
* Gate/up backward now uses direct AVX kernels on the raw/transposed BF16
* weights, so only the down-path AMX BufferB weights still need lazy prep.
*/
void prepare_lora_backward_weights() {
if constexpr (!supports_standard_mat_mul_v<T>) {
return;
}
if (lora_backward_weights_prepared_) {
return;
}
if (gate_lora_a_ == nullptr) {
return;
}
auto pool = config_.pool->get_subpool(tp_part_idx);
// Only down-path LoRA backward still consumes BufferB weights.
pool->do_work_stealing_job(
config_.expert_num * 2, nullptr,
[this](int task_id) {
int expert_idx = task_id / 2;
int lora_type = task_id % 2;
switch (lora_type) {
case 0: // down_lora_a^T [rank, inter_size] -> [inter_size, padded_rank]
convert_lora_a_transposed_to_buffer_b(down_lora_a_, down_lora_a_t_bb_[expert_idx], expert_idx, lora_rank_,
config_.intermediate_size, config_.intermediate_size,
padded_lora_rank_);
break;
case 1: // down_lora_b^T [hidden_size, rank] -> [padded_rank, hidden_size]
convert_lora_b_transposed_to_buffer_b(down_lora_b_, down_lora_b_t_bb_[expert_idx], expert_idx,
config_.hidden_size, lora_rank_, padded_lora_rank_,
config_.hidden_size);
break;
}
},
nullptr);
lora_backward_weights_prepared_ = true;
}
// Debug getter for LoRA pointer verification
void* get_gate_lora_a() const { return (void*)gate_lora_a_; }
/**
* @brief Prepare backward weights for AMX GEMM.
*
* Converts base weights to transposed BufferB format for backward pass.
* For backward GEMM, we need:
* - gate_backward_bb_: gate_proj transposed [hidden_size, intermediate_size]
* - up_backward_bb_: up_proj transposed [hidden_size, intermediate_size]
* - down_backward_bb_: down_proj transposed [intermediate_size, hidden_size]
*
* Must be called before backward_down/backward_gate_up if backward_weights_prepared_ is false.
*/
void prepare_backward_weights() {
// Only prepare weights for kernels that support standard mat_mul
if constexpr (!supports_standard_mat_mul_v<T>) {
return; // KGroup kernels use for-loop implementation
}
if (backward_weights_prepared_) return;
if (config_.gate_proj == nullptr) return; // No base weights to prepare
auto pool = config_.pool->get_subpool(tp_part_idx);
// Fine-grained parallelism: nth_gate_up * expert_num * 2 + nth_down * expert_num tasks
int nth_gate_up = T::recommended_nth(config_.hidden_size);
int nth_down = T::recommended_nth(config_.intermediate_size);
// Phase 1: gate + up backward (both have same dimensions)
// gate/up_proj: [intermediate_size, hidden_size] -> transposed BufferB [hidden_size, intermediate_size]
pool->do_work_stealing_job(
nth_gate_up * config_.expert_num * 2, nullptr,
[this, nth_gate_up](int task_id) {
int proj_idx = task_id / (nth_gate_up * config_.expert_num); // 0=gate, 1=up
int remaining = task_id % (nth_gate_up * config_.expert_num);
int expert_idx = remaining / nth_gate_up;
int ith = remaining % nth_gate_up;
const ggml_bf16_t* src =
(proj_idx == 0) ? (const ggml_bf16_t*)config_.gate_proj : (const ggml_bf16_t*)config_.up_proj;
auto& dst_bb = (proj_idx == 0) ? gate_backward_bb_[expert_idx] : up_backward_bb_[expert_idx];
// source: [intermediate_size, hidden_size], target: [hidden_size, intermediate_size]
size_t expert_offset = (size_t)expert_idx * config_.intermediate_size * config_.hidden_size;
dst_bb->from_mat_transposed((ggml_bf16_t*)(src + expert_offset), config_.intermediate_size,
config_.hidden_size, ith, nth_gate_up);
},
nullptr);
// Phase 2: down backward
// down_proj: [hidden_size, intermediate_size] -> transposed BufferB [intermediate_size, hidden_size]
pool->do_work_stealing_job(
nth_down * config_.expert_num, nullptr,
[this, nth_down](int task_id) {
int expert_idx = task_id / nth_down;
int ith = task_id % nth_down;
const ggml_bf16_t* src = (const ggml_bf16_t*)config_.down_proj;
// source: [hidden_size, intermediate_size], target: [intermediate_size, hidden_size]
size_t expert_offset = (size_t)expert_idx * config_.hidden_size * config_.intermediate_size;
down_backward_bb_[expert_idx]->from_mat_transposed((ggml_bf16_t*)(src + expert_offset), config_.hidden_size,
config_.intermediate_size, ith, nth_down);
},
nullptr);
backward_weights_prepared_ = true;
}
/**
* @brief Dynamically repack backward BufferB from forward weights using to_mat() + from_mat_transposed().
* Used in share_backward_bb mode (Mode 1) to avoid persistent backward_bb_pool_ per instance.
*/
void prepare_backward_weights_from_forward() {
if constexpr (!supports_standard_mat_mul_v<T>) return;
auto pool = config_.pool->get_subpool(tp_part_idx);
// Phase 1: gate + up (both use [intermediate_size, hidden_size] -> [hidden_size, intermediate_size])
pool->do_work_stealing_job(
config_.expert_num * 2, nullptr,
[this](int task_id) {
int proj = task_id / config_.expert_num;
int expert_idx = task_id % config_.expert_num;
auto& src_bb = (proj == 0) ? gate_bb_[expert_idx] : up_bb_[expert_idx];
auto& dst_bb = (proj == 0) ? gate_backward_bb_[expert_idx] : up_backward_bb_[expert_idx];
if constexpr (has_bb_transposed_repack_v<T>) {
int nth = T::recommended_nth(dst_bb->n);
for (int p = 0; p < nth; p++) dst_bb->from_bb_transposed(*src_bb, p, nth);
} else {
thread_local std::vector<ggml_bf16_t> workspace;
workspace.resize((size_t)src_bb->n * src_bb->k);
int src_nth = T::recommended_nth(src_bb->n);
for (int p = 0; p < src_nth; p++) src_bb->to_mat(workspace.data(), p, src_nth);
int dst_nth = T::recommended_nth(dst_bb->n);
for (int p = 0; p < dst_nth; p++)
dst_bb->from_mat_transposed(workspace.data(), src_bb->n, src_bb->k, p, dst_nth);
}
},
nullptr);
// Phase 2: down (uses [hidden_size, intermediate_size] -> [intermediate_size, hidden_size])
pool->do_work_stealing_job(
config_.expert_num, nullptr,
[this](int task_id) {
auto& src_bb = down_bb_[task_id];
auto& dst_bb = down_backward_bb_[task_id];
if constexpr (has_bb_transposed_repack_v<T>) {
int nth = T::recommended_nth(dst_bb->n);
for (int p = 0; p < nth; p++) dst_bb->from_bb_transposed(*src_bb, p, nth);
} else {
thread_local std::vector<ggml_bf16_t> workspace;
workspace.resize((size_t)src_bb->n * src_bb->k);
int src_nth = T::recommended_nth(src_bb->n);
for (int p = 0; p < src_nth; p++) src_bb->to_mat(workspace.data(), p, src_nth);
int dst_nth = T::recommended_nth(dst_bb->n);
for (int p = 0; p < dst_nth; p++)
dst_bb->from_mat_transposed(workspace.data(), src_bb->n, src_bb->k, p, dst_nth);
}
},
nullptr);
backward_weights_prepared_ = true;
}
/**
* @brief Standalone method for async backward BB repack (Phase 2).
* Called from TP_MOE_SFT::submit_backward_repack() on a separate thread.
* Allocates/resizes the shared backward_bb pool, repacks from forward weights,
* and sets the owner layer on the shared pool.
*/
void prepare_backward_bb_for_async() {
if constexpr (!supports_standard_mat_mul_v<T>) return;
if (backward_bb_pool_bytes_ == 0) return;
// Free any locally-allocated pool before switching to shared
if (backward_bb_locally_owned_ && backward_bb_pool_ != nullptr) {
free(backward_bb_pool_);
backward_bb_pool_ = nullptr;
backward_bb_locally_owned_ = false;
}
alloc_or_resize_backward_bb(backward_bb_pool_bytes_);
backward_bb_locally_owned_ = false;
init_backward_bb_pointers();
backward_weights_prepared_ = false;
prepare_backward_weights_from_forward();
// backward_weights_prepared_ = true is set inside prepare_backward_weights_from_forward()
auto& shared = SFTSharedPools::instance();
shared.ensure_numa_count(tp_part_idx + 1);
shared.pools[tp_part_idx].bwd_bb_owner_layer = config_.layer_idx;
}
/**
* @brief Set base weight pointers for TP partitioning.
* Used by TP_MOE_SFT::load_weights() to set partitioned weights before calling load_weights().
* Unlike prepare_bwd, this does NOT call prepare_backward_weights() and does NOT reset pointers.
*/
void set_weight_pointers_for_forward(void* gate_proj, void* up_proj, void* down_proj) {
config_.gate_proj = gate_proj;
config_.up_proj = up_proj;
config_.down_proj = down_proj;
}
/**
* @brief Clear base weight pointers after forward path initialization.
*/
void clear_weight_pointers() {
config_.gate_proj = nullptr;
config_.up_proj = nullptr;
config_.down_proj = nullptr;
}
/**
* @brief Set base weight pointers for TP partitioning (backward path).
* Used by TP_MOE_SFT::load_weights() to set partitioned weights and prepare backward weights.
*/
void prepare_bwd(void* gate_proj, void* up_proj, void* down_proj) {
// If pool not yet allocated (Mode 1 init), allocate per-instance for save/load path
if (backward_bb_pool_ == nullptr && backward_bb_pool_bytes_ > 0) {
backward_bb_pool_ = aligned_alloc(64, backward_bb_pool_bytes_);
init_backward_bb_pointers();
backward_bb_locally_owned_ = true;
}
// Try loading pre-quantized backward weights from disk first
if (!config_.path.empty()) {
std::filesystem::path prefix = config_.path;
prefix = prefix / ("_layer_" + std::to_string(config_.layer_idx)) / ("_numa_" + std::to_string(tp_part_idx));
if (load_backward_weights(prefix)) {
printf(" [BWD] Loaded pre-quantized backward weights from disk (layer %d, numa %d)\n", config_.layer_idx,
tp_part_idx);
return;
}
}
// Fall back to online transpose + quantize
config_.gate_proj = gate_proj;
config_.up_proj = up_proj;
config_.down_proj = down_proj;
prepare_backward_weights();
// Save to disk for next time if save mode is enabled
if (config_.save && !config_.path.empty()) {
std::filesystem::path prefix = config_.path;
prefix = prefix / ("_layer_" + std::to_string(config_.layer_idx)) / ("_numa_" + std::to_string(tp_part_idx));
save_backward_weights(prefix);
}
config_.gate_proj = 0;
config_.up_proj = 0;
config_.down_proj = 0;
}
/**
* @brief Write backward weights to disk (reuses forward weight save pattern from moe.hpp).
*/
void write_bwd_weights(std::filesystem::path prefix, std::string mat_class, char* bb, int expert_idx, size_t size,
size_t scale_size) {
std::ofstream of(prefix / (T::name() + mat_class + std::to_string(expert_idx) + "_" +
std::to_string(size - scale_size) + "Byte" + "_quant_" + ".kt"));
if (!of.is_open()) {
printf("write_bwd_weights: cannot open file: %s\n",
(prefix / (T::name() + mat_class + std::to_string(expert_idx) + "_" + std::to_string(size - scale_size) +
"Byte" + "_quant_" + ".kt"))
.c_str());
}
of.write(bb, size - scale_size);
of.close();
of.open(prefix / (T::name() + mat_class + std::to_string(expert_idx) + "_" + std::to_string(scale_size) + "Byte" +
"_scale_" + ".kt"));
if (!of.is_open()) {
printf("write_bwd_weights: cannot open scale file\n");
}
of.write(bb + (size - scale_size), scale_size);
of.close();
}
/**
* @brief Save pre-quantized backward weights to disk.
* Must be called after prepare_backward_weights().
*/
void save_backward_weights(const std::filesystem::path& prefix) {
if constexpr (!supports_standard_mat_mul_v<T>) return;
if (!backward_weights_prepared_) return;
std::filesystem::create_directories(prefix);
for (int expert_idx = 0; expert_idx < config_.expert_num; expert_idx++) {
// gate_bwd: [hidden_size, intermediate_size]
size_t gu_size = T::BufferB::required_size(config_.hidden_size, config_.intermediate_size);
size_t gu_scale = T::BufferB::SCALE ? config_.hidden_size * sizeof(float) : 0;
write_bwd_weights(prefix, "_gate_bwd_", (char*)gate_backward_bb_[expert_idx]->b, expert_idx, gu_size, gu_scale);
write_bwd_weights(prefix, "_up_bwd_", (char*)up_backward_bb_[expert_idx]->b, expert_idx, gu_size, gu_scale);
// down_bwd: [intermediate_size, hidden_size]
size_t d_size = T::BufferB::required_size(config_.intermediate_size, config_.hidden_size);
size_t d_scale = T::BufferB::SCALE ? config_.intermediate_size * sizeof(float) : 0;
write_bwd_weights(prefix, "_down_bwd_", (char*)down_backward_bb_[expert_idx]->b, expert_idx, d_size, d_scale);
}
}
/**
* @brief Load pre-quantized backward weights from disk.
* @return true if files exist and loading succeeds, false otherwise.
*/
bool load_backward_weights(const std::filesystem::path& prefix) {
if constexpr (!supports_standard_mat_mul_v<T>) return false;
if (backward_weights_prepared_) return true;
// Check if files exist for the first expert
size_t gu_size = T::BufferB::required_size(config_.hidden_size, config_.intermediate_size);
size_t gu_scale = T::BufferB::SCALE ? config_.hidden_size * sizeof(float) : 0;
std::string test_file = T::name() + "_gate_bwd_0_" + std::to_string(gu_size - gu_scale) + "Byte_quant_.kt";
if (!std::filesystem::exists(prefix / test_file)) return false;
size_t d_size = T::BufferB::required_size(config_.intermediate_size, config_.hidden_size);
size_t d_scale = T::BufferB::SCALE ? config_.intermediate_size * sizeof(float) : 0;
// mat_class: 0=gate_bwd, 1=up_bwd, 2=down_bwd
static constexpr int mat_type_all = 3;
std::atomic<bool> ok{true};
auto pool = config_.pool->get_subpool(tp_part_idx);
auto read_one = [&](int expert_idx, const char* proj_name, char* dst_b, size_t size, size_t scale_size,
auto* bb_ptr /* only used when SCALE */) {
std::ifstream f(prefix / (T::name() + proj_name + std::to_string(expert_idx) + "_" +
std::to_string(size - scale_size) + "Byte_quant_.kt"));
if (!f.is_open()) {
ok.store(false, std::memory_order_relaxed);
return;
}
f.read(dst_b, size - scale_size);
f.close();
if constexpr (T::BufferB::SCALE) {
f.open(prefix / (T::name() + proj_name + std::to_string(expert_idx) + "_" + std::to_string(scale_size) +
"Byte_scale_.kt"));
if (!f.is_open()) {
ok.store(false, std::memory_order_relaxed);
return;
}
f.read((char*)bb_ptr->d, scale_size);
}
};
pool->do_work_stealing_job(
config_.expert_num * mat_type_all, nullptr,
[&](int task_id) {
if (!ok.load(std::memory_order_relaxed)) return;
int expert_idx = task_id / mat_type_all;
int mat_class = task_id % mat_type_all;
if (mat_class == 0) {
read_one(expert_idx, "_gate_bwd_", (char*)gate_backward_bb_[expert_idx]->b, gu_size, gu_scale,
gate_backward_bb_[expert_idx].get());
} else if (mat_class == 1) {
read_one(expert_idx, "_up_bwd_", (char*)up_backward_bb_[expert_idx]->b, gu_size, gu_scale,
up_backward_bb_[expert_idx].get());
} else {
read_one(expert_idx, "_down_bwd_", (char*)down_backward_bb_[expert_idx]->b, d_size, d_scale,
down_backward_bb_[expert_idx].get());
}
},
nullptr);
if (!ok.load()) return false;
backward_weights_prepared_ = true;
return true;
}
/**
* @brief Load backward weights from pre-quantized per-NUMA buffers (memcpy path).
* Uses gate_bwd_projs/scales etc. from GeneralMOEConfig.
*/
void load_backward_weights_from_projs() {
if constexpr (!supports_standard_mat_mul_v<T>) return;
if (backward_weights_prepared_) return;
auto pool = config_.pool->get_subpool(tp_part_idx);
pool->do_work_stealing_job(
config_.expert_num, nullptr,
[this](int expert_idx) {
const uint64_t* physical_to_logical_map = (const uint64_t*)config_.physical_to_logical_map;
uint64_t logical_expert_id = expert_map(physical_to_logical_map, expert_idx);
// gate_bwd: [hidden_size, intermediate_size]
{
size_t scale_size = T::BufferB::SCALE ? config_.hidden_size * sizeof(float) : 0;
size_t size = T::BufferB::required_size(config_.hidden_size, config_.intermediate_size) - scale_size;
memcpy(gate_backward_bb_[expert_idx]->b, config_.gate_bwd_projs[tp_part_idx][logical_expert_id], size);
if constexpr (T::BufferB::SCALE) {
memcpy(gate_backward_bb_[expert_idx]->d, config_.gate_bwd_scales[tp_part_idx][logical_expert_id],
scale_size);
}
}
// up_bwd
{
size_t scale_size = T::BufferB::SCALE ? config_.hidden_size * sizeof(float) : 0;
size_t size = T::BufferB::required_size(config_.hidden_size, config_.intermediate_size) - scale_size;
memcpy(up_backward_bb_[expert_idx]->b, config_.up_bwd_projs[tp_part_idx][logical_expert_id], size);
if constexpr (T::BufferB::SCALE) {
memcpy(up_backward_bb_[expert_idx]->d, config_.up_bwd_scales[tp_part_idx][logical_expert_id], scale_size);
}
}
// down_bwd: [intermediate_size, hidden_size]
{
size_t scale_size = T::BufferB::SCALE ? config_.intermediate_size * sizeof(float) : 0;
size_t size = T::BufferB::required_size(config_.intermediate_size, config_.hidden_size) - scale_size;
memcpy(down_backward_bb_[expert_idx]->b, config_.down_bwd_projs[tp_part_idx][logical_expert_id], size);
if constexpr (T::BufferB::SCALE) {
memcpy(down_backward_bb_[expert_idx]->d, config_.down_bwd_scales[tp_part_idx][logical_expert_id],
scale_size);
}
}
},
nullptr);
backward_weights_prepared_ = true;
}
/**
* @brief Set physical to logical expert mapping.
*/
void set_physical_to_logical_map(const void* map) { config_.physical_to_logical_map = const_cast<void*>(map); }
private:
/**
* @brief Initialize all buffers in a single alloc() call.
*
* IMPORTANT: SharedMemBuffer is designed to let multiple callers share the same memory pool.
* Each alloc() call assigns pointers starting from the SAME base address, which means:
* - Multiple alloc() calls will OVERLAP in memory!
* - This is intentional for temporary buffers that are not used simultaneously.
* - But for SFT, cache and grad buffers ARE used simultaneously (cache written during forward,
* grad written during backward, both needed in backward_activation).
*
* Solution: Combine all buffer requests into a SINGLE alloc() call, so they get
* consecutive, non-overlapping addresses.
*
* Bug #15 root cause: Three separate alloc() calls caused grad_intermediate_ to overlap
* with cache_gate_output_pool_, and memset in backward_down() zeroed the cache data.
*/
void init_all_buffers() {
// =====================================================
// Calculate padded_lora_rank for AMX alignment
// AMX requires K dimension to be multiple of K_STEP (32 for BF16)
// =====================================================
constexpr int K_STEP = T::K_STEP;
constexpr int N_STEP = T::N_STEP;
constexpr int M_STEP = T::M_STEP;
int lora_k_step = K_STEP;
if constexpr (std::is_same_v<T, amx::GemmKernel224Int4> || std::is_same_v<T, amx::GemmKernel224Int4_1>) {
lora_k_step = 2 * K_STEP;
}
padded_lora_rank_ = ((lora_rank_ + lora_k_step - 1) / lora_k_step) * lora_k_step;
// Also need N dimension aligned for BufferB output dimension
int padded_lora_rank_n = ((lora_rank_ + N_STEP - 1) / N_STEP) * N_STEP;
// Use the larger of the two for consistency
padded_lora_rank_ = std::max(padded_lora_rank_, padded_lora_rank_n);
// Calculate all buffer sizes (cast to size_t to prevent int overflow with large max_len)
const size_t ml = config_.max_len;
const size_t k_tok = config_.num_experts_per_tok;
const size_t H = config_.hidden_size;
const size_t I = config_.intermediate_size;
lora_intermediate_pool_bytes_ = sizeof(ggml_bf16_t) * ml * k_tok * lora_rank_;
cache_slot_bytes_input_ = ml * H * sizeof(ggml_bf16_t);
cache_slot_bytes_intermediate_ = ml * k_tok * I * sizeof(ggml_bf16_t);
cache_slot_bytes_down_lora_u_ = ml * k_tok * lora_rank_ * sizeof(float);
cache_down_output_bytes_ = (size_t)max_cache_depth_ * ml * k_tok * H * sizeof(ggml_bf16_t);
grad_buffer_bytes_ = ml * k_tok * I * sizeof(ggml_bf16_t);
// =====================================================
// Calculate LoRA AMX buffer sizes
// Only for kernels that support standard mat_mul API
// =====================================================
// Max tokens per expert (with M_STEP alignment)
// Bug-C Fix: Each expert processes at most max_len tokens (worst case: all tokens select this expert)
// Previously used max_len * num_experts_per_tok which is incorrect and wastes 8x memory
size_t max_m = ((config_.max_len + M_STEP - 1) / M_STEP) * M_STEP;
// Variables for buffer sizes (used in init_lora_amx_buffers)
size_t lora_a_gate_up_bb_size = 0;
size_t lora_b_gate_up_bb_size = 0;
size_t lora_a_gate_up_t_bb_size = 0;
size_t lora_b_gate_up_t_bb_size = 0;
size_t lora_a_down_bb_size = 0;
size_t lora_b_down_bb_size = 0;
size_t lora_a_down_t_bb_size = 0;
size_t lora_b_down_t_bb_size = 0;
size_t lora_intermediate_ba_size = 0;
size_t lora_intermediate_bc_size = 0;
size_t lora_gate_up_out_bc_size = 0;
size_t lora_down_out_bc_size = 0;
size_t grad_output_ba_size = 0;
size_t grad_intermediate_bc_size = 0;
size_t grad_gate_up_bc_size = 0;
size_t gate_up_backward_bb_size = 0;
size_t down_backward_bb_size = 0;
if constexpr (supports_standard_mat_mul_v<T>) {
// BufferB sizes for LoRA weights (need to be aligned)
// gate/up lora_A: [padded_lora_rank, hidden_size] per expert
lora_a_gate_up_bb_size = T::BufferB::required_size(padded_lora_rank_, config_.hidden_size);
// gate/up lora_B: [intermediate_size, padded_lora_rank] per expert
lora_b_gate_up_bb_size = T::BufferB::required_size(config_.intermediate_size, padded_lora_rank_);
// Transposed weights for backward LoRA GEMM
// gate/up lora_A^T: [hidden_size, padded_lora_rank] per expert
lora_a_gate_up_t_bb_size = T::BufferB::required_size(config_.hidden_size, padded_lora_rank_);
// gate/up lora_B^T: [padded_lora_rank, intermediate_size] per expert
lora_b_gate_up_t_bb_size = T::BufferB::required_size(padded_lora_rank_, config_.intermediate_size);
// down lora_A: [padded_lora_rank, intermediate_size] per expert
lora_a_down_bb_size = T::BufferB::required_size(padded_lora_rank_, config_.intermediate_size);
// down lora_B: [hidden_size, padded_lora_rank] per expert
lora_b_down_bb_size = T::BufferB::required_size(config_.hidden_size, padded_lora_rank_);
// down lora_A^T: [intermediate_size, padded_lora_rank] per expert (for backward)
lora_a_down_t_bb_size = T::BufferB::required_size(config_.intermediate_size, padded_lora_rank_);
// down lora_B^T: [padded_lora_rank, hidden_size] per expert (for backward)
lora_b_down_t_bb_size = T::BufferB::required_size(padded_lora_rank_, config_.hidden_size);
// Total BufferB pool size for all experts (12 matrices per expert)
lora_bb_pool_bytes_ = config_.expert_num * (lora_a_gate_up_bb_size * 2 + // gate_a, up_a
lora_b_gate_up_bb_size * 2 + // gate_b, up_b
lora_a_gate_up_t_bb_size * 2 + // gate_a^T, up_a^T
lora_b_gate_up_t_bb_size * 2 + // gate_b^T, up_b^T
lora_a_down_bb_size + // down_a
lora_b_down_bb_size + // down_b
lora_a_down_t_bb_size + // down_a^T
lora_b_down_t_bb_size); // down_b^T
size_t raw_total_tokens = (size_t)config_.max_len * config_.num_experts_per_tok;
size_t safe_alloc_tokens = raw_total_tokens + (config_.expert_num * M_STEP);
// Ensure global alignment too
safe_alloc_tokens = ((safe_alloc_tokens + M_STEP - 1) / M_STEP) * M_STEP;
// Add extra bytes for "align64" calls inside the loops (64 bytes per expert per buffer)
size_t align_overhead = config_.expert_num * 64;
// BufferA for LoRA intermediate: shared pool for all activated experts
// Need 2x for gate and up separate buffers (to avoid race condition)
lora_intermediate_ba_size = T::BufferA::required_size(max_m, padded_lora_rank_); // per-expert size for set_data
lora_ba_pool_bytes_ = T::BufferA::required_size(safe_alloc_tokens, padded_lora_rank_) * 2 + align_overhead * 2;
// BufferC for LoRA step 1 output: shared pool for all activated experts
// Need 2x for gate and up separate buffers (to avoid race condition)
lora_intermediate_bc_size = T::BufferC::required_size(max_m, padded_lora_rank_); // per-expert size for set_data
lora_bc_inter_pool_bytes_ =
T::BufferC::required_size(safe_alloc_tokens, padded_lora_rank_) * 2 + align_overhead * 2;
// BufferC for LoRA step 2 output (gate, up, down): shared pool for all activated experts
lora_gate_up_out_bc_size = T::BufferC::required_size(max_m, config_.intermediate_size); // per-expert size
lora_down_out_bc_size = T::BufferC::required_size(max_m, config_.hidden_size); // per-expert size
// Note: bc_out needs space for Gate, Up AND Down
lora_bc_out_pool_bytes_ = T::BufferC::required_size(safe_alloc_tokens, config_.intermediate_size) * 2 +
T::BufferC::required_size(safe_alloc_tokens, config_.hidden_size) + align_overhead * 3;
// BF16 intermediate buffer for step 1 -> step 2 conversion
// Need 2x for gate and up separate buffers (to avoid race condition)
lora_intermediate_bf16_pool_bytes_ =
safe_alloc_tokens * padded_lora_rank_ * sizeof(ggml_bf16_t) * 2 + align_overhead * 2;
// =====================================================
// Calculate Backward pass AMX buffer sizes
// =====================================================
// BufferA for scattered grad_output: shared pool for all activated experts
grad_output_ba_size = T::BufferA::required_size(max_m, config_.hidden_size); // per-expert size
backward_ba_pool_bytes_ = T::BufferA::required_size(safe_alloc_tokens, config_.hidden_size) + align_overhead;
// BufferC for backward GEMM outputs: shared pool for all activated experts
// grad_intermediate: [safe_alloc_tokens, intermediate_size]
grad_intermediate_bc_size = T::BufferC::required_size(max_m, config_.intermediate_size); // per-expert size
// grad_gate_up: [safe_alloc_tokens, hidden_size]
grad_gate_up_bc_size = T::BufferC::required_size(max_m, config_.hidden_size); // per-expert size
backward_bc_pool_bytes_ = T::BufferC::required_size(safe_alloc_tokens, config_.intermediate_size) +
T::BufferC::required_size(safe_alloc_tokens, config_.hidden_size) + align_overhead * 2;
// BF16 buffer for scattered grad_output
grad_output_bf16_pool_bytes_ = safe_alloc_tokens * config_.hidden_size * sizeof(ggml_bf16_t) + align_overhead;
// LoRA gradient computation FP32 pools (used in bwd_down_lora_precompute and grad computation)
// Total tokens across all activated experts = safe_alloc_tokens
lora_grad_out_pool_bytes_ = safe_alloc_tokens * config_.hidden_size * sizeof(float) + align_overhead;
lora_inter_proj_pool_bytes_ = safe_alloc_tokens * lora_rank_ * sizeof(float) + align_overhead;
lora_grad_times_b_pool_bytes_ = safe_alloc_tokens * lora_rank_ * sizeof(float) + align_overhead;
down_lora_grad_b_accum_pool_bytes_ =
static_cast<size_t>(config_.expert_num) * config_.hidden_size * lora_rank_ * sizeof(float) + align_overhead;
down_lora_grad_a_accum_pool_bytes_ =
static_cast<size_t>(config_.expert_num) * config_.intermediate_size * lora_rank_ * sizeof(float) +
align_overhead;
// =====================================================
// Calculate Backward pass BufferB sizes (transposed base weights)
// =====================================================
// For backward GEMM, we need transposed versions of base weights:
// - gate/up backward: BufferB[hidden_size, intermediate_size] per expert
// - down backward: BufferB[intermediate_size, hidden_size] per expert
gate_up_backward_bb_size = T::BufferB::required_size(config_.hidden_size, config_.intermediate_size);
down_backward_bb_size = T::BufferB::required_size(config_.intermediate_size, config_.hidden_size);
backward_bb_pool_bytes_ = config_.expert_num * (gate_up_backward_bb_size * 2 + down_backward_bb_size);
} else {
// For unsupported kernels (KGroup kernels), set all AMX buffer sizes to 0
// These kernels will use the original for-loop implementation
lora_bb_pool_bytes_ = 0;
lora_ba_pool_bytes_ = 0;
lora_bc_inter_pool_bytes_ = 0;
lora_bc_out_pool_bytes_ = 0;
lora_intermediate_bf16_pool_bytes_ = 0;
backward_ba_pool_bytes_ = 0;
backward_bc_pool_bytes_ = 0;
grad_output_bf16_pool_bytes_ = 0;
backward_bb_pool_bytes_ = 0;
lora_grad_out_pool_bytes_ = 0;
lora_inter_proj_pool_bytes_ = 0;
lora_grad_times_b_pool_bytes_ = 0;
down_lora_grad_b_accum_pool_bytes_ = 0;
down_lora_grad_a_accum_pool_bytes_ = 0;
}
// ★ Bug #18 fix: Cache buffers use aligned_alloc instead of shared_mem_buffer_numa ★
// The base class AMX_MOE_BASE::init() also calls shared_mem_buffer_numa.alloc(), and
// SharedMemBuffer is designed to let multiple callers share the same memory pool.
// This causes cache buffers to overlap with base class buffers like m_local_gate_output_,
// which corrupts the cache when apply_activation() writes to m_local_gate_output_.
// Solution: Use aligned_alloc for cache pools so they have dedicated memory.
// ★ seqlen-dependent buffers are allocated on-demand ★
// Forward/cache buffers are pooled to avoid frequent alloc/free overhead:
// - Cache buffers: allocated in forward_sft() when save_for_backward=true (kept in per-instance cache_pool_)
// - LoRA working buffers (ba/bc/bf16): allocated in forward_sft() (kept in shared forward_pool_)
// - Backward working buffers: allocated in backward() (kept in shared backward_pool_)
//
// Only persistent buffers are allocated here:
// - lora_bb_pool_: LoRA weights in BufferB format (not seqlen-dependent)
// - backward_bb_pool_: transposed base weights in BufferB format (not seqlen-dependent)
MemoryRequest mem_requests;
// LoRA buffers (legacy, kept for compatibility) - still uses SharedMemBuffer
mem_requests.append_pointer(&lora_intermediate_pool_, lora_intermediate_pool_bytes_);
// LoRA BB pool (persistent - stores converted LoRA weights, not seqlen-dependent)
if (lora_bb_pool_bytes_ > 0) {
lora_bb_pool_ = aligned_alloc(64, lora_bb_pool_bytes_);
}
// ★ Backward pass working buffers are allocated on-demand in backward() and freed after use ★
// This saves memory when not training (inference mode).
// backward_ba_pool_, backward_bc_pool_, grad_output_bf16_pool_ are allocated at the start of backward()
// and freed at the end.
//
// backward_bb_pool_ is different: it stores transposed base weights (BufferB format) that need to be
// initialized once and persist. So it's allocated here in the constructor.
// In share_backward_bb mode (Mode 1), skip per-instance allocation — backward() will use a shared pool
// and dynamically repack from forward weights each step.
if (config_.share_backward_bb) {
backward_bb_pool_ = nullptr;
backward_bb_locally_owned_ = false;
} else {
if (backward_bb_pool_bytes_ > 0) {
backward_bb_pool_ = aligned_alloc(64, backward_bb_pool_bytes_);
}
backward_bb_locally_owned_ = true;
}
// Single allocation for remaining buffers (only lora_intermediate_pool_ uses SharedMemBuffer now)
shared_mem_buffer_numa.alloc(tp_part_idx, this, mem_requests);
// Initialize LoRA pointer (only lora_intermediate_pool_ is allocated via SharedMemBuffer)
lora_intermediate_ = (ggml_bf16_t*)lora_intermediate_pool_;
// Note: grad_intermediate_, grad_gate_output_, grad_up_output_ are set in alloc_backward_buffers()
// Initialize cache stack (only vectors, pointers are set in alloc_forward_buffers())
cache_stack_.resize(max_cache_depth_);
// Preallocate cache offsets to avoid heap allocation in hot path
cache_offsets_.resize(config_.expert_num + 1);
for (int i = 0; i < max_cache_depth_; i++) {
// Note: cache pointers (input_cache, gate_output_cache, etc.) are set in alloc_forward_buffers()
cache_stack_[i].input_cache = nullptr;
cache_stack_[i].gate_output_cache = nullptr;
cache_stack_[i].up_output_cache = nullptr;
cache_stack_[i].intermediate_cache = nullptr;
cache_stack_[i].down_output_cache = nullptr;
cache_stack_[i].m_local_num_cache.resize(config_.expert_num);
cache_stack_[i].m_local_pos_cache.resize(config_.max_len);
for (int j = 0; j < config_.max_len; j++) {
cache_stack_[i].m_local_pos_cache[j].resize(config_.num_experts_per_tok);
}
cache_stack_[i].m_expert_id_map_cache.resize(config_.expert_num);
}
// =====================================================
// Initialize LoRA AMX buffer objects (only for supported kernels)
// =====================================================
if constexpr (supports_standard_mat_mul_v<T>) {
init_lora_amx_buffers(max_m, lora_a_gate_up_bb_size, lora_b_gate_up_bb_size, lora_a_gate_up_t_bb_size,
lora_b_gate_up_t_bb_size, lora_a_down_bb_size, lora_b_down_bb_size, lora_a_down_t_bb_size,
lora_b_down_t_bb_size, lora_intermediate_ba_size, lora_intermediate_bc_size,
lora_gate_up_out_bc_size, lora_down_out_bc_size, grad_output_ba_size,
grad_intermediate_bc_size, grad_gate_up_bc_size, gate_up_backward_bb_size,
down_backward_bb_size);
}
// Pool logger: static allocation summary (printed once per instance at init)
SFT_POOL_LOG("init_static", config_.layer_idx, tp_part_idx, config_.max_len, 0, lora_bb_pool_bytes_,
backward_bb_pool_bytes_, 0, backward_bb_pool_bytes_ + lora_bb_pool_bytes_,
"static_alloc: expert_num=%d hidden=%d inter=%d lora_bb=%.2fGB bwd_bb=%.2fGB", config_.expert_num,
config_.hidden_size, config_.intermediate_size, lora_bb_pool_bytes_ / 1024.0 / 1024.0 / 1024.0,
backward_bb_pool_bytes_ / 1024.0 / 1024.0 / 1024.0);
}
/**
* @brief Initialize LoRA AMX buffer objects (including backward pass buffers).
*/
void init_lora_amx_buffers(size_t max_m, size_t lora_a_gate_up_bb_size, size_t lora_b_gate_up_bb_size,
size_t lora_a_gate_up_t_bb_size, size_t lora_b_gate_up_t_bb_size,
size_t lora_a_down_bb_size, size_t lora_b_down_bb_size, size_t lora_a_down_t_bb_size,
size_t lora_b_down_t_bb_size, size_t lora_intermediate_ba_size,
size_t lora_intermediate_bc_size, size_t lora_gate_up_out_bc_size,
size_t lora_down_out_bc_size, size_t grad_output_ba_size, size_t grad_intermediate_bc_size,
size_t grad_gate_up_bc_size, size_t gate_up_backward_bb_size,
size_t down_backward_bb_size) {
// Resize vectors - forward pass
gate_lora_a_bb_.resize(config_.expert_num);
up_lora_a_bb_.resize(config_.expert_num);
down_lora_a_bb_.resize(config_.expert_num);
gate_lora_b_bb_.resize(config_.expert_num);
up_lora_b_bb_.resize(config_.expert_num);
down_lora_b_bb_.resize(config_.expert_num);
gate_lora_a_t_bb_.resize(config_.expert_num);
up_lora_a_t_bb_.resize(config_.expert_num);
gate_lora_b_t_bb_.resize(config_.expert_num);
up_lora_b_t_bb_.resize(config_.expert_num);
down_lora_a_t_bb_.resize(config_.expert_num);
down_lora_b_t_bb_.resize(config_.expert_num);
// Separate buffers for gate and up to avoid race condition
lora_gate_intermediate_ba_.resize(config_.expert_num);
lora_up_intermediate_ba_.resize(config_.expert_num);
lora_gate_intermediate_bc_.resize(config_.expert_num);
lora_up_intermediate_bc_.resize(config_.expert_num);
lora_gate_out_bc_.resize(config_.expert_num);
lora_up_out_bc_.resize(config_.expert_num);
lora_down_out_bc_.resize(config_.expert_num);
lora_gate_intermediate_ptr_.resize(config_.expert_num);
lora_up_intermediate_ptr_.resize(config_.expert_num);
// Resize vectors - backward pass
grad_output_ba_.resize(config_.expert_num);
grad_intermediate_bc_.resize(config_.expert_num);
grad_gate_up_bc_.resize(config_.expert_num);
grad_output_bf16_ptr_.resize(config_.expert_num);
// Resize vectors - backward BufferB (transposed base weights)
gate_backward_bb_.resize(config_.expert_num);
up_backward_bb_.resize(config_.expert_num);
down_backward_bb_.resize(config_.expert_num);
// Calculate offsets and create buffer objects
// Bug-C Fix Step 2: BufferA/BufferC use shared pools, data will be assigned in forward/backward
char* bb_ptr = (char*)lora_bb_pool_;
for (int i = 0; i < config_.expert_num; i++) {
// BufferB for LoRA weights (still per-expert, as weights are different for each expert)
gate_lora_a_bb_[i] = std::make_shared<typename T::BufferB>(padded_lora_rank_, config_.hidden_size, (void*)bb_ptr);
bb_ptr += lora_a_gate_up_bb_size;
up_lora_a_bb_[i] = std::make_shared<typename T::BufferB>(padded_lora_rank_, config_.hidden_size, (void*)bb_ptr);
bb_ptr += lora_a_gate_up_bb_size;
gate_lora_b_bb_[i] =
std::make_shared<typename T::BufferB>(config_.intermediate_size, padded_lora_rank_, (void*)bb_ptr);
bb_ptr += lora_b_gate_up_bb_size;
up_lora_b_bb_[i] =
std::make_shared<typename T::BufferB>(config_.intermediate_size, padded_lora_rank_, (void*)bb_ptr);
bb_ptr += lora_b_gate_up_bb_size;
gate_lora_a_t_bb_[i] =
std::make_shared<typename T::BufferB>(config_.hidden_size, padded_lora_rank_, (void*)bb_ptr);
bb_ptr += lora_a_gate_up_t_bb_size;
up_lora_a_t_bb_[i] = std::make_shared<typename T::BufferB>(config_.hidden_size, padded_lora_rank_, (void*)bb_ptr);
bb_ptr += lora_a_gate_up_t_bb_size;
gate_lora_b_t_bb_[i] =
std::make_shared<typename T::BufferB>(padded_lora_rank_, config_.intermediate_size, (void*)bb_ptr);
bb_ptr += lora_b_gate_up_t_bb_size;
up_lora_b_t_bb_[i] =
std::make_shared<typename T::BufferB>(padded_lora_rank_, config_.intermediate_size, (void*)bb_ptr);
bb_ptr += lora_b_gate_up_t_bb_size;
down_lora_a_bb_[i] =
std::make_shared<typename T::BufferB>(padded_lora_rank_, config_.intermediate_size, (void*)bb_ptr);
bb_ptr += lora_a_down_bb_size;
down_lora_b_bb_[i] = std::make_shared<typename T::BufferB>(config_.hidden_size, padded_lora_rank_, (void*)bb_ptr);
bb_ptr += lora_b_down_bb_size;
down_lora_a_t_bb_[i] =
std::make_shared<typename T::BufferB>(config_.intermediate_size, padded_lora_rank_, (void*)bb_ptr);
bb_ptr += lora_a_down_t_bb_size;
down_lora_b_t_bb_[i] =
std::make_shared<typename T::BufferB>(padded_lora_rank_, config_.hidden_size, (void*)bb_ptr);
bb_ptr += lora_b_down_t_bb_size;
// BufferA for LoRA intermediate: create with nullptr, will set_data in forward
lora_gate_intermediate_ba_[i] = std::make_shared<typename T::BufferA>(max_m, padded_lora_rank_, nullptr);
lora_up_intermediate_ba_[i] = std::make_shared<typename T::BufferA>(max_m, padded_lora_rank_, nullptr);
// BufferC for LoRA step 1 output: create with nullptr, will set_data in forward
lora_gate_intermediate_bc_[i] = std::make_shared<typename T::BufferC>(max_m, padded_lora_rank_, nullptr);
lora_up_intermediate_bc_[i] = std::make_shared<typename T::BufferC>(max_m, padded_lora_rank_, nullptr);
// BufferC for LoRA step 2 output: create with nullptr, will set_data in forward
lora_gate_out_bc_[i] = std::make_shared<typename T::BufferC>(max_m, config_.intermediate_size, nullptr);
lora_up_out_bc_[i] = std::make_shared<typename T::BufferC>(max_m, config_.intermediate_size, nullptr);
lora_down_out_bc_[i] = std::make_shared<typename T::BufferC>(max_m, config_.hidden_size, nullptr);
// BF16 intermediate pointer: will be assigned in forward
lora_gate_intermediate_ptr_[i] = nullptr;
lora_up_intermediate_ptr_[i] = nullptr;
}
// =====================================================
// Initialize backward pass buffer objects
// Bug-C Fix Step 2: Use shared pools, data will be assigned in backward
// =====================================================
for (int i = 0; i < config_.expert_num; i++) {
// BufferA for grad_output: create with nullptr, will set_data in backward
grad_output_ba_[i] = std::make_shared<typename T::BufferA>(max_m, config_.hidden_size, nullptr);
// BufferC for grad_intermediate: create with nullptr, will set_data in backward
grad_intermediate_bc_[i] = std::make_shared<typename T::BufferC>(max_m, config_.intermediate_size, nullptr);
// BufferC for grad_gate_up: create with nullptr, will set_data in backward
grad_gate_up_bc_[i] = std::make_shared<typename T::BufferC>(max_m, config_.hidden_size, nullptr);
// BF16 pointer: will be assigned in backward
grad_output_bf16_ptr_[i] = nullptr;
}
// =====================================================
// Initialize backward BufferB objects (transposed base weights)
// =====================================================
if (backward_bb_pool_ != nullptr) {
init_backward_bb_pointers();
}
// If nullptr (Mode 1 at init), vectors stay with nullptr shared_ptrs — safe.
lora_weights_prepared_ = false;
lora_backward_weights_prepared_ = false;
backward_weights_prepared_ = false;
}
/**
* @brief Point backward BufferB objects at the current backward_bb_pool_.
* Requires backward_bb_pool_ != nullptr and backward_bb_pool_bytes_ > 0.
*/
void init_backward_bb_pointers() {
size_t gate_up_backward_bb_size = T::BufferB::required_size(config_.hidden_size, config_.intermediate_size);
size_t down_backward_bb_size = T::BufferB::required_size(config_.intermediate_size, config_.hidden_size);
char* backward_bb_ptr = (char*)backward_bb_pool_;
for (int i = 0; i < config_.expert_num; i++) {
gate_backward_bb_[i] =
std::make_shared<typename T::BufferB>(config_.hidden_size, config_.intermediate_size, (void*)backward_bb_ptr);
backward_bb_ptr += gate_up_backward_bb_size;
up_backward_bb_[i] =
std::make_shared<typename T::BufferB>(config_.hidden_size, config_.intermediate_size, (void*)backward_bb_ptr);
backward_bb_ptr += gate_up_backward_bb_size;
down_backward_bb_[i] =
std::make_shared<typename T::BufferB>(config_.intermediate_size, config_.hidden_size, (void*)backward_bb_ptr);
backward_bb_ptr += down_backward_bb_size;
}
}
/**
* @brief Get thread-local buffer for LoRA weight conversion.
*
* Uses thread_local storage to avoid repeated memory allocation.
* The buffer is resized only when a larger size is needed.
*/
static ggml_bf16_t* get_lora_convert_buffer(size_t required_size) {
thread_local std::vector<ggml_bf16_t> tl_buffer;
if (tl_buffer.size() < required_size) {
tl_buffer.resize(required_size);
}
return tl_buffer.data();
}
/**
* @brief Get thread-local FP32 buffer for LoRA intermediate results.
*
* Used by AVX512 LoRA computation to store intermediate FP32 values.
*/
static float* get_lora_fp32_buffer(size_t required_size) {
thread_local std::vector<float> tl_fp32_buffer;
if (tl_fp32_buffer.size() < required_size) {
tl_fp32_buffer.resize(required_size);
}
return tl_fp32_buffer.data();
}
/**
* @brief Convert LoRA A matrix to BufferB format with padding.
*
* LoRA A shape: [expert_num, lora_rank, k_dim]
* Padded shape: [expert_num, padded_lora_rank, k_dim]
* BufferB expects: [n_dim, k_dim] where n_dim = padded_lora_rank
*
* Padding rows with zeros for lora_rank < padded_lora_rank.
*/
void convert_lora_a_to_buffer_b(const ggml_bf16_t* src, std::shared_ptr<typename T::BufferB>& dst_bb, int expert_idx,
int src_n, int src_k, int dst_n, int dst_k) {
// Use thread-local buffer to avoid allocation
size_t buf_size = static_cast<size_t>(dst_n) * dst_k;
ggml_bf16_t* padded = get_lora_convert_buffer(buf_size);
// Zero-initialize the buffer
const ggml_bf16_t zero = GGML_FP32_TO_BF16(0.0f);
std::fill(padded, padded + buf_size, zero);
// Copy source data (with potential padding)
const ggml_bf16_t* expert_src = src + expert_idx * src_n * src_k;
for (int r = 0; r < src_n && r < dst_n; r++) {
for (int c = 0; c < src_k && c < dst_k; c++) {
padded[r * dst_k + c] = expert_src[r * src_k + c];
}
}
// Convert to BufferB format using from_mat
// NOTE: from_mat with (ith, nth) only processes one N_BLOCK chunk.
// For dst_n > N_BLOCK, we need to loop over all N_BLOCKs.
int num_n_blocks = (dst_n + T::N_BLOCK - 1) / T::N_BLOCK;
for (int ith = 0; ith < num_n_blocks; ith++) {
dst_bb->from_mat(padded, ith, num_n_blocks);
}
}
/**
* @brief Convert LoRA B matrix to BufferB format with padding.
*
* LoRA B shape: [expert_num, output_dim, lora_rank]
* Padded shape: [expert_num, output_dim, padded_lora_rank]
* BufferB expects: [n_dim, k_dim] where n_dim = output_dim, k_dim = padded_lora_rank
*
* Padding columns with zeros for lora_rank < padded_lora_rank.
*/
void convert_lora_b_to_buffer_b(const ggml_bf16_t* src, std::shared_ptr<typename T::BufferB>& dst_bb, int expert_idx,
int src_n, int src_k, int dst_n, int dst_k) {
// Use thread-local buffer to avoid allocation
size_t buf_size = static_cast<size_t>(dst_n) * dst_k;
ggml_bf16_t* padded = get_lora_convert_buffer(buf_size);
// Zero-initialize the buffer
const ggml_bf16_t zero = GGML_FP32_TO_BF16(0.0f);
std::fill(padded, padded + buf_size, zero);
// Copy source data (with potential padding on K dimension)
const ggml_bf16_t* expert_src = src + expert_idx * src_n * src_k;
for (int r = 0; r < src_n && r < dst_n; r++) {
for (int c = 0; c < src_k && c < dst_k; c++) {
padded[r * dst_k + c] = expert_src[r * src_k + c];
}
}
// Convert to BufferB format using from_mat
// NOTE: from_mat with (ith, nth) only processes one N_BLOCK chunk.
// For dst_n > N_BLOCK, we need to loop over all N_BLOCKs.
int num_n_blocks = (dst_n + T::N_BLOCK - 1) / T::N_BLOCK;
for (int ith = 0; ith < num_n_blocks; ith++) {
dst_bb->from_mat(padded, ith, num_n_blocks);
}
}
/**
* @brief Convert LoRA A^T matrix to BufferB format with padding on rank dimension.
*
* Input shape: [expert_num, lora_rank, hidden_size]
* Output shape: [expert_num, hidden_size, padded_lora_rank]
*/
void convert_lora_a_transposed_to_buffer_b(const ggml_bf16_t* src, std::shared_ptr<typename T::BufferB>& dst_bb,
int expert_idx, int src_n, int src_k, int dst_n, int dst_k) {
// Use thread-local buffer to avoid allocation
size_t buf_size = static_cast<size_t>(dst_n) * dst_k;
ggml_bf16_t* padded = get_lora_convert_buffer(buf_size);
// Zero-initialize the buffer
const ggml_bf16_t zero = GGML_FP32_TO_BF16(0.0f);
std::fill(padded, padded + buf_size, zero);
const ggml_bf16_t* expert_src = src + expert_idx * src_n * src_k;
for (int h = 0; h < src_k && h < dst_n; h++) {
for (int r = 0; r < src_n && r < dst_k; r++) {
padded[h * dst_k + r] = expert_src[r * src_k + h];
}
}
// NOTE: from_mat with (ith, nth) only processes one N_BLOCK chunk.
// For dst_n > N_BLOCK (hidden_size is typically 7168), we need to loop over all N_BLOCKs.
int num_n_blocks = (dst_n + T::N_BLOCK - 1) / T::N_BLOCK;
for (int ith = 0; ith < num_n_blocks; ith++) {
dst_bb->from_mat(padded, ith, num_n_blocks);
}
}
/**
* @brief Convert LoRA B^T matrix to BufferB format with padding on rank dimension.
*
* Input shape: [expert_num, intermediate_size, lora_rank]
* Output shape: [expert_num, padded_lora_rank, intermediate_size]
*/
void convert_lora_b_transposed_to_buffer_b(const ggml_bf16_t* src, std::shared_ptr<typename T::BufferB>& dst_bb,
int expert_idx, int src_n, int src_k, int dst_n, int dst_k) {
// Use thread-local buffer to avoid allocation
size_t buf_size = static_cast<size_t>(dst_n) * dst_k;
ggml_bf16_t* padded = get_lora_convert_buffer(buf_size);
// Zero-initialize the buffer
const ggml_bf16_t zero = GGML_FP32_TO_BF16(0.0f);
std::fill(padded, padded + buf_size, zero);
const ggml_bf16_t* expert_src = src + expert_idx * src_n * src_k;
for (int r = 0; r < src_k && r < dst_n; r++) {
for (int i = 0; i < src_n && i < dst_k; i++) {
padded[r * dst_k + i] = expert_src[i * src_k + r];
}
}
// NOTE: from_mat with (ith, nth) only processes one N_BLOCK chunk.
// For dst_n > N_BLOCK, we need to loop over all N_BLOCKs.
int num_n_blocks = (dst_n + T::N_BLOCK - 1) / T::N_BLOCK;
for (int ith = 0; ith < num_n_blocks; ith++) {
dst_bb->from_mat(padded, ith, num_n_blocks);
}
}
/**
* @brief Compute LoRA for gate and up projections using AMX GEMM.
*
* gate_lora_out = (input @ gate_lora_A^T) @ gate_lora_B^T * scaling
* gate_output += gate_lora_out
* (similar for up)
*
* This is the AMX-optimized version replacing the naive for-loop implementation.
*/
void compute_lora_gate_up_amx(int qlen, int activated_expert) {
if (gate_lora_a_ == nullptr || gate_lora_b_ == nullptr) {
return;
}
auto pool = config_.pool->get_subpool(tp_part_idx);
// Ensure LoRA weights are prepared
prepare_lora_weights();
// =====================================================
// Bug-C Fix Step 2: Allocate LoRA buffers from shared pool
// =====================================================
constexpr size_t M_STEP = T::M_STEP;
auto align64 = [](size_t v) { return (v + 63) & (~(size_t)63); };
// Pool pointers for forward LoRA buffers
char* lora_ba_ptr = (char*)lora_ba_pool_;
char* lora_bc_inter_ptr = (char*)lora_bc_inter_pool_;
char* lora_bc_out_ptr = (char*)lora_bc_out_pool_;
char* bf16_inter_ptr = (char*)lora_intermediate_bf16_pool_;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) continue;
size_t local_max_m = ((m + M_STEP - 1) / M_STEP) * M_STEP;
// Allocate BufferA for intermediate (gate and up)
lora_gate_intermediate_ba_[expert_idx]->max_m = local_max_m;
lora_gate_intermediate_ba_[expert_idx]->set_data(lora_ba_ptr);
lora_ba_ptr += align64(T::BufferA::required_size(local_max_m, padded_lora_rank_));
lora_up_intermediate_ba_[expert_idx]->max_m = local_max_m;
lora_up_intermediate_ba_[expert_idx]->set_data(lora_ba_ptr);
lora_ba_ptr += align64(T::BufferA::required_size(local_max_m, padded_lora_rank_));
// Allocate BufferC for intermediate (gate and up)
lora_gate_intermediate_bc_[expert_idx]->max_m = local_max_m;
lora_gate_intermediate_bc_[expert_idx]->set_data(lora_bc_inter_ptr);
lora_bc_inter_ptr += align64(T::BufferC::required_size(local_max_m, padded_lora_rank_));
lora_up_intermediate_bc_[expert_idx]->max_m = local_max_m;
lora_up_intermediate_bc_[expert_idx]->set_data(lora_bc_inter_ptr);
lora_bc_inter_ptr += align64(T::BufferC::required_size(local_max_m, padded_lora_rank_));
// Allocate BufferC for output (gate, up, down - but down is done in compute_lora_down_amx)
lora_gate_out_bc_[expert_idx]->max_m = local_max_m;
lora_gate_out_bc_[expert_idx]->set_data(lora_bc_out_ptr);
lora_bc_out_ptr += align64(T::BufferC::required_size(local_max_m, config_.intermediate_size));
lora_up_out_bc_[expert_idx]->max_m = local_max_m;
lora_up_out_bc_[expert_idx]->set_data(lora_bc_out_ptr);
lora_bc_out_ptr += align64(T::BufferC::required_size(local_max_m, config_.intermediate_size));
// Allocate BF16 intermediate buffer (gate and up)
lora_gate_intermediate_ptr_[expert_idx] = (ggml_bf16_t*)bf16_inter_ptr;
bf16_inter_ptr += align64(local_max_m * padded_lora_rank_ * sizeof(ggml_bf16_t));
lora_up_intermediate_ptr_[expert_idx] = (ggml_bf16_t*)bf16_inter_ptr;
bf16_inter_ptr += align64(local_max_m * padded_lora_rank_ * sizeof(ggml_bf16_t));
}
// =====================================================
// Bounds Check: Verify pool allocation didn't overflow
// =====================================================
if (is_nan_check_enabled()) {
char* lora_ba_pool_end = (char*)lora_ba_pool_ + lora_ba_pool_bytes_;
char* lora_bc_inter_pool_end = (char*)lora_bc_inter_pool_ + lora_bc_inter_pool_bytes_;
char* lora_bc_out_pool_end = (char*)lora_bc_out_pool_ + lora_bc_out_pool_bytes_;
char* lora_bf16_pool_end = (char*)lora_intermediate_bf16_pool_ + lora_intermediate_bf16_pool_bytes_;
size_t ba_used = lora_ba_ptr - (char*)lora_ba_pool_;
size_t bc_inter_used = lora_bc_inter_ptr - (char*)lora_bc_inter_pool_;
size_t bc_out_used = lora_bc_out_ptr - (char*)lora_bc_out_pool_;
size_t bf16_used = bf16_inter_ptr - (char*)lora_intermediate_bf16_pool_;
bool overflow = false;
if (lora_ba_ptr > lora_ba_pool_end) {
printf(
ANSI_BG_RED
"[OVERFLOW L%d] lora_ba_pool: used=%zu bytes, allocated=%zu bytes, OVERFLOW by %zu bytes" ANSI_COLOR_RESET
"\n",
config_.layer_idx, ba_used, lora_ba_pool_bytes_, ba_used - lora_ba_pool_bytes_);
overflow = true;
}
if (lora_bc_inter_ptr > lora_bc_inter_pool_end) {
printf(ANSI_BG_RED
"[OVERFLOW L%d] lora_bc_inter_pool: used=%zu bytes, allocated=%zu bytes, OVERFLOW by %zu "
"bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, bc_inter_used, lora_bc_inter_pool_bytes_, bc_inter_used - lora_bc_inter_pool_bytes_);
overflow = true;
}
if (lora_bc_out_ptr > lora_bc_out_pool_end) {
printf(ANSI_BG_RED
"[OVERFLOW L%d] lora_bc_out_pool: used=%zu bytes, allocated=%zu bytes, OVERFLOW by %zu "
"bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, bc_out_used, lora_bc_out_pool_bytes_, bc_out_used - lora_bc_out_pool_bytes_);
overflow = true;
}
if (bf16_inter_ptr > lora_bf16_pool_end) {
printf(ANSI_BG_RED
"[OVERFLOW L%d] lora_intermediate_bf16_pool: used=%zu bytes, allocated=%zu bytes, OVERFLOW by %zu "
"bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, bf16_used, lora_intermediate_bf16_pool_bytes_,
bf16_used - lora_intermediate_bf16_pool_bytes_);
overflow = true;
}
if (overflow) {
// Print detailed per-expert allocation info
printf("[OVERFLOW DEBUG L%d] activated_expert=%d, M_STEP=%zu, padded_lora_rank=%d\n", config_.layer_idx,
activated_expert, M_STEP, padded_lora_rank_);
size_t sum_tokens = 0, sum_padded_tokens = 0;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m > 0) {
size_t local_max_m = ((m + M_STEP - 1) / M_STEP) * M_STEP;
sum_tokens += m;
sum_padded_tokens += local_max_m;
printf(" expert=%d tokens=%d padded=%zu\n", expert_idx, m, local_max_m);
}
}
printf("[OVERFLOW DEBUG L%d] sum_tokens=%zu, sum_padded_tokens=%zu, padding_overhead=%zu\n", config_.layer_idx,
sum_tokens, sum_padded_tokens, sum_padded_tokens - sum_tokens);
printf("[OVERFLOW DEBUG L%d] config: max_len=%d, num_experts_per_tok=%d, expert_num=%d\n", config_.layer_idx,
config_.max_len, config_.num_experts_per_tok, config_.expert_num);
printf("[OVERFLOW DEBUG L%d] expected raw_total_tokens=%zu, safe_alloc_tokens estimate=%zu\n",
config_.layer_idx, (size_t)config_.max_len * config_.num_experts_per_tok,
(size_t)config_.max_len * config_.num_experts_per_tok + config_.expert_num * (size_t)M_STEP);
}
// Always print summary for debugging token distribution
size_t sum_tokens = 0, max_expert_tokens = 0;
int max_expert_idx = -1;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
sum_tokens += m;
if ((size_t)m > max_expert_tokens) {
max_expert_tokens = m;
max_expert_idx = expert_idx;
}
}
// Check if any single expert has extremely high token count
size_t expected_per_expert = sum_tokens / (activated_expert > 0 ? activated_expert : 1);
if (max_expert_tokens > expected_per_expert * 10 && max_expert_tokens > 1000) {
printf(ANSI_COLOR_YELLOW
"[WARN L%d] Expert %d has %zu tokens (%.1fx average), activated_expert=%d, total=%zu" ANSI_COLOR_RESET
"\n",
config_.layer_idx, max_expert_idx, max_expert_tokens,
(double)max_expert_tokens / (expected_per_expert > 0 ? expected_per_expert : 1), activated_expert,
sum_tokens);
}
}
// =====================================================
// Step 1: input @ lora_A^T -> lora_intermediate
// Uses gate_up_ba_ (already quantized input)
// Gate and Up use SEPARATE intermediate buffers to avoid race condition
// =====================================================
int nth = T::recommended_nth(padded_lora_rank_);
pool->do_work_stealing_job(
nth * activated_expert * 2, [](int _) { T::config(); },
[this, nth](int task_id2) {
int task_id = task_id2 / 2;
bool do_up = task_id2 % 2;
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
auto& ba = gate_up_ba_[expert_idx]; // Reuse quantized input
auto& bb = do_up ? up_lora_a_bb_[expert_idx] : gate_lora_a_bb_[expert_idx];
// Use separate BufferC for gate and up to avoid race condition
auto& bc = do_up ? lora_up_intermediate_bc_[expert_idx] : lora_gate_intermediate_bc_[expert_idx];
// GEMM: [m, hidden_size] @ [padded_lora_rank, hidden_size]^T -> [m, padded_lora_rank]
amx::mat_mul(m, padded_lora_rank_, config_.hidden_size, ba, bb, bc, ith, nth);
// Convert BufferC to BF16 for step 2 input (separate for gate and up)
ggml_bf16_t* inter_ptr =
do_up ? lora_up_intermediate_ptr_[expert_idx] : lora_gate_intermediate_ptr_[expert_idx];
bc->to_mat(m, inter_ptr, ith, nth);
},
nullptr);
// =====================================================
// Step 2: Quantize lora_intermediate to BufferA
// Need to quantize BOTH gate and up intermediates separately
// =====================================================
pool->do_work_stealing_job(
activated_expert * 2, nullptr, // 2x tasks for gate and up
[this](int task_id) {
bool do_up = task_id % 2;
int expert_idx = m_expert_id_map_[task_id / 2];
int m = m_local_num_[expert_idx];
if (m == 0) return;
// Use separate BufferA and BF16 pointer for gate and up
auto& ba = do_up ? lora_up_intermediate_ba_[expert_idx] : lora_gate_intermediate_ba_[expert_idx];
ggml_bf16_t* ptr = do_up ? lora_up_intermediate_ptr_[expert_idx] : lora_gate_intermediate_ptr_[expert_idx];
ba->from_mat(m, ptr, 0, 1);
},
nullptr);
// =====================================================
// Step 3a: lora_intermediate @ lora_B^T -> lora_output (GEMM only)
// =====================================================
nth = T::recommended_nth(config_.intermediate_size);
pool->do_work_stealing_job(
nth * activated_expert * 2, [](int _) { T::config(); },
[this, nth](int task_id2) {
int task_id = task_id2 / 2;
bool do_up = task_id2 % 2;
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
// Use separate BufferA for gate and up
auto& ba = do_up ? lora_up_intermediate_ba_[expert_idx] : lora_gate_intermediate_ba_[expert_idx];
auto& bb = do_up ? up_lora_b_bb_[expert_idx] : gate_lora_b_bb_[expert_idx];
auto& bc = do_up ? lora_up_out_bc_[expert_idx] : lora_gate_out_bc_[expert_idx];
// GEMM: [m, padded_lora_rank] @ [intermediate_size, padded_lora_rank]^T -> [m, intermediate_size]
amx::mat_mul(m, config_.intermediate_size, padded_lora_rank_, ba, bb, bc, ith, nth);
},
nullptr);
// =====================================================
// Step 3b: Add LoRA output to main output with scaling
// =====================================================
double gate_lora_sum = 0.0;
double up_lora_sum = 0.0;
pool->do_work_stealing_job(
nth * activated_expert * 2, nullptr,
[this, nth, &gate_lora_sum, &up_lora_sum](int task_id2) {
int task_id = task_id2 / 2;
bool do_up = task_id2 % 2;
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
auto& bc = do_up ? lora_up_out_bc_[expert_idx] : lora_gate_out_bc_[expert_idx];
ggml_bf16_t* main_output = do_up ? m_local_up_output_ptr_[expert_idx] : m_local_gate_output_ptr_[expert_idx];
double* lora_sum_ptr = do_up ? &up_lora_sum : &gate_lora_sum;
add_lora_output_to_main(bc.get(), main_output, m, config_.intermediate_size, lora_scaling_, ith, nth,
lora_sum_ptr);
},
nullptr);
}
/**
* @brief Compute LoRA for down projection using AMX GEMM.
*/
void compute_lora_down_amx(int qlen, int activated_expert) {
if (down_lora_a_ == nullptr || down_lora_b_ == nullptr) return;
auto pool = config_.pool->get_subpool(tp_part_idx);
// Ensure LoRA weights are prepared
prepare_lora_weights();
// =====================================================
// Bug-C Fix Step 2: Allocate lora_down_out_bc_ from shared pool
// Note: lora_gate_intermediate_bc_ and lora_gate_intermediate_ba_ are reused
// from compute_lora_gate_up_amx (they are not used simultaneously)
// =====================================================
constexpr size_t M_STEP = T::M_STEP;
auto align64 = [](size_t v) { return (v + 63) & (~(size_t)63); };
// Use offset after gate and up output buffers in lora_bc_out_pool_
// Pool layout: [gate_out × N] [up_out × N] [down_out × N]
// But since we allocate dynamically, we need to track the offset
// Actually, we can reuse the lora_bc_out_pool_ starting position since
// gate/up outputs are already consumed by this point
// For simplicity, allocate from the end of the pool (after gate+up)
// Calculate gate+up total size first
size_t gate_up_total = 0;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
size_t local_max_m = ((m_local_num_[expert_idx] + M_STEP - 1) / M_STEP) * M_STEP;
gate_up_total += align64(T::BufferC::required_size(local_max_m, config_.intermediate_size)) * 2; // gate + up
}
char* lora_down_bc_ptr = (char*)lora_bc_out_pool_ + gate_up_total;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) continue;
size_t local_max_m = ((m + M_STEP - 1) / M_STEP) * M_STEP;
lora_down_out_bc_[expert_idx]->max_m = local_max_m;
lora_down_out_bc_[expert_idx]->set_data(lora_down_bc_ptr);
lora_down_bc_ptr += align64(T::BufferC::required_size(local_max_m, config_.hidden_size));
}
// =====================================================
// Bounds Check: Verify pool allocation didn't overflow (gate+up+down)
// =====================================================
if (is_nan_check_enabled()) {
char* lora_bc_out_pool_end = (char*)lora_bc_out_pool_ + lora_bc_out_pool_bytes_;
size_t bc_out_used = lora_down_bc_ptr - (char*)lora_bc_out_pool_;
if (lora_down_bc_ptr > lora_bc_out_pool_end) {
printf(ANSI_BG_RED
"[OVERFLOW L%d] lora_bc_out_pool (gate+up+down): used=%zu bytes, allocated=%zu bytes, OVERFLOW by %zu "
"bytes" ANSI_COLOR_RESET "\n",
config_.layer_idx, bc_out_used, lora_bc_out_pool_bytes_, bc_out_used - lora_bc_out_pool_bytes_);
printf("[OVERFLOW DEBUG L%d] gate_up_total=%zu bytes\n", config_.layer_idx, gate_up_total);
}
}
// =====================================================
// Step 1: intermediate @ down_lora_A^T -> lora_intermediate
// Uses down_ba_ (already quantized intermediate after activation)
// =====================================================
int nth = T::recommended_nth(padded_lora_rank_);
pool->do_work_stealing_job(
nth * activated_expert, [](int _) { T::config(); },
[this, nth](int task_id) {
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
auto& ba = down_ba_[expert_idx]; // Reuse quantized intermediate
auto& bb = down_lora_a_bb_[expert_idx];
// Reuse gate intermediate buffer (no race condition for down projection)
auto& bc = lora_gate_intermediate_bc_[expert_idx];
// GEMM: [m, intermediate_size] @ [padded_lora_rank, intermediate_size]^T -> [m, padded_lora_rank]
amx::mat_mul(m, padded_lora_rank_, config_.intermediate_size, ba, bb, bc, ith, nth);
// Convert BufferC to BF16 for step 2 input
bc->to_mat(m, lora_gate_intermediate_ptr_[expert_idx], ith, nth);
},
nullptr);
// =====================================================
// Step 2: Quantize lora_intermediate to BufferA
// =====================================================
pool->do_work_stealing_job(
activated_expert, nullptr,
[this](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) return;
// Reuse gate intermediate buffer (no race condition for down projection)
lora_gate_intermediate_ba_[expert_idx]->from_mat(m, lora_gate_intermediate_ptr_[expert_idx], 0, 1);
},
nullptr);
// =====================================================
// Step 3a: lora_intermediate @ down_lora_B^T -> lora_output (GEMM only)
// =====================================================
nth = T::recommended_nth(config_.hidden_size);
pool->do_work_stealing_job(
nth * activated_expert, [](int _) { T::config(); },
[this, nth](int task_id) {
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
// Reuse gate intermediate buffer (no race condition for down projection)
auto& ba = lora_gate_intermediate_ba_[expert_idx];
auto& bb = down_lora_b_bb_[expert_idx];
auto& bc = lora_down_out_bc_[expert_idx];
// GEMM: [m, padded_lora_rank] @ [hidden_size, padded_lora_rank]^T -> [m, hidden_size]
amx::mat_mul(m, config_.hidden_size, padded_lora_rank_, ba, bb, bc, ith, nth);
},
nullptr);
// =====================================================
// Step 3b: Add LoRA output to main output with scaling
// =====================================================
double down_lora_sum = 0.0;
pool->do_work_stealing_job(
nth * activated_expert, nullptr,
[this, nth, &down_lora_sum](int task_id) {
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
auto& bc = lora_down_out_bc_[expert_idx];
// Add LoRA output to main output with scaling and collect statistics
add_lora_output_to_main(bc.get(), m_local_down_output_ptr_[expert_idx], m, config_.hidden_size, lora_scaling_,
ith, nth, &down_lora_sum);
},
nullptr);
// // Print LoRA contribution statistics
// size_t total_elements = 0;
// for (int i = 0; i < activated_expert; i++) {
// total_elements += m_local_num_[m_expert_id_map_[i]];
// }
// total_elements *= config_.hidden_size;
// if (total_elements > 0) {
// double down_lora_mean = down_lora_sum / total_elements;
// printf("[LoRA] layer=%d down_mean=%.6e\n", config_.layer_idx, down_lora_mean);
// }
}
/**
* @brief Add LoRA BufferC output to main BF16 output with scaling.
*
* main_output[i] += lora_bc_output[i] * scaling
* @param lora_sum Optional pointer to accumulate sum of absolute LoRA contributions for statistics
*/
void add_lora_output_to_main(typename T::BufferC* bc, ggml_bf16_t* main_output, int m, int n, float scaling, int ith,
int nth, double* lora_sum = nullptr) {
// BUG FIX: BufferC uses tiled layout [n_blocks][m_blocks][n_steps][M_STEP][N_STEP]
// We must iterate over tiles (m_begin in M_STEP steps) and rows within tiles (i)
// to correctly compute the offset into the tiled buffer.
constexpr int M_STEP = T::M_STEP;
constexpr int N_STEP = T::N_STEP;
constexpr int N_BLOCK = T::N_BLOCK;
auto [n_start, n_end] = T::split_range_n(n, ith, nth);
double local_sum = 0.0;
int m_block_size = (m + M_STEP - 1) / M_STEP * M_STEP;
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
__m512 scale = _mm512_set1_ps(scaling);
for (int m_begin = 0; m_begin < m; m_begin += M_STEP) {
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int i = 0; i < M_STEP && m_begin + i < m; i++) {
// Compute correct offset into tiled BufferC (same formula as BufferC::to_mat)
float* c_ptr = bc->c + m_block_size * n_block_begin + m_begin * n_block_size + n_begin * M_STEP + i * N_STEP;
// Load from main output (BF16)
int row = m_begin + i;
int col = n_block_begin + n_begin;
__m512 main0, main1;
avx512_32xbf16_to_32xfp32((__m512i*)(main_output + row * n + col), &main0, &main1);
// Load LoRA output from BufferC (FP32)
__m512 lora0 = _mm512_load_ps(c_ptr);
__m512 lora1 = _mm512_load_ps(c_ptr + 16);
// // Accumulate absolute LoRA contribution for statistics
// if (lora_sum != nullptr) {
// for (int j = 0; j < 16; j++) {
// local_sum += std::abs(c_ptr[j] * scaling);
// local_sum += std::abs(c_ptr[j + 16] * scaling);
// }
// }
// Add with scaling: main = main + lora * scale
main0 = _mm512_fmadd_ps(lora0, scale, main0);
main1 = _mm512_fmadd_ps(lora1, scale, main1);
// Store back to main output (BF16)
avx512_32xfp32_to_32xbf16(&main0, &main1, (__m512i*)(main_output + row * n + col));
}
}
}
// if (lora_sum != nullptr) {
// #pragma omp atomic
// *lora_sum += local_sum;
// }
}
/**
* @brief Compute LoRA for gate and up projections (AVX512 BF16 optimized).
*
* gate_lora_out = (input @ gate_lora_A^T) @ gate_lora_B^T * scaling
* gate_output += gate_lora_out
* (similar for up)
*
* Optimized with:
* - Native _mm512_dpbf16_ps for BF16 dot-accumulate (no BF16->FP32 conversion)
* - Token-blocking (T_BLOCK=4): process 4 tokens per weight load
* - Rank-blocking (R_BLOCK=4): process 4 ranks in parallel
* - Arithmetic intensity: 2.0 FLOP/byte
*/
void compute_lora_gate_up(int qlen, int activated_expert) {
auto pool = config_.pool->get_subpool(tp_part_idx);
const int hidden = config_.hidden_size;
const int inter_size = config_.intermediate_size;
const int rank = lora_rank_;
const float scale = lora_scaling_;
const int nth = 2;
pool->do_work_stealing_job(
activated_expert * 2 * nth, nullptr,
[this, hidden, inter_size, rank, scale, nth](int task_id) {
bool do_up = (task_id / nth) % 2;
int expert_task = task_id / (2 * nth);
int ith = task_id % nth;
int expert_idx = m_expert_id_map_[expert_task];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
// Divide tokens among threads
int tokens_per_thread = (num_tokens + nth - 1) / nth;
int t_start = ith * tokens_per_thread;
int t_end = std::min(t_start + tokens_per_thread, num_tokens);
if (t_start >= num_tokens) return;
// Get weight pointers
ggml_bf16_t* lora_a = do_up ? up_lora_a_ : gate_lora_a_;
ggml_bf16_t* lora_b_t = do_up ? up_lora_b_transposed_ : gate_lora_b_transposed_;
ggml_bf16_t* input = m_local_input_ptr_[expert_idx];
ggml_bf16_t* output = do_up ? m_local_up_output_ptr_[expert_idx] : m_local_gate_output_ptr_[expert_idx];
if (lora_a == nullptr || lora_b_t == nullptr) return;
size_t lora_a_offset = expert_idx * lora_rank_ * config_.hidden_size;
// Transposed layout: [expert_num][rank][intermediate_size]
size_t lora_b_t_offset = expert_idx * lora_rank_ * config_.intermediate_size;
ggml_bf16_t* expert_lora_a = lora_a + lora_a_offset;
ggml_bf16_t* expert_lora_b_t = lora_b_t + lora_b_t_offset;
int local_num_tokens = t_end - t_start;
float* local_intermediate = get_lora_fp32_buffer(local_num_tokens * rank);
// Step 1: intermediate = input @ lora_A^T (optimized with T_BLOCK=4, R_BLOCK=4)
avx::lora_bf16_matmul_t4r4(input + t_start * hidden, // input for this thread's tokens
expert_lora_a, // lora_A weight [rank, hidden]
local_intermediate, // output [local_num_tokens, rank]
local_num_tokens, hidden, rank);
// Step 2: output += scale * (intermediate @ lora_B_transposed)
// Using optimized kernel with pre-transposed weight layout [rank][inter_size]
avx::lora_fp32_bf16_fused_add_transposed(
local_intermediate, // intermediate [local_num_tokens, rank]
expert_lora_b_t, // lora_B transposed [rank, inter_size]
output + t_start * inter_size, // output [local_num_tokens, inter_size]
local_num_tokens, rank, inter_size, scale);
},
nullptr);
}
/**
* @brief Compute LoRA for down projection (AVX512 BF16 optimized).
*
* Optimized with:
* - Native _mm512_dpbf16_ps for BF16 dot-accumulate (no BF16->FP32 conversion)
* - Token-blocking (T_BLOCK=4): process 4 tokens per weight load
* - Rank-blocking (R_BLOCK=4): process 4 ranks in parallel
* - Arithmetic intensity: 2.0 FLOP/byte
*/
void compute_lora_down(int qlen, int activated_expert, ForwardCache* cache = nullptr) {
auto pool = config_.pool->get_subpool(tp_part_idx);
if (down_lora_a_ == nullptr || down_lora_b_ == nullptr) return;
const int inter_size = config_.intermediate_size;
const int hidden = config_.hidden_size;
const int rank = lora_rank_;
const float scale = lora_scaling_;
const int nth = 2;
pool->do_work_stealing_job(
nth * activated_expert, nullptr,
[this, cache, inter_size, hidden, rank, scale, nth](int task_id) {
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
int tokens_per_thread = (num_tokens + nth - 1) / nth;
int t_start = ith * tokens_per_thread;
int t_end = std::min(t_start + tokens_per_thread, num_tokens);
if (t_start >= num_tokens) return;
ggml_bf16_t* input = m_local_gate_output_ptr_[expert_idx];
ggml_bf16_t* output = m_local_down_output_ptr_[expert_idx];
size_t lora_a_offset = expert_idx * lora_rank_ * config_.intermediate_size;
// Transposed layout: [expert_num][rank][hidden_size]
size_t lora_b_t_offset = expert_idx * lora_rank_ * config_.hidden_size;
ggml_bf16_t* expert_lora_a = down_lora_a_ + lora_a_offset;
ggml_bf16_t* expert_lora_b_t = down_lora_b_transposed_ + lora_b_t_offset;
int local_num_tokens = t_end - t_start;
float* local_intermediate = get_lora_fp32_buffer(local_num_tokens * rank);
// Step 1: intermediate = input @ lora_A^T (optimized with T_BLOCK=4, R_BLOCK=4)
avx::lora_bf16_matmul_t4r4(input + t_start * inter_size, // input for this thread's tokens
expert_lora_a, // lora_A weight [rank, inter_size]
local_intermediate, // output [local_num_tokens, rank]
local_num_tokens, inter_size, rank);
if (cache != nullptr && cache->down_lora_u_cache != nullptr) {
float* cache_u = cache->down_lora_u_cache + (cache_offsets_[task_id / nth] + t_start) * rank;
memcpy(cache_u, local_intermediate, static_cast<size_t>(local_num_tokens) * rank * sizeof(float));
}
// Step 2: output += scale * (intermediate @ lora_B_transposed)
// Using optimized kernel with pre-transposed weight layout [rank][hidden]
avx::lora_fp32_bf16_fused_add_transposed(local_intermediate, // intermediate [local_num_tokens, rank]
expert_lora_b_t, // lora_B transposed [rank, hidden]
output + t_start * hidden, // output [local_num_tokens, hidden]
local_num_tokens, rank, hidden, scale);
},
nullptr);
}
ForwardCache& push_cache() {
if (cache_stack_top_ >= max_cache_depth_) {
// std::cerr << "[KT-MOE ERROR] Forward cache stack overflow!" << std::endl;
// std::cerr << " cache_stack_top_ = " << cache_stack_top_ << std::endl;
// std::cerr << " max_cache_depth_ = " << max_cache_depth_ << std::endl;
// std::cerr << " Hint: If you are doing inference (forward only without backward)," << std::endl;
// std::cerr << " set save_for_backward=False in forward_sft() call." << std::endl;
// std::cerr << " Or increase max_cache_depth in MOESFTConfig." << std::endl;
// throw std::runtime_error("Forward cache stack overflow");
cache_stack_top_ = 0; // Wrap around (for inference only)
}
return cache_stack_[cache_stack_top_++];
}
ForwardCache pop_cache() {
if (cache_stack_top_ <= 0) {
std::cerr << "[KT-MOE ERROR] Forward cache stack underflow!" << std::endl;
std::cerr << " cache_stack_top_ = " << cache_stack_top_ << std::endl;
std::cerr << " Hint: Calling backward() without corresponding forward(save_for_backward=True)." << std::endl;
throw std::runtime_error("Forward cache stack underflow");
}
return cache_stack_[--cache_stack_top_];
}
void save_to_cache(ForwardCache& cache, int qlen, int k, const int64_t* expert_ids, const float* weights,
int activated_expert, const void* input) {
auto pool = config_.pool->get_subpool(tp_part_idx);
cache.qlen_cache = qlen;
cache.k_cache = k;
cache.activated_expert_cache = activated_expert;
// Copy routing information (small data, keep serial)
cache.expert_ids_cache.resize(qlen * k);
cache.weights_cache.resize(qlen * k);
std::copy(expert_ids, expert_ids + qlen * k, cache.expert_ids_cache.begin());
std::copy(weights, weights + qlen * k, cache.weights_cache.begin());
cache.m_local_num_cache = m_local_num_;
// Optimized: use memcpy for inner vector instead of scalar loop
for (int i = 0; i < qlen; i++) {
memcpy(cache.m_local_pos_cache[i].data(), m_local_pos_[i].data(), k * sizeof(int));
}
for (int i = 0; i < activated_expert; i++) {
cache.m_expert_id_map_cache[i] = m_expert_id_map_[i];
}
// Compute offsets using preallocated buffer (avoid heap allocation)
cache_offsets_[0] = 0;
for (int i = 0; i < activated_expert; i++) {
int expert_idx = m_expert_id_map_[i];
cache_offsets_[i + 1] = cache_offsets_[i] + m_local_num_[expert_idx];
}
// Parallel copy: input(1 task) + gate(N tasks) + up(N tasks) = 1 + 2N tasks
// This parallelizes the ~1.8MB input copy that was previously serial
int total_tasks = 1 + activated_expert * 2;
pool->do_work_stealing_job(
total_tasks, nullptr,
[this, &cache, input, qlen, activated_expert](int task_id) {
if (task_id == 0) {
// Task 0: copy input (~1.8MB for qlen=128, hidden=7168)
memcpy(cache.input_cache, input, qlen * config_.hidden_size * sizeof(ggml_bf16_t));
} else {
// Tasks 1..2N: copy gate and up outputs
int idx = task_id - 1;
bool do_up = idx % 2;
int i = idx / 2;
int expert_idx = m_expert_id_map_[i];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
size_t offset = cache_offsets_[i];
if (do_up) {
memcpy(cache.up_output_cache + offset * config_.intermediate_size, m_local_up_output_ptr_[expert_idx],
num_tokens * config_.intermediate_size * sizeof(ggml_bf16_t));
} else {
memcpy(cache.gate_output_cache + offset * config_.intermediate_size, m_local_gate_output_ptr_[expert_idx],
num_tokens * config_.intermediate_size * sizeof(ggml_bf16_t));
}
}
},
nullptr);
cache.valid = true;
}
/**
* @brief Save intermediate values AFTER activation for backward_down.
*
* Must be called after apply_activation() since m_local_gate_output_ptr_
* now contains silu(gate) * up (the intermediate value).
*
* Note: Uses cache_offsets_ computed by save_to_cache() - must be called after it.
*/
void save_intermediate_to_cache(ForwardCache& cache, int activated_expert) {
auto pool = config_.pool->get_subpool(tp_part_idx);
// Parallel memcpy (reuse cache_offsets_ from save_to_cache)
pool->do_work_stealing_job(
activated_expert, nullptr,
[this, &cache](int i) {
int expert_idx = m_expert_id_map_[i];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
// m_local_gate_output_ptr_ now contains intermediate (after activation: silu(gate) * up)
memcpy(cache.intermediate_cache + cache_offsets_[i] * config_.intermediate_size,
m_local_gate_output_ptr_[expert_idx], num_tokens * config_.intermediate_size * sizeof(ggml_bf16_t));
},
nullptr);
}
/**
* @brief Save down projection output for grad_weights computation.
*
* Must be called after down projection (and LoRA) but before weighted merge.
*
* Note: Uses cache_offsets_ computed by save_to_cache() - must be called after it.
*/
void save_down_output_to_cache(ForwardCache& cache, int activated_expert) {
auto pool = config_.pool->get_subpool(tp_part_idx);
// Expert-level parallelism: each task copies one expert's contiguous data block
// This maintains memory locality and cache efficiency
pool->do_work_stealing_job(
activated_expert, nullptr,
[this, &cache](int i) {
int expert_idx = m_expert_id_map_[i];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
ggml_bf16_t* src_ptr = m_local_down_output_ptr_[expert_idx];
memcpy(cache.down_output_cache + cache_offsets_[i] * config_.hidden_size, src_ptr,
num_tokens * config_.hidden_size * sizeof(ggml_bf16_t));
},
nullptr);
}
void backward_down(const ForwardCache& cache, const void* grad_output, void* grad_down_lora_a,
void* grad_down_lora_b) {
auto pool = config_.pool->get_subpool(tp_part_idx);
int activated_expert = cache.activated_expert_cache;
int qlen = cache.qlen_cache;
int k = cache.k_cache;
ggml_bf16_t* grad_down_a = (ggml_bf16_t*)grad_down_lora_a;
ggml_bf16_t* grad_down_b = (ggml_bf16_t*)grad_down_lora_b;
// Debug code commented out - Bug #15 verified fixed
// printf("[DEBUG ADDR backward_down] grad_intermediate_ = %p\n", (void*)grad_intermediate_);
// printf("[DEBUG ADDR backward_down] cache.gate_output_cache = %p\n", (void*)cache.gate_output_cache);
// printf("[DEBUG ADDR backward_down] cache.up_output_cache = %p\n", (void*)cache.up_output_cache);
// Initialize gradient intermediate buffer (parallelized)
{
size_t total_size =
(size_t)config_.max_len * config_.num_experts_per_tok * config_.intermediate_size * sizeof(ggml_bf16_t);
const int num_chunks = 8;
size_t chunk_size = (total_size + num_chunks - 1) / num_chunks;
pool->do_work_stealing_job(
num_chunks, nullptr,
[this, total_size, chunk_size](int i) {
size_t offset = i * chunk_size;
size_t size = std::min(chunk_size, total_size - offset);
if (size > 0) {
memset(reinterpret_cast<char*>(grad_intermediate_) + offset, 0, size);
}
},
nullptr);
}
// Scatter grad_output to per-expert buffers and compute gradients
pool->do_work_stealing_job(
activated_expert, nullptr,
[this, &cache, grad_output, grad_down_a, grad_down_b, qlen, k](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
// Collect gradients for this expert from grad_output
// grad_output is [qlen, hidden_size] in bf16, need to scatter based on routing
const ggml_bf16_t* grad_out = (const ggml_bf16_t*)grad_output;
std::vector<float> expert_grad_out(num_tokens * config_.hidden_size, 0.0f);
for (int i = 0; i < qlen; i++) {
for (int j = 0; j < k; j++) {
if (cache.expert_ids_cache[i * k + j] == expert_idx) {
int pos = cache.m_local_pos_cache[i][j];
float w = cache.weights_cache[i * k + j];
for (int h = 0; h < config_.hidden_size; h++) {
expert_grad_out[pos * config_.hidden_size + h] +=
GGML_BF16_TO_FP32(grad_out[i * config_.hidden_size + h]) * w;
}
}
}
}
// Get cached intermediate (after activation)
ggml_bf16_t* intermediate = cache.intermediate_cache; // Will use gate_output_cache after activation saved
// Compute grad w.r.t. intermediate: grad_intermediate = grad_output @ down_proj
// down_proj layout: [expert_num, hidden_size, intermediate_size]
// grad_output: [num_tokens, hidden_size], grad_intermediate: [num_tokens, intermediate_size]
// grad_intermediate[t, i] = sum_h grad_output[t, h] * down_proj[h, i]
{
const ggml_bf16_t* down_proj = (const ggml_bf16_t*)config_.down_proj;
size_t expert_offset = (size_t)expert_idx * config_.hidden_size * config_.intermediate_size;
// Compute offset into grad_intermediate_ for this expert
size_t grad_inter_offset = 0;
for (int e = 0; e < task_id; e++) {
grad_inter_offset += m_local_num_[m_expert_id_map_[e]];
}
grad_inter_offset *= config_.intermediate_size;
for (int t = 0; t < num_tokens; t++) {
for (int i = 0; i < config_.intermediate_size; i++) {
float sum = 0.0f;
for (int h = 0; h < config_.hidden_size; h++) {
float grad_out_val = expert_grad_out[t * config_.hidden_size + h];
float down_val = GGML_BF16_TO_FP32(down_proj[expert_offset + h * config_.intermediate_size + i]);
sum += grad_out_val * down_val;
}
grad_intermediate_[grad_inter_offset + t * config_.intermediate_size + i] = GGML_FP32_TO_BF16(sum);
}
}
}
// Skip LoRA gradient computation when SkipLoRA is true
if (!SkipLoRA && down_lora_a_ != nullptr && down_lora_b_ != nullptr) {
// Get expert's LoRA weights
size_t lora_a_offset = expert_idx * lora_rank_ * config_.intermediate_size;
size_t lora_b_offset = expert_idx * config_.hidden_size * lora_rank_;
ggml_bf16_t* expert_lora_a = down_lora_a_ + lora_a_offset;
ggml_bf16_t* expert_lora_b = down_lora_b_ + lora_b_offset;
// Bug #17c fix: Use cached intermediate (after activation), not gate_output_cache (before activation)
// The cache is stored in task order (activated expert order), need to compute offset
size_t cache_offset = 0;
for (int e = 0; e < task_id; e++) {
cache_offset += m_local_num_[m_expert_id_map_[e]];
}
const ggml_bf16_t* cached_intermediate =
cache.intermediate_cache + cache_offset * config_.intermediate_size;
// Gradient for LoRA B: grad_B = grad_output^T @ (intermediate @ lora_A^T) * scaling
// = (grad_output^T @ intermediate @ lora_A^T) * scaling
// Shape: [hidden_size, num_tokens] @ [num_tokens, lora_rank] → [hidden_size, lora_rank]
// First compute intermediate @ lora_A^T → [num_tokens, lora_rank]
std::vector<float> inter_proj(num_tokens * lora_rank_, 0.0f);
for (int t = 0; t < num_tokens; t++) {
for (int r = 0; r < lora_rank_; r++) {
float sum = 0.0f;
for (int i = 0; i < config_.intermediate_size; i++) {
// Use cached intermediate (gate_output after activation)
float inp = GGML_BF16_TO_FP32(cached_intermediate[t * config_.intermediate_size + i]);
float w = GGML_BF16_TO_FP32(expert_lora_a[r * config_.intermediate_size + i]);
sum += inp * w;
}
inter_proj[t * lora_rank_ + r] = sum;
}
}
// grad_B = grad_output^T @ inter_proj * scaling
// [hidden_size, num_tokens] @ [num_tokens, lora_rank] → [hidden_size, lora_rank]
for (int h = 0; h < config_.hidden_size; h++) {
for (int r = 0; r < lora_rank_; r++) {
float sum = 0.0f;
for (int t = 0; t < num_tokens; t++) {
sum += expert_grad_out[t * config_.hidden_size + h] * inter_proj[t * lora_rank_ + r];
}
// Accumulate gradient
size_t idx = lora_b_offset + h * lora_rank_ + r;
float cur = GGML_BF16_TO_FP32(grad_down_b[idx]);
cur += sum * lora_scaling_;
grad_down_b[idx] = GGML_FP32_TO_BF16(cur);
}
}
// Gradient for LoRA A: more complex, involves backprop through lora_B
// grad_A = (lora_B^T @ grad_output^T @ intermediate)^T * scaling
// = intermediate^T @ grad_output @ lora_B * scaling
// Shape: [intermediate_size, num_tokens] @ [num_tokens, hidden_size] @ [hidden_size, lora_rank]
// = [intermediate_size, lora_rank]
// First: grad_output @ lora_B → [num_tokens, lora_rank]
std::vector<float> grad_times_b(num_tokens * lora_rank_, 0.0f);
for (int t = 0; t < num_tokens; t++) {
for (int r = 0; r < lora_rank_; r++) {
float sum = 0.0f;
for (int h = 0; h < config_.hidden_size; h++) {
float g = expert_grad_out[t * config_.hidden_size + h];
float b = GGML_BF16_TO_FP32(expert_lora_b[h * lora_rank_ + r]);
sum += g * b;
}
grad_times_b[t * lora_rank_ + r] = sum;
}
}
// grad_A = intermediate^T @ grad_times_b * scaling
// [intermediate_size, num_tokens] @ [num_tokens, lora_rank] → [intermediate_size, lora_rank]
// But A is stored as [lora_rank, intermediate_size], so we compute for that layout
for (int r = 0; r < lora_rank_; r++) {
for (int i = 0; i < config_.intermediate_size; i++) {
float sum = 0.0f;
for (int t = 0; t < num_tokens; t++) {
// Bug #17a fix: Use cached_intermediate instead of m_local_gate_output_ptr_
float inter = GGML_BF16_TO_FP32(cached_intermediate[t * config_.intermediate_size + i]);
sum += inter * grad_times_b[t * lora_rank_ + r];
}
size_t idx_a = lora_a_offset + r * config_.intermediate_size + i;
float cur = GGML_BF16_TO_FP32(grad_down_a[idx_a]);
cur += sum * lora_scaling_;
grad_down_a[idx_a] = GGML_FP32_TO_BF16(cur);
}
}
}
},
nullptr);
}
/**
* @brief AMX-optimized backward pass for down projection.
*
* Optimizes the main GEMM: grad_intermediate = grad_output @ down_proj
* Using AMX mat_mul with down_backward_bb_ (transposed weight).
*
* LoRA gradient computation is kept as for-loop for now due to complexity
* and small matrix sizes involved.
*/
void backward_down_amx(const ForwardCache& cache, const void* grad_output, void* grad_down_lora_a,
void* grad_down_lora_b, int full_intermediate_size = 0,
float* fp32_grad_down_lora_b = nullptr) {
if (full_intermediate_size == 0) full_intermediate_size = config_.intermediate_size;
auto pool = config_.pool->get_subpool(tp_part_idx);
int activated_expert = cache.activated_expert_cache;
int qlen = cache.qlen_cache;
int k = cache.k_cache;
constexpr int kSmallBwdDirectQlen = 0;
constexpr int kSmallBwdDirectMaxTasks = 16;
auto direct_or_pool = [&](int count, auto&& fn) {
if (qlen <= kSmallBwdDirectQlen && count <= kSmallBwdDirectMaxTasks) {
for (int i = 0; i < count; i++) {
fn(i);
}
} else {
pool->do_work_stealing_job(count, nullptr, fn, nullptr);
}
};
ggml_bf16_t* grad_down_a = (ggml_bf16_t*)grad_down_lora_a;
ggml_bf16_t* grad_down_b = (ggml_bf16_t*)grad_down_lora_b;
// Ensure backward weights are prepared
assert(backward_weights_prepared_);
// =====================================================
// Bug-C Fix Step 2: Allocate backward buffers from shared pool
// =====================================================
constexpr size_t M_STEP = T::M_STEP;
auto align64 = [](size_t v) { return (v + 63) & (~(size_t)63); };
char* backward_ba_ptr = (char*)backward_ba_pool_;
char* backward_bc_ptr = (char*)backward_bc_pool_;
char* grad_output_bf16_ptr = (char*)grad_output_bf16_pool_;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) continue;
size_t local_max_m = ((m + M_STEP - 1) / M_STEP) * M_STEP;
// Allocate BufferA for grad_output
grad_output_ba_[expert_idx]->max_m = local_max_m;
grad_output_ba_[expert_idx]->set_data(backward_ba_ptr);
backward_ba_ptr += align64(T::BufferA::required_size(local_max_m, config_.hidden_size));
// Allocate BufferC for grad_intermediate
grad_intermediate_bc_[expert_idx]->max_m = local_max_m;
grad_intermediate_bc_[expert_idx]->set_data(backward_bc_ptr);
backward_bc_ptr += align64(T::BufferC::required_size(local_max_m, config_.intermediate_size));
// Allocate BF16 buffer for scattered grad_output
grad_output_bf16_ptr_[expert_idx] = (ggml_bf16_t*)grad_output_bf16_ptr;
grad_output_bf16_ptr += align64(local_max_m * config_.hidden_size * sizeof(ggml_bf16_t));
}
// NOTE: no full-buffer memset here; grad_intermediate_ is overwritten by to_mat() for active tokens.
// =====================================================
// Step 1: Zero per-expert grad_output buffers
// =====================================================
direct_or_pool(activated_expert, [this](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
memset(grad_output_bf16_ptr_[expert_idx], 0, num_tokens * config_.hidden_size * sizeof(ggml_bf16_t));
});
// =====================================================
// Step 2: Scatter grad_output to per-expert BF16 buffers
// =====================================================
{
const int hidden = config_.hidden_size;
const int hidden_vec_end = hidden & ~31;
direct_or_pool(qlen, [this, &cache, grad_output, k, hidden, hidden_vec_end](int token_id) {
const ggml_bf16_t* src_row = (const ggml_bf16_t*)grad_output + token_id * hidden;
for (int j = 0; j < k; j++) {
int expert_idx = cache.expert_ids_cache[token_id * k + j];
if (expert_idx < config_.num_gpu_experts || expert_idx >= config_.expert_num) {
continue;
}
if (m_local_num_[expert_idx] == 0) {
continue;
}
// Each token-route pair owns one unique local position within an expert buffer.
int pos = cache.m_local_pos_cache[token_id][j];
float w = cache.weights_cache[token_id * k + j];
ggml_bf16_t* dst_row = grad_output_bf16_ptr_[expert_idx] + pos * hidden;
__m512 w_vec = _mm512_set1_ps(w);
int h = 0;
for (; h < hidden_vec_end; h += 32) {
__m512 x0, x1, cur0, cur1;
avx512_32xbf16_to_32xfp32((__m512i*)(src_row + h), &x0, &x1);
avx512_32xbf16_to_32xfp32((__m512i*)(dst_row + h), &cur0, &cur1);
x0 = _mm512_mul_ps(x0, w_vec);
x1 = _mm512_mul_ps(x1, w_vec);
x0 = _mm512_add_ps(x0, cur0);
x1 = _mm512_add_ps(x1, cur1);
avx512_32xfp32_to_32xbf16(&x0, &x1, (__m512i*)(dst_row + h));
}
for (; h < hidden; h++) {
float cur = GGML_BF16_TO_FP32(dst_row[h]);
cur += GGML_BF16_TO_FP32(src_row[h]) * w;
dst_row[h] = GGML_FP32_TO_BF16(cur);
}
}
});
}
// =====================================================
// Step 3: Quantize scattered grad_output to BufferA
// =====================================================
direct_or_pool(activated_expert, [this](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
grad_output_ba_[expert_idx]->from_mat(num_tokens, grad_output_bf16_ptr_[expert_idx], 0, 1);
});
// =====================================================
// Step 3+4: AMX GEMM + to_mat (merged to use same ith/nth)
// grad_intermediate = grad_output @ down_proj
// Using: A @ B^T where A = grad_output, B = down_proj^T (stored in down_backward_bb_)
// m = num_tokens, n = intermediate_size, k = hidden_size
//
// BUG FIX: Previously Step 3 used (ith, nth) for mat_mul but Step 4 used (0, 1) for to_mat,
// which only output the first N_BLOCK columns. Now merged to use same (ith, nth).
// =====================================================
int nth = T::recommended_nth(config_.intermediate_size);
// Pre-compute offsets for each expert in both token units and BF16 matrix units.
std::vector<size_t> expert_offsets(activated_expert);
std::vector<size_t> expert_token_offsets(activated_expert);
{
size_t offset = 0;
for (int i = 0; i < activated_expert; i++) {
expert_token_offsets[i] = offset;
expert_offsets[i] = offset * config_.intermediate_size;
offset += m_local_num_[m_expert_id_map_[i]];
}
}
pool->do_work_stealing_job(
nth * activated_expert, [](int _) { T::config(); },
[this, nth, &expert_offsets](int task_id) {
int task_idx = task_id / nth; // Which expert (0 to activated_expert-1)
int expert_idx = m_expert_id_map_[task_idx];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
auto& ba = grad_output_ba_[expert_idx];
auto& bb = down_backward_bb_[expert_idx];
auto& bc = grad_intermediate_bc_[expert_idx];
// mat_mul: [m, hidden_size] @ [intermediate_size, hidden_size]^T = [m, intermediate_size]
amx::mat_mul(m, config_.intermediate_size, config_.hidden_size, ba, bb, bc, ith, nth);
// to_mat: Convert BufferC to BF16 - use same ith, nth as mat_mul!
bc->to_mat(m, grad_intermediate_ + expert_offsets[task_idx], ith, nth);
},
nullptr);
// =====================================================
// Step 3.5: Add LoRA contribution to grad_intermediate (AVX512)
// grad_intermediate += grad_output @ down_lora_B @ down_lora_A * scaling
// This is needed for correct backward through activation to gate/up
// =====================================================
if (down_lora_a_ != nullptr && down_lora_b_ != nullptr && down_lora_b_transposed_ != nullptr) {
const int hidden = config_.hidden_size;
const int inter_size = config_.intermediate_size;
const int rank = lora_rank_;
const float scale = lora_scaling_;
const int nth = 4;
direct_or_pool(nth * activated_expert, [this, &expert_offsets, &expert_token_offsets, hidden, inter_size, rank,
scale, nth](int task_id) {
int expert_idx = m_expert_id_map_[task_id / nth];
int ith = task_id % nth;
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
// Divide tokens among threads
int tokens_per_thread = (num_tokens + nth - 1) / nth;
int t_start = ith * tokens_per_thread;
int t_end = std::min(t_start + tokens_per_thread, num_tokens);
if (t_start >= num_tokens) return;
// Get expert's LoRA weights (use transposed layout for lora_B)
size_t lora_a_offset = (size_t)expert_idx * rank * inter_size;
size_t lora_b_t_offset = (size_t)expert_idx * rank * hidden; // Transposed: [rank, hidden]
const ggml_bf16_t* expert_lora_a = down_lora_a_ + lora_a_offset;
const ggml_bf16_t* expert_lora_b_t = down_lora_b_transposed_ + lora_b_t_offset;
const ggml_bf16_t* expert_grad = grad_output_bf16_ptr_[expert_idx];
ggml_bf16_t* grad_inter = grad_intermediate_ + expert_offsets[task_id / nth];
float* grad_times_b = lora_grad_times_b_pool_ + (expert_token_offsets[task_id / nth] + t_start) * rank;
int local_num_tokens = t_end - t_start;
// Step 1: grad_output @ down_lora_B_transposed -> [local_num_tokens, rank]
// Using optimized kernel with transposed weight layout [rank, hidden]
avx::lora_backward_matmul_transposed(expert_grad + t_start * hidden, // [local_num_tokens, hidden] BF16
expert_lora_b_t, // [rank, hidden] BF16 (transposed)
grad_times_b, // [local_num_tokens, rank] FP32
local_num_tokens, hidden, rank);
// Step 2: grad_times_b @ down_lora_A -> [local_num_tokens, inter_size] (AVX512)
// Using optimized kernel with weight layout [rank, inter_size]
avx::lora_fp32_bf16_fused_add_wt(grad_times_b, // [local_num_tokens, rank] FP32
expert_lora_a, // [rank, inter_size] BF16
grad_inter + t_start * inter_size, // [local_num_tokens, inter_size] BF16
local_num_tokens, rank, inter_size, scale);
});
}
// =====================================================
// Step 5: LoRA gradient computation (parallelized across blocks)
// Skip when SkipLoRA is true (only compute grad_input, not LoRA weight gradients)
// =====================================================
if (!SkipLoRA) {
struct LoraGradBuf {
int expert_idx = -1;
int num_tokens = 0;
size_t lora_a_offset = 0; // copy-type: expert_idx * rank * full_I (stride uses full_intermediate_size)
size_t lora_b_offset = 0; // reduce-type: task_id * hidden * rank (sparse FP32)
const ggml_bf16_t* cached_intermediate = nullptr;
const float* cached_down_lora_u = nullptr;
const ggml_bf16_t* expert_grad_bf16 = nullptr;
const float* grad_times_b = nullptr;
};
const int hidden = config_.hidden_size;
const int inter_size = config_.intermediate_size;
const int rank = lora_rank_;
const int down_a_row_stride = full_intermediate_size; // full I for copy-type direct write
const size_t grad_b_elems = static_cast<size_t>(hidden) * rank;
const size_t grad_a_elems = static_cast<size_t>(inter_size) * rank;
const bool use_fp32_down_b = (fp32_grad_down_lora_b != nullptr);
std::vector<LoraGradBuf> lora_grad_bufs(activated_expert);
int max_down_lora_tokens = 0;
// Initialize per-expert buffers (sequential - cheaper than parallel job dispatch)
for (int task_id = 0; task_id < activated_expert; task_id++) {
LoraGradBuf& buf = lora_grad_bufs[task_id];
int expert_idx = m_expert_id_map_[task_id];
int num_tokens = m_local_num_[expert_idx];
buf.expert_idx = expert_idx;
buf.num_tokens = num_tokens;
if (num_tokens == 0) continue;
buf.lora_a_offset = static_cast<size_t>(expert_idx) * rank * down_a_row_stride; // copy-type: full_I stride
buf.lora_b_offset = use_fp32_down_b ? static_cast<size_t>(task_id) * hidden * rank // sparse FP32 indexing
: static_cast<size_t>(expert_idx) * hidden * rank; // legacy dense BF16
buf.expert_grad_bf16 = grad_output_bf16_ptr_[expert_idx];
buf.grad_times_b = lora_grad_times_b_pool_ + expert_token_offsets[task_id] * rank;
size_t token_offset = expert_token_offsets[task_id];
buf.cached_intermediate = cache.intermediate_cache + token_offset * inter_size;
buf.cached_down_lora_u = cache.down_lora_u_cache + token_offset * rank;
max_down_lora_tokens = std::max(max_down_lora_tokens, num_tokens);
}
const float scale = lora_scaling_;
constexpr int kDownGradABBlockedThreshold = 4096;
if (max_down_lora_tokens >= kDownGradABBlockedThreshold) {
struct GradABBlockTask {
int expert_task = -1;
int start = 0;
int end = 0;
bool is_grad_b = false;
};
constexpr int kDownGradBTile = 256;
constexpr int kDownGradATile = 256;
constexpr int kMaxDownGradTile = kDownGradBTile > kDownGradATile ? kDownGradBTile : kDownGradATile;
std::vector<GradABBlockTask> grad_ab_tasks;
grad_ab_tasks.reserve(
static_cast<size_t>(activated_expert) *
(((hidden + kDownGradBTile - 1) / kDownGradBTile) + ((inter_size + kDownGradATile - 1) / kDownGradATile)));
for (int task_id = 0; task_id < activated_expert; task_id++) {
const LoraGradBuf& buf = lora_grad_bufs[task_id];
if (buf.num_tokens == 0) continue;
for (int h = 0; h < hidden; h += kDownGradBTile) {
grad_ab_tasks.push_back({task_id, h, std::min(h + kDownGradBTile, hidden), true});
}
for (int i = 0; i < inter_size; i += kDownGradATile) {
grad_ab_tasks.push_back({task_id, i, std::min(i + kDownGradATile, inter_size), false});
}
}
pool->do_work_stealing_job(
static_cast<int>(grad_ab_tasks.size()), nullptr,
[&, hidden, inter_size, rank, scale](int task_id) {
const GradABBlockTask& task = grad_ab_tasks[task_id];
const LoraGradBuf& buf = lora_grad_bufs[task.expert_task];
if (buf.num_tokens == 0) return;
const int block_len = task.end - task.start;
float* accum = get_lora_fp32_buffer(static_cast<size_t>(kMaxDownGradTile) * rank);
memset(accum, 0, static_cast<size_t>(block_len) * rank * sizeof(float));
if (task.is_grad_b) {
for (int t = 0; t < buf.num_tokens; t++) {
const ggml_bf16_t* grad_row_bf16 =
buf.expert_grad_bf16 + static_cast<size_t>(t) * hidden + task.start;
const float* inter_proj = buf.cached_down_lora_u + static_cast<size_t>(t) * rank;
if (rank == 8) {
__m256 inter_proj_vec = _mm256_loadu_ps(inter_proj);
for (int hh = 0; hh < block_len; hh++) {
float g = GGML_BF16_TO_FP32(grad_row_bf16[hh]);
if (g == 0.0f) continue;
float* out = accum + static_cast<size_t>(hh) * rank;
__m256 acc = _mm256_loadu_ps(out);
acc = _mm256_fmadd_ps(_mm256_set1_ps(g), inter_proj_vec, acc);
_mm256_storeu_ps(out, acc);
}
} else {
for (int hh = 0; hh < block_len; hh++) {
float g = GGML_BF16_TO_FP32(grad_row_bf16[hh]);
if (g == 0.0f) continue;
float* out = accum + static_cast<size_t>(hh) * rank;
for (int r = 0; r < rank; r++) {
out[r] += g * inter_proj[r];
}
}
}
}
if (use_fp32_down_b) {
for (int hh = 0; hh < block_len; hh++) {
float* fp32_out =
fp32_grad_down_lora_b + buf.lora_b_offset + static_cast<size_t>(task.start + hh) * rank;
float* acc_row = accum + static_cast<size_t>(hh) * rank;
for (int r = 0; r < rank; r++) {
fp32_out[r] += acc_row[r] * scale;
}
}
} else {
for (int hh = 0; hh < block_len; hh++) {
ggml_bf16_t* out = grad_down_b + buf.lora_b_offset + static_cast<size_t>(task.start + hh) * rank;
float* acc_row = accum + static_cast<size_t>(hh) * rank;
for (int r = 0; r < rank; r++) {
float cur = GGML_BF16_TO_FP32(out[r]);
cur += acc_row[r] * scale;
out[r] = GGML_FP32_TO_BF16(cur);
}
}
}
} else {
for (int t = 0; t < buf.num_tokens; t++) {
const ggml_bf16_t* inter_row_bf16 =
buf.cached_intermediate + static_cast<size_t>(t) * inter_size + task.start;
const float* grad_times_b = buf.grad_times_b + static_cast<size_t>(t) * rank;
if (rank == 8) {
__m256 grad_times_b_vec = _mm256_loadu_ps(grad_times_b);
for (int ii = 0; ii < block_len; ii++) {
float x = GGML_BF16_TO_FP32(inter_row_bf16[ii]);
if (x == 0.0f) continue;
float* out = accum + static_cast<size_t>(ii) * rank;
__m256 acc = _mm256_loadu_ps(out);
acc = _mm256_fmadd_ps(_mm256_set1_ps(x), grad_times_b_vec, acc);
_mm256_storeu_ps(out, acc);
}
} else {
for (int ii = 0; ii < block_len; ii++) {
float x = GGML_BF16_TO_FP32(inter_row_bf16[ii]);
if (x == 0.0f) continue;
float* out = accum + static_cast<size_t>(ii) * rank;
for (int r = 0; r < rank; r++) {
out[r] += x * grad_times_b[r];
}
}
}
}
for (int r = 0; r < rank; r++) {
ggml_bf16_t* grad_row =
grad_down_a + buf.lora_a_offset + static_cast<size_t>(r) * down_a_row_stride + task.start;
float* acc_row = accum + static_cast<size_t>(r);
for (int ii = 0; ii < block_len; ii++) {
float cur = GGML_BF16_TO_FP32(grad_row[ii]);
cur += acc_row[static_cast<size_t>(ii) * rank] * scale;
grad_row[ii] = GGML_FP32_TO_BF16(cur);
}
}
}
},
nullptr);
} else {
struct LoraGradTask {
int expert_task = -1;
int t_start = 0;
int t_end = 0;
};
std::vector<LoraGradTask> lora_grad_tasks;
float* grad_b_accum_all = down_lora_grad_b_accum_pool_;
float* grad_a_accum_all = down_lora_grad_a_accum_pool_;
if (activated_expert > 0) {
std::memset(down_lora_grad_accum_initialized_.data(), 0, static_cast<size_t>(activated_expert));
}
int total_task_count = 0;
for (int task_id = 0; task_id < activated_expert; task_id++) {
const LoraGradBuf& buf = lora_grad_bufs[task_id];
const int token_tile = buf.num_tokens <= 128 ? 32 : 64;
total_task_count += (buf.num_tokens + token_tile - 1) / token_tile;
}
lora_grad_tasks.reserve(total_task_count);
for (int task_id = 0; task_id < activated_expert; task_id++) {
const LoraGradBuf& buf = lora_grad_bufs[task_id];
const int token_tile = buf.num_tokens <= 128 ? 32 : 64;
for (int t = 0; t < buf.num_tokens; t += token_tile) {
lora_grad_tasks.push_back({task_id, t, std::min(t + token_tile, buf.num_tokens)});
}
}
if (!lora_grad_tasks.empty()) {
direct_or_pool(static_cast<int>(lora_grad_tasks.size()), [&, hidden, inter_size, rank, grad_b_elems,
grad_a_elems](int task_id) {
const LoraGradTask& task = lora_grad_tasks[task_id];
LoraGradBuf& buf = lora_grad_bufs[task.expert_task];
if (buf.num_tokens == 0) return;
const int hidden_vec_end = hidden & ~31;
const int inter_vec_end = inter_size & ~31;
size_t scratch_elems = grad_b_elems + grad_a_elems + hidden + inter_size;
float* scratch = get_lora_fp32_buffer(scratch_elems);
float* grad_b_local = scratch;
float* grad_a_local = grad_b_local + grad_b_elems;
float* grad_row_fp32 = grad_a_local + grad_a_elems;
float* inter_row_fp32 = grad_row_fp32 + hidden;
memset(grad_b_local, 0, (grad_b_elems + grad_a_elems) * sizeof(float));
for (int t = task.t_start; t < task.t_end; t++) {
const ggml_bf16_t* grad_row_bf16 = buf.expert_grad_bf16 + static_cast<size_t>(t) * hidden;
const ggml_bf16_t* inter_row_bf16 = buf.cached_intermediate + static_cast<size_t>(t) * inter_size;
int h = 0;
for (; h < hidden_vec_end; h += 32) {
__m512 g0, g1;
avx512_32xbf16_to_32xfp32((__m512i*)(grad_row_bf16 + h), &g0, &g1);
_mm512_storeu_ps(grad_row_fp32 + h, g0);
_mm512_storeu_ps(grad_row_fp32 + h + 16, g1);
}
for (; h < hidden; h++) {
grad_row_fp32[h] = GGML_BF16_TO_FP32(grad_row_bf16[h]);
}
int i = 0;
for (; i < inter_vec_end; i += 32) {
__m512 x0, x1;
avx512_32xbf16_to_32xfp32((__m512i*)(inter_row_bf16 + i), &x0, &x1);
_mm512_storeu_ps(inter_row_fp32 + i, x0);
_mm512_storeu_ps(inter_row_fp32 + i + 16, x1);
}
for (; i < inter_size; i++) {
inter_row_fp32[i] = GGML_BF16_TO_FP32(inter_row_bf16[i]);
}
const float* inter_proj = buf.cached_down_lora_u + static_cast<size_t>(t) * rank;
const float* grad_times_b = buf.grad_times_b + static_cast<size_t>(t) * rank;
if (rank == 8) {
__m256 inter_proj_vec = _mm256_loadu_ps(inter_proj);
for (int hh = 0; hh < hidden; hh++) {
float g = grad_row_fp32[hh];
if (g == 0.0f) continue;
float* out = grad_b_local + static_cast<size_t>(hh) * rank;
__m256 acc = _mm256_loadu_ps(out);
acc = _mm256_fmadd_ps(_mm256_set1_ps(g), inter_proj_vec, acc);
_mm256_storeu_ps(out, acc);
}
__m256 grad_times_b_vec = _mm256_loadu_ps(grad_times_b);
for (int ii = 0; ii < inter_size; ii++) {
float x = inter_row_fp32[ii];
if (x == 0.0f) continue;
float* out = grad_a_local + static_cast<size_t>(ii) * rank;
__m256 acc = _mm256_loadu_ps(out);
acc = _mm256_fmadd_ps(_mm256_set1_ps(x), grad_times_b_vec, acc);
_mm256_storeu_ps(out, acc);
}
} else {
for (int hh = 0; hh < hidden; hh++) {
float g = grad_row_fp32[hh];
if (g == 0.0f) continue;
float* out = grad_b_local + static_cast<size_t>(hh) * rank;
for (int r = 0; r < rank; r++) {
out[r] += g * inter_proj[r];
}
}
for (int ii = 0; ii < inter_size; ii++) {
float x = inter_row_fp32[ii];
if (x == 0.0f) continue;
float* out = grad_a_local + static_cast<size_t>(ii) * rank;
for (int r = 0; r < rank; r++) {
out[r] += x * grad_times_b[r];
}
}
}
}
std::lock_guard<std::mutex> lock(down_lora_grad_mutexes_[task.expert_task]);
float* grad_b_global = grad_b_accum_all + static_cast<size_t>(task.expert_task) * grad_b_elems;
float* grad_a_global = grad_a_accum_all + static_cast<size_t>(task.expert_task) * grad_a_elems;
if (!down_lora_grad_accum_initialized_[task.expert_task]) {
std::memcpy(grad_b_global, grad_b_local, grad_b_elems * sizeof(float));
std::memcpy(grad_a_global, grad_a_local, grad_a_elems * sizeof(float));
down_lora_grad_accum_initialized_[task.expert_task] = 1;
} else if (rank == 8) {
for (size_t off = 0; off < grad_b_elems; off += rank) {
__m256 acc = _mm256_loadu_ps(grad_b_global + off);
acc = _mm256_add_ps(acc, _mm256_loadu_ps(grad_b_local + off));
_mm256_storeu_ps(grad_b_global + off, acc);
}
for (size_t off = 0; off < grad_a_elems; off += rank) {
__m256 acc = _mm256_loadu_ps(grad_a_global + off);
acc = _mm256_add_ps(acc, _mm256_loadu_ps(grad_a_local + off));
_mm256_storeu_ps(grad_a_global + off, acc);
}
} else {
for (size_t off = 0; off < grad_b_elems; off++) {
grad_b_global[off] += grad_b_local[off];
}
for (size_t off = 0; off < grad_a_elems; off++) {
grad_a_global[off] += grad_a_local[off];
}
}
});
constexpr int kDownGradBTile = 512;
constexpr int kDownGradATile = 512;
int grad_b_blocks = (hidden + kDownGradBTile - 1) / kDownGradBTile;
int grad_a_blocks = (inter_size + kDownGradATile - 1) / kDownGradATile;
pool->do_work_stealing_job(
activated_expert * grad_b_blocks, nullptr,
[&, hidden, rank, scale, grad_b_elems, grad_b_blocks, use_fp32_down_b](int task_id) {
int expert_task = task_id / grad_b_blocks;
int block_idx = task_id % grad_b_blocks;
LoraGradBuf& buf = lora_grad_bufs[expert_task];
if (buf.num_tokens == 0) return;
int h_start = block_idx * kDownGradBTile;
int h_end = std::min(hidden, h_start + kDownGradBTile);
float* grad_b_global = grad_b_accum_all + static_cast<size_t>(expert_task) * grad_b_elems;
if (use_fp32_down_b) {
for (int hh = h_start; hh < h_end; hh++) {
float* fp32_out = fp32_grad_down_lora_b + buf.lora_b_offset + static_cast<size_t>(hh) * rank;
float* acc_row = grad_b_global + static_cast<size_t>(hh) * rank;
for (int r = 0; r < rank; r++) {
fp32_out[r] += acc_row[r] * scale;
}
}
} else {
for (int hh = h_start; hh < h_end; hh++) {
ggml_bf16_t* out = grad_down_b + buf.lora_b_offset + static_cast<size_t>(hh) * rank;
float* acc_row = grad_b_global + static_cast<size_t>(hh) * rank;
for (int r = 0; r < rank; r++) {
float cur = GGML_BF16_TO_FP32(out[r]);
cur += acc_row[r] * scale;
out[r] = GGML_FP32_TO_BF16(cur);
}
}
}
},
nullptr);
pool->do_work_stealing_job(
activated_expert * grad_a_blocks, nullptr,
[&, inter_size, rank, scale, grad_a_elems, grad_a_blocks, down_a_row_stride](int task_id) {
int expert_task = task_id / grad_a_blocks;
int block_idx = task_id % grad_a_blocks;
LoraGradBuf& buf = lora_grad_bufs[expert_task];
if (buf.num_tokens == 0) return;
int i_start = block_idx * kDownGradATile;
int i_end = std::min(inter_size, i_start + kDownGradATile);
float* grad_a_global = grad_a_accum_all + static_cast<size_t>(expert_task) * grad_a_elems;
for (int r = 0; r < rank; r++) {
ggml_bf16_t* grad_row =
grad_down_a + buf.lora_a_offset + static_cast<size_t>(r) * down_a_row_stride + i_start;
for (int ii = i_start; ii < i_end; ii++) {
float cur = GGML_BF16_TO_FP32(grad_row[ii - i_start]);
cur += grad_a_global[static_cast<size_t>(ii) * rank + r] * scale;
grad_row[ii - i_start] = GGML_FP32_TO_BF16(cur);
}
}
},
nullptr);
}
}
}
}
void backward_activation(const ForwardCache& cache) {
auto pool = config_.pool->get_subpool(tp_part_idx);
int activated_expert = cache.activated_expert_cache;
int qlen = cache.qlen_cache;
constexpr int kSmallBwdDirectQlen = 0;
constexpr int kSmallBwdDirectMaxTasks = 16;
auto direct_or_pool = [&](int count, auto&& fn) {
if (qlen <= kSmallBwdDirectQlen && count <= kSmallBwdDirectMaxTasks) {
for (int i = 0; i < count; i++) {
fn(i);
}
} else {
pool->do_work_stealing_job(count, nullptr, fn, nullptr);
}
};
// // DEBUG: Check cache values for NaN at the beginning
// {
// bool gate_nan = false, up_nan = false;
// size_t total_elems = 0;
// for (int i = 0; i < activated_expert; i++) {
// total_elems += m_local_num_[m_expert_id_map_[i]] * config_.intermediate_size;
// }
// for (size_t i = 0; i < total_elems && (!gate_nan || !up_nan); i++) {
// float g = GGML_BF16_TO_FP32(cache.gate_output_cache[i]);
// float u = GGML_BF16_TO_FP32(cache.up_output_cache[i]);
// if (std::isnan(g) || std::isinf(g)) gate_nan = true;
// if (std::isnan(u) || std::isinf(u)) up_nan = true;
// }
// if (gate_nan || up_nan) {
// printf("[NaN DEBUG L%d] Cache has NaN BEFORE backward_activation: gate=%s, up=%s\n",
// config_.layer_idx, gate_nan ? "NaN" : "OK", up_nan ? "NaN" : "OK");
// }
// }
// SiLU backward:
// y = silu(gate) * up = gate * sigmoid(gate) * up
// dy/d(gate) = sigmoid(gate) * (1 + gate * (1 - sigmoid(gate))) * up
// dy/d(up) = silu(gate) = gate * sigmoid(gate)
size_t cache_offset = 0;
direct_or_pool(activated_expert, [this, &cache, &cache_offset](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
// Get cached gate and up outputs (before activation)
// Need to compute offset into cache
size_t offset = 0;
for (int i = 0; i < task_id; i++) {
offset += m_local_num_[m_expert_id_map_[i]];
}
ggml_bf16_t* gate_output = cache.gate_output_cache + offset * config_.intermediate_size;
ggml_bf16_t* up_output = cache.up_output_cache + offset * config_.intermediate_size;
ggml_bf16_t* grad_inter = grad_intermediate_ + offset * config_.intermediate_size;
ggml_bf16_t* grad_gate = grad_gate_output_ + offset * config_.intermediate_size;
ggml_bf16_t* grad_up = grad_up_output_ + offset * config_.intermediate_size;
// Debug code commented out - Bug #15 verified fixed
// if (task_id == 0) {
// printf("[DEBUG backward_activation] task_id=0, expert_idx=%d, num_tokens=%d, offset=%zu\n", expert_idx,
// num_tokens, offset);
// printf("[DEBUG] gate_output[0..7] = ");
// for (int dbg = 0; dbg < 8 && dbg < num_tokens * config_.intermediate_size; dbg++) {
// printf("%.4f ", GGML_BF16_TO_FP32(gate_output[dbg]));
// }
// printf("\n");
// printf("[DEBUG] up_output[0..7] = ");
// for (int dbg = 0; dbg < 8 && dbg < num_tokens * config_.intermediate_size; dbg++) {
// printf("%.4f ", GGML_BF16_TO_FP32(up_output[dbg]));
// }
// printf("\n");
// printf("[DEBUG] grad_inter[0..7] = ");
// for (int dbg = 0; dbg < 8 && dbg < num_tokens * config_.intermediate_size; dbg++) {
// printf("%.4f ", GGML_BF16_TO_FP32(grad_inter[dbg]));
// }
// printf("\n");
// }
int total = num_tokens * config_.intermediate_size;
int i = 0;
// AVX512: process 32 BF16 elements (2×16 FP32) per iteration
__m512 one = _mm512_set1_ps(1.0f);
for (; i + 32 <= total; i += 32) {
__m512 g0, g1, u0, u1, gi0, gi1;
avx512_32xbf16_to_32xfp32((__m512i*)(gate_output + i), &g0, &g1);
avx512_32xbf16_to_32xfp32((__m512i*)(up_output + i), &u0, &u1);
avx512_32xbf16_to_32xfp32((__m512i*)(grad_inter + i), &gi0, &gi1);
// First 16: sigmoid, silu derivative, gradients
__m512 exp0 = avx512_exp_ps(_mm512_sub_ps(_mm512_setzero_ps(), g0));
__m512 sig0 = _mm512_div_ps(one, _mm512_add_ps(one, exp0));
__m512 silu0 = _mm512_mul_ps(g0, sig0);
__m512 dsilu0 = _mm512_mul_ps(sig0, _mm512_fmadd_ps(g0, _mm512_sub_ps(one, sig0), one));
__m512 gg0 = _mm512_mul_ps(_mm512_mul_ps(gi0, u0), dsilu0);
__m512 gu0 = _mm512_mul_ps(gi0, silu0);
// Second 16: same computation
__m512 exp1 = avx512_exp_ps(_mm512_sub_ps(_mm512_setzero_ps(), g1));
__m512 sig1 = _mm512_div_ps(one, _mm512_add_ps(one, exp1));
__m512 silu1 = _mm512_mul_ps(g1, sig1);
__m512 dsilu1 = _mm512_mul_ps(sig1, _mm512_fmadd_ps(g1, _mm512_sub_ps(one, sig1), one));
__m512 gg1 = _mm512_mul_ps(_mm512_mul_ps(gi1, u1), dsilu1);
__m512 gu1 = _mm512_mul_ps(gi1, silu1);
avx512_32xfp32_to_32xbf16(&gg0, &gg1, (__m512i*)(grad_gate + i));
avx512_32xfp32_to_32xbf16(&gu0, &gu1, (__m512i*)(grad_up + i));
}
// Scalar tail
for (; i < total; i++) {
float g_val = GGML_BF16_TO_FP32(gate_output[i]);
float u_val = GGML_BF16_TO_FP32(up_output[i]);
float sigmoid_val = 1.0f / (1.0f + expf(-g_val));
float silu_val = g_val * sigmoid_val;
float grad_i_val = GGML_BF16_TO_FP32(grad_inter[i]);
grad_gate[i] = GGML_FP32_TO_BF16(grad_i_val * u_val * sigmoid_val * (1.0f + g_val * (1.0f - sigmoid_val)));
grad_up[i] = GGML_FP32_TO_BF16(grad_i_val * silu_val);
}
});
}
/**
* @brief AMX-optimized backward pass for gate and up projections.
*
* Uses AMX GEMM for base weight contribution and LoRA grad_input. LoRA weight gradients
* remain small for-loops.
*/
void backward_gate_up_amx(const ForwardCache& cache, void* grad_input, void* grad_gate_lora_a, void* grad_gate_lora_b,
void* grad_up_lora_a, void* grad_up_lora_b, int full_intermediate_size = 0,
float* fp32_grad_gate_lora_a = nullptr, float* fp32_grad_up_lora_a = nullptr) {
if (full_intermediate_size == 0) full_intermediate_size = config_.intermediate_size;
auto pool = config_.pool->get_subpool(tp_part_idx);
int activated_expert = cache.activated_expert_cache;
int qlen = cache.qlen_cache;
int k = cache.k_cache;
constexpr int kSmallBwdDirectQlen = 0;
constexpr int kSmallBwdDirectMaxTasks = 16;
auto direct_or_pool = [&](int count, auto&& fn) {
if (qlen <= kSmallBwdDirectQlen && count <= kSmallBwdDirectMaxTasks) {
for (int i = 0; i < count; i++) {
fn(i);
}
} else {
pool->do_work_stealing_job(count, nullptr, fn, nullptr);
}
};
ggml_bf16_t* grad_gate_a = (ggml_bf16_t*)grad_gate_lora_a;
ggml_bf16_t* grad_gate_b = (ggml_bf16_t*)grad_gate_lora_b;
ggml_bf16_t* grad_up_a = (ggml_bf16_t*)grad_up_lora_a;
ggml_bf16_t* grad_up_b = (ggml_bf16_t*)grad_up_lora_b;
assert(backward_weights_prepared_);
if (gate_lora_a_ != nullptr && gate_lora_b_ != nullptr) {
prepare_lora_backward_weights();
}
// =====================================================
// Bug-C Fix Step 2: Allocate backward buffers from shared pool
// Note: backward_down_amx already allocated grad_output_ba_ and grad_intermediate_bc_
// Here we need grad_gate_up_bc_ which uses the remaining part of backward_bc_pool_
// =====================================================
constexpr size_t M_STEP = T::M_STEP;
auto align64 = [](size_t v) { return (v + 63) & (~(size_t)63); };
// Calculate offset after grad_intermediate_bc_ allocations
size_t grad_intermediate_total = 0;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
size_t local_max_m = ((m_local_num_[expert_idx] + M_STEP - 1) / M_STEP) * M_STEP;
grad_intermediate_total += align64(T::BufferC::required_size(local_max_m, config_.intermediate_size));
}
char* grad_gate_up_bc_ptr = (char*)backward_bc_pool_ + grad_intermediate_total;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) continue;
size_t local_max_m = ((m + M_STEP - 1) / M_STEP) * M_STEP;
// Allocate BufferC for grad_gate_up
grad_gate_up_bc_[expert_idx]->max_m = local_max_m;
grad_gate_up_bc_[expert_idx]->set_data(grad_gate_up_bc_ptr);
grad_gate_up_bc_ptr += align64(T::BufferC::required_size(local_max_m, config_.hidden_size));
}
// Allocate LoRA intermediate buffers from shared pools (for LoRA backward pass)
char* lora_ba_ptr = (char*)lora_ba_pool_;
char* lora_bc_inter_ptr = (char*)lora_bc_inter_pool_;
char* bf16_inter_ptr = (char*)lora_intermediate_bf16_pool_;
for (int task_id = 0; task_id < activated_expert; task_id++) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) continue;
size_t local_max_m = ((m + M_STEP - 1) / M_STEP) * M_STEP;
// BufferA for LoRA intermediate (gate)
lora_gate_intermediate_ba_[expert_idx]->max_m = local_max_m;
lora_gate_intermediate_ba_[expert_idx]->set_data(lora_ba_ptr);
lora_ba_ptr += align64(T::BufferA::required_size(local_max_m, padded_lora_rank_));
// BufferA for LoRA intermediate (up)
lora_up_intermediate_ba_[expert_idx]->max_m = local_max_m;
lora_up_intermediate_ba_[expert_idx]->set_data(lora_ba_ptr);
lora_ba_ptr += align64(T::BufferA::required_size(local_max_m, padded_lora_rank_));
// BufferC for LoRA step 1 output (gate)
lora_gate_intermediate_bc_[expert_idx]->max_m = local_max_m;
lora_gate_intermediate_bc_[expert_idx]->set_data(lora_bc_inter_ptr);
lora_bc_inter_ptr += align64(T::BufferC::required_size(local_max_m, padded_lora_rank_));
// BufferC for LoRA step 1 output (up)
lora_up_intermediate_bc_[expert_idx]->max_m = local_max_m;
lora_up_intermediate_bc_[expert_idx]->set_data(lora_bc_inter_ptr);
lora_bc_inter_ptr += align64(T::BufferC::required_size(local_max_m, padded_lora_rank_));
// BF16 intermediate pointers (gate)
lora_gate_intermediate_ptr_[expert_idx] = (ggml_bf16_t*)bf16_inter_ptr;
bf16_inter_ptr += align64(local_max_m * padded_lora_rank_ * sizeof(ggml_bf16_t));
// BF16 intermediate pointers (up)
lora_up_intermediate_ptr_[expert_idx] = (ggml_bf16_t*)bf16_inter_ptr;
bf16_inter_ptr += align64(local_max_m * padded_lora_rank_ * sizeof(ggml_bf16_t));
}
// Offsets into contiguous grad_gate/up buffers
std::vector<size_t> expert_offsets(activated_expert);
{
size_t offset = 0;
for (int i = 0; i < activated_expert; i++) {
expert_offsets[i] = offset;
offset += m_local_num_[m_expert_id_map_[i]];
}
}
auto scatter_to_grad_input = [&](float scale) {
ggml_bf16_t* grad_input_bf16 = (ggml_bf16_t*)grad_input;
const int hidden = config_.hidden_size;
const int hidden_vec_end = hidden & ~31;
const __m512 scale_vec = _mm512_set1_ps(scale);
direct_or_pool(qlen, [&, scale, hidden, hidden_vec_end, scale_vec](int token_id) {
ggml_bf16_t* dst = grad_input_bf16 + token_id * hidden;
for (int j = 0; j < k; j++) {
int expert_idx = cache.expert_ids_cache[token_id * k + j];
if (expert_idx < config_.num_gpu_experts || expert_idx >= config_.expert_num) {
continue;
}
if (m_local_num_[expert_idx] == 0) {
continue;
}
int pos = cache.m_local_pos_cache[token_id][j];
ggml_bf16_t* contrib = grad_output_bf16_ptr_[expert_idx] + pos * config_.hidden_size;
int h = 0;
for (; h < hidden_vec_end; h += 32) {
__m512 x0, x1, cur0, cur1;
avx512_32xbf16_to_32xfp32((__m512i*)(contrib + h), &x0, &x1);
avx512_32xbf16_to_32xfp32((__m512i*)(dst + h), &cur0, &cur1);
x0 = _mm512_fmadd_ps(x0, scale_vec, cur0);
x1 = _mm512_fmadd_ps(x1, scale_vec, cur1);
avx512_32xfp32_to_32xbf16(&x0, &x1, (__m512i*)(dst + h));
}
for (; h < hidden; h++) {
float add = GGML_BF16_TO_FP32(contrib[h]) * scale;
float cur = GGML_BF16_TO_FP32(dst[h]);
cur += add;
dst[h] = GGML_FP32_TO_BF16(cur);
}
}
});
};
auto base_pass = [&](bool do_up) {
// Quantize grad to BufferA
direct_or_pool(activated_expert, [&, do_up](int task_id) {
int expert_idx = m_expert_id_map_[task_id];
int m = m_local_num_[expert_idx];
if (m == 0) return;
size_t offset = expert_offsets[task_id];
ggml_bf16_t* grad = do_up ? (grad_up_output_ + offset * config_.intermediate_size)
: (grad_gate_output_ + offset * config_.intermediate_size);
down_ba_[expert_idx]->from_mat(m, grad, 0, 1);
});
int nth = T::recommended_nth(config_.hidden_size);
pool->do_work_stealing_job(
nth * activated_expert, [](int _) { T::config(); },
[&, do_up, nth](int task_id) {
int task_idx = task_id / nth;
int expert_idx = m_expert_id_map_[task_idx];
int ith = task_id % nth;
int m = m_local_num_[expert_idx];
if (m == 0) return;
auto& ba = down_ba_[expert_idx];
auto& bb = do_up ? up_backward_bb_[expert_idx] : gate_backward_bb_[expert_idx];
auto& bc = grad_gate_up_bc_[expert_idx];
amx::mat_mul(m, config_.hidden_size, config_.intermediate_size, ba, bb, bc, ith, nth);
bc->to_mat(m, grad_output_bf16_ptr_[expert_idx], ith, nth);
},
nullptr);
scatter_to_grad_input(1.0f);
};
base_pass(false); // gate
base_pass(true); // up
// // DEBUG: Check m_local_input_ptr_ AFTER base_pass (before LoRA)
// {
// bool has_nan = false, has_large = false;
// float max_val = 0.0f;
// for (int task_id = 0; task_id < activated_expert && !has_nan; task_id++) {
// int expert_idx = m_expert_id_map_[task_id];
// int m = m_local_num_[expert_idx];
// if (m == 0) continue;
// ggml_bf16_t* input_ptr = m_local_input_ptr_[expert_idx];
// for (int i = 0; i < m * config_.hidden_size && !has_nan; i++) {
// float v = GGML_BF16_TO_FP32(input_ptr[i]);
// if (std::isnan(v) || std::isinf(v)) has_nan = true;
// float av = std::abs(v);
// if (av > max_val) max_val = av;
// if (av > 1e10f) has_large = true;
// }
// }
// if (has_nan || has_large) {
// printf("[NaN DEBUG L%d] m_local_input AFTER base_pass: has_nan=%d has_large=%d max=%.6e\n",
// config_.layer_idx, has_nan, has_large, max_val);
// }
// }
// Skip all LoRA computation when SkipLoRA is true
if (SkipLoRA || gate_lora_a_ == nullptr || gate_lora_b_ == nullptr) {
return;
}
const bool use_fp32_lora_a = (fp32_grad_gate_lora_a != nullptr);
// =====================================================
// Fused LoRA Step 1+2:
// input -> u_gate/u_up (stored for later gb_gemm/gradA)
// grad_gate/grad_up + u_gate/u_up -> grad_B
// This removes the extra BF16 reread between u_merged and gradb_merged.
// =====================================================
struct GuLoraFusedBuf {
int expert_idx = -1;
int num_tokens = 0;
size_t token_offset = 0;
size_t lora_b_offset = 0; // copy-type: expert_idx * full_I * rank
size_t lora_a_sparse_offset = 0; // reduce-type: task_id * rank * hidden (sparse FP32)
size_t lora_a_dense_offset = 0; // legacy: expert_idx * rank * hidden (dense BF16)
const ggml_bf16_t* input = nullptr;
const ggml_bf16_t* gate_lora_a = nullptr;
const ggml_bf16_t* up_lora_a = nullptr;
ggml_bf16_t* gate_inter = nullptr;
ggml_bf16_t* up_inter = nullptr;
ggml_bf16_t* gate_grad = nullptr;
ggml_bf16_t* up_grad = nullptr;
};
struct GuLoraFusedTask {
int expert_task = -1;
int t_start = 0;
int t_end = 0;
};
const int hidden = config_.hidden_size;
const int inter_size = config_.intermediate_size;
const int rank = lora_rank_;
const int lora_b_expert_stride = full_intermediate_size * rank; // copy-type: full_I * rank
const size_t gradb_elems = static_cast<size_t>(inter_size) * rank;
std::vector<GuLoraFusedBuf> fused_bufs(activated_expert);
std::vector<GuLoraFusedTask> fused_tasks;
std::vector<float> gate_gradb_all(static_cast<size_t>(activated_expert) * gradb_elems, 0.0f);
std::vector<float> up_gradb_all(static_cast<size_t>(activated_expert) * gradb_elems, 0.0f);
std::vector<std::mutex> gradb_mutexes(activated_expert);
for (int task_id = 0; task_id < activated_expert; task_id++) {
GuLoraFusedBuf& buf = fused_bufs[task_id];
int expert_idx = m_expert_id_map_[task_id];
int num_tokens = m_local_num_[expert_idx];
buf.expert_idx = expert_idx;
buf.num_tokens = num_tokens;
if (num_tokens == 0) continue;
buf.token_offset = expert_offsets[task_id];
buf.lora_b_offset = static_cast<size_t>(expert_idx) * lora_b_expert_stride; // copy-type: full_I * rank
buf.lora_a_sparse_offset = static_cast<size_t>(task_id) * rank * hidden; // sparse FP32
buf.lora_a_dense_offset = static_cast<size_t>(expert_idx) * rank * hidden; // legacy dense BF16
buf.input = m_local_input_ptr_[expert_idx];
buf.gate_lora_a = gate_lora_a_ + static_cast<size_t>(expert_idx) * rank * hidden;
buf.up_lora_a = up_lora_a_ + static_cast<size_t>(expert_idx) * rank * hidden;
buf.gate_inter = lora_gate_intermediate_ptr_[expert_idx];
buf.up_inter = lora_up_intermediate_ptr_[expert_idx];
buf.gate_grad = grad_gate_output_ + buf.token_offset * inter_size;
buf.up_grad = grad_up_output_ + buf.token_offset * inter_size;
constexpr int kGuLoraTokenTile = 1024;
for (int t = 0; t < num_tokens; t += kGuLoraTokenTile) {
fused_tasks.push_back({task_id, t, std::min(t + kGuLoraTokenTile, num_tokens)});
}
}
if (!fused_tasks.empty()) {
direct_or_pool(static_cast<int>(fused_tasks.size()), [&, hidden, inter_size, rank, gradb_elems](int task_id) {
const GuLoraFusedTask& task = fused_tasks[task_id];
GuLoraFusedBuf& buf = fused_bufs[task.expert_task];
if (buf.num_tokens == 0) return;
int local_tokens = task.t_end - task.t_start;
size_t u_elems = static_cast<size_t>(local_tokens) * rank;
size_t scratch_elems = u_elems * 2 + gradb_elems * 2;
float* scratch = get_lora_fp32_buffer(scratch_elems);
float* gate_u = scratch;
float* up_u = gate_u + u_elems;
float* gate_gradb_local = up_u + u_elems;
float* up_gradb_local = gate_gradb_local + gradb_elems;
memset(gate_gradb_local, 0, gradb_elems * 2 * sizeof(float));
avx::lora_bf16_matmul_t4r4(buf.input + static_cast<size_t>(task.t_start) * hidden, buf.gate_lora_a, gate_u,
local_tokens, hidden, rank);
avx::lora_bf16_matmul_t4r4(buf.input + static_cast<size_t>(task.t_start) * hidden, buf.up_lora_a, up_u,
local_tokens, hidden, rank);
for (int t = 0; t < local_tokens; t++) {
ggml_bf16_t* gate_row = buf.gate_inter + static_cast<size_t>(task.t_start + t) * padded_lora_rank_;
ggml_bf16_t* up_row = buf.up_inter + static_cast<size_t>(task.t_start + t) * padded_lora_rank_;
memset(gate_row, 0, padded_lora_rank_ * sizeof(ggml_bf16_t));
memset(up_row, 0, padded_lora_rank_ * sizeof(ggml_bf16_t));
const float* gate_u_row = gate_u + static_cast<size_t>(t) * rank;
const float* up_u_row = up_u + static_cast<size_t>(t) * rank;
for (int r = 0; r < rank; r++) {
gate_row[r] = GGML_FP32_TO_BF16(gate_u_row[r]);
up_row[r] = GGML_FP32_TO_BF16(up_u_row[r]);
}
const ggml_bf16_t* gate_grad_row = buf.gate_grad + static_cast<size_t>(task.t_start + t) * inter_size;
const ggml_bf16_t* up_grad_row = buf.up_grad + static_cast<size_t>(task.t_start + t) * inter_size;
if (rank == 8) {
__m256 gate_u_vec = _mm256_loadu_ps(gate_u_row);
__m256 up_u_vec = _mm256_loadu_ps(up_u_row);
for (int i = 0; i < inter_size; i++) {
float gg = GGML_BF16_TO_FP32(gate_grad_row[i]);
if (gg != 0.0f) {
float* out = gate_gradb_local + static_cast<size_t>(i) * rank;
__m256 acc = _mm256_loadu_ps(out);
acc = _mm256_fmadd_ps(_mm256_set1_ps(gg), gate_u_vec, acc);
_mm256_storeu_ps(out, acc);
}
float ug = GGML_BF16_TO_FP32(up_grad_row[i]);
if (ug != 0.0f) {
float* out = up_gradb_local + static_cast<size_t>(i) * rank;
__m256 acc = _mm256_loadu_ps(out);
acc = _mm256_fmadd_ps(_mm256_set1_ps(ug), up_u_vec, acc);
_mm256_storeu_ps(out, acc);
}
}
} else {
for (int i = 0; i < inter_size; i++) {
float gg = GGML_BF16_TO_FP32(gate_grad_row[i]);
if (gg != 0.0f) {
float* out = gate_gradb_local + static_cast<size_t>(i) * rank;
for (int r = 0; r < rank; r++) {
out[r] += gg * gate_u_row[r];
}
}
float ug = GGML_BF16_TO_FP32(up_grad_row[i]);
if (ug != 0.0f) {
float* out = up_gradb_local + static_cast<size_t>(i) * rank;
for (int r = 0; r < rank; r++) {
out[r] += ug * up_u_row[r];
}
}
}
}
}
std::lock_guard<std::mutex> lock(gradb_mutexes[task.expert_task]);
float* gate_gradb_global = gate_gradb_all.data() + static_cast<size_t>(task.expert_task) * gradb_elems;
float* up_gradb_global = up_gradb_all.data() + static_cast<size_t>(task.expert_task) * gradb_elems;
if (rank == 8) {
for (size_t off = 0; off < gradb_elems; off += rank) {
__m256 gate_acc = _mm256_loadu_ps(gate_gradb_global + off);
gate_acc = _mm256_add_ps(gate_acc, _mm256_loadu_ps(gate_gradb_local + off));
_mm256_storeu_ps(gate_gradb_global + off, gate_acc);
__m256 up_acc = _mm256_loadu_ps(up_gradb_global + off);
up_acc = _mm256_add_ps(up_acc, _mm256_loadu_ps(up_gradb_local + off));
_mm256_storeu_ps(up_gradb_global + off, up_acc);
}
} else {
for (size_t off = 0; off < gradb_elems; off++) {
gate_gradb_global[off] += gate_gradb_local[off];
up_gradb_global[off] += up_gradb_local[off];
}
}
});
constexpr int kGuGradBBlock = 256;
int gradb_blocks = (inter_size + kGuGradBBlock - 1) / kGuGradBBlock;
const float scale = lora_scaling_;
pool->do_work_stealing_job(
activated_expert * 2 * gradb_blocks, nullptr,
[&, grad_gate_b, grad_up_b, activated_expert, gradb_blocks, inter_size, rank, gradb_elems,
scale](int task_id) {
int half_tasks = activated_expert * gradb_blocks;
bool do_up = task_id >= half_tasks;
int local_task_id = do_up ? (task_id - half_tasks) : task_id;
int expert_task = local_task_id / gradb_blocks;
int block_idx = local_task_id % gradb_blocks;
GuLoraFusedBuf& buf = fused_bufs[expert_task];
if (buf.num_tokens == 0) return;
int i_start = block_idx * kGuGradBBlock;
int i_end = std::min(inter_size, i_start + kGuGradBBlock);
float* gradb_global =
(do_up ? up_gradb_all.data() : gate_gradb_all.data()) + static_cast<size_t>(expert_task) * gradb_elems;
ggml_bf16_t* grad_lora_b = do_up ? grad_up_b : grad_gate_b;
for (int i = i_start; i < i_end; i++) {
ggml_bf16_t* out = grad_lora_b + buf.lora_b_offset + static_cast<size_t>(i) * rank;
float* acc_row = gradb_global + static_cast<size_t>(i) * rank;
for (int r = 0; r < rank; r++) {
float cur = GGML_BF16_TO_FP32(out[r]);
cur += acc_row[r] * scale;
out[r] = GGML_FP32_TO_BF16(cur);
}
}
},
nullptr);
}
// =====================================================
// Remaining LoRA steps:
// grad @ B^T -> G_B
// G_B @ A -> grad_input contribution
// scatter + grad_A
// Gate and up still run sequentially because they share grad_output_bf16_ptr_.
// =====================================================
auto lora_pass_remainder = [&](bool do_up) {
struct GuLoraGradInTask {
int expert_task = -1;
int t_start = 0;
int t_end = 0;
};
std::vector<GuLoraGradInTask> gradin_tasks;
gradin_tasks.reserve(activated_expert * 16);
for (int expert_task = 0; expert_task < activated_expert; expert_task++) {
int expert_idx = m_expert_id_map_[expert_task];
int m = m_local_num_[expert_idx];
constexpr int kGuGradInTile = 512;
for (int t = 0; t < m; t += kGuGradInTile) {
gradin_tasks.push_back({expert_task, t, std::min(t + kGuGradInTile, m)});
}
}
if (!gradin_tasks.empty()) {
direct_or_pool(static_cast<int>(gradin_tasks.size()), [&, do_up](int task_id) {
const GuLoraGradInTask& task = gradin_tasks[task_id];
int expert_task = task.expert_task;
int expert_idx = m_expert_id_map_[expert_task];
int local_tokens = task.t_end - task.t_start;
if (local_tokens <= 0) return;
const int hidden = config_.hidden_size;
const int inter_size = config_.intermediate_size;
const size_t offset = expert_offsets[expert_task] + task.t_start;
ggml_bf16_t* grad =
do_up ? (grad_up_output_ + offset * inter_size) : (grad_gate_output_ + offset * inter_size);
ggml_bf16_t* inter_ptr_base =
do_up ? lora_up_intermediate_ptr_[expert_idx] : lora_gate_intermediate_ptr_[expert_idx];
ggml_bf16_t* inter_ptr = inter_ptr_base + static_cast<size_t>(task.t_start) * padded_lora_rank_;
ggml_bf16_t* grad_out = grad_output_bf16_ptr_[expert_idx] + static_cast<size_t>(task.t_start) * hidden;
const ggml_bf16_t* lora_b_t = (do_up ? up_lora_b_transposed_ : gate_lora_b_transposed_) +
static_cast<size_t>(expert_idx) * lora_rank_ * inter_size;
const ggml_bf16_t* lora_a =
(do_up ? up_lora_a_ : gate_lora_a_) + static_cast<size_t>(expert_idx) * lora_rank_ * hidden;
float* gb = get_lora_fp32_buffer(static_cast<size_t>(local_tokens) * lora_rank_);
avx::lora_backward_matmul_transposed(grad, lora_b_t, gb, local_tokens, inter_size, lora_rank_);
memset(inter_ptr, 0, static_cast<size_t>(local_tokens) * padded_lora_rank_ * sizeof(ggml_bf16_t));
for (int t = 0; t < local_tokens; t++) {
ggml_bf16_t* inter_row = inter_ptr + static_cast<size_t>(t) * padded_lora_rank_;
const float* gb_row = gb + static_cast<size_t>(t) * lora_rank_;
for (int r = 0; r < lora_rank_; r++) {
inter_row[r] = GGML_FP32_TO_BF16(gb_row[r]);
}
}
memset(grad_out, 0, static_cast<size_t>(local_tokens) * hidden * sizeof(ggml_bf16_t));
avx::lora_fp32_bf16_fused_add_transposed(gb, lora_a, grad_out, local_tokens, lora_rank_, hidden, 1.0f);
});
}
scatter_to_grad_input(lora_scaling_);
// Step 6: grad_A = G_B^T @ X
ggml_bf16_t* grad_lora_a = do_up ? grad_up_a : grad_gate_a;
float* fp32_grad_lora_a = do_up ? fp32_grad_up_lora_a : fp32_grad_gate_lora_a;
constexpr int kGuGradATile = 512;
int grad_a_blocks = (config_.hidden_size + kGuGradATile - 1) / kGuGradATile;
pool->do_work_stealing_job(
activated_expert * grad_a_blocks, nullptr,
[this, do_up, grad_lora_a, fp32_grad_lora_a, use_fp32_lora_a, grad_a_blocks, &fused_bufs](int task_id) {
int expert_task = task_id / grad_a_blocks;
int block_idx = task_id % grad_a_blocks;
int expert_idx = m_expert_id_map_[expert_task];
int num_tokens = m_local_num_[expert_idx];
if (num_tokens == 0) return;
ggml_bf16_t* g_ptr =
do_up ? lora_up_intermediate_ptr_[expert_idx] : lora_gate_intermediate_ptr_[expert_idx];
const GuLoraFusedBuf& buf = fused_bufs[expert_task];
ggml_bf16_t* expert_input = m_local_input_ptr_[expert_idx];
const int hidden = config_.hidden_size;
constexpr int kVecWidth = 32;
int h_start = block_idx * kGuGradATile;
int h_end = std::min(hidden, h_start + kGuGradATile);
int tile_len = h_end - h_start;
if (tile_len <= 0) return;
int tile_vec_end = tile_len & ~(kVecWidth - 1);
__m512 scale_vec = _mm512_set1_ps(lora_scaling_);
const int lora_r = lora_rank_;
// Split one expert into hidden-dimension tiles so LoRA grad_A can use all CPU threads.
std::vector<float> accum(lora_r * tile_len, 0.0f);
for (int t = 0; t < num_tokens; t++) {
const ggml_bf16_t* g_row = g_ptr + t * padded_lora_rank_;
const ggml_bf16_t* input_row = expert_input + t * hidden + h_start;
for (int r = 0; r < lora_r; r++) {
float gb = GGML_BF16_TO_FP32(g_row[r]);
if (gb == 0.0f) continue;
__m512 gb_vec = _mm512_set1_ps(gb);
float* acc_row = accum.data() + r * tile_len;
int h = 0;
for (; h < tile_vec_end; h += kVecWidth) {
__m512 acc0 = _mm512_loadu_ps(acc_row + h);
__m512 acc1 = _mm512_loadu_ps(acc_row + h + 16);
__m512 x0, x1;
avx512_32xbf16_to_32xfp32((__m512i*)(input_row + h), &x0, &x1);
acc0 = _mm512_fmadd_ps(x0, gb_vec, acc0);
acc1 = _mm512_fmadd_ps(x1, gb_vec, acc1);
_mm512_storeu_ps(acc_row + h, acc0);
_mm512_storeu_ps(acc_row + h + 16, acc1);
}
for (; h < tile_len; h++) {
float inp = GGML_BF16_TO_FP32(input_row[h]);
acc_row[h] += inp * gb;
}
}
}
// Write back
if (use_fp32_lora_a) {
// Sparse FP32 direct accumulation
for (int r = 0; r < lora_r; r++) {
float* fp32_row = fp32_grad_lora_a + buf.lora_a_sparse_offset + r * hidden + h_start;
float* acc_row = accum.data() + r * tile_len;
int h = 0;
for (; h + 16 <= tile_len; h += 16) {
__m512 cur = _mm512_loadu_ps(fp32_row + h);
__m512 val = _mm512_loadu_ps(acc_row + h);
cur = _mm512_fmadd_ps(val, scale_vec, cur);
_mm512_storeu_ps(fp32_row + h, cur);
}
for (; h < tile_len; h++) {
fp32_row[h] += acc_row[h] * lora_scaling_;
}
}
} else {
// Legacy dense BF16 RMW
for (int r = 0; r < lora_r; r++) {
ggml_bf16_t* grad_row = grad_lora_a + buf.lora_a_dense_offset + r * hidden + h_start;
float* acc_row = accum.data() + r * tile_len;
int h = 0;
for (; h + kVecWidth <= tile_len; h += kVecWidth) {
__m512 sum0 = _mm512_loadu_ps(acc_row + h);
__m512 sum1 = _mm512_loadu_ps(acc_row + h + 16);
__m512 cur0, cur1;
avx512_32xbf16_to_32xfp32((__m512i*)(grad_row + h), &cur0, &cur1);
cur0 = _mm512_fmadd_ps(sum0, scale_vec, cur0);
cur1 = _mm512_fmadd_ps(sum1, scale_vec, cur1);
avx512_32xfp32_to_32xbf16(&cur0, &cur1, (__m512i*)(grad_row + h));
}
for (; h < tile_len; h++) {
float cur = GGML_BF16_TO_FP32(grad_row[h]);
cur += acc_row[h] * lora_scaling_;
grad_row[h] = GGML_FP32_TO_BF16(cur);
}
}
}
},
nullptr);
};
lora_pass_remainder(false); // gate: gb_gradin_fused, scatter, gradA
lora_pass_remainder(true); // up: gb_gradin_fused, scatter, gradA
}
};
#endif // CPUINFER_OPERATOR_AMX_SFT_MOE_H