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
5490 lines
252 KiB
C++
5490 lines
252 KiB
C++
/**
|
||
* @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
|