Copyright (c) 2025-2025 Huawei Technologies Co., Ltd.
sysHAX-adapter is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
PURPOSE.
See the Mulan PSL v2 for more details.
Created: 2026-1-31
Desc: CPU inference qwen3 moe model
*/
#include "qwen3_moe.h"
namespace cpu_inference {
namespace {
float16_t exp_table_fp16[1 << 16];
static bool is_exp_table_fp16_init = false;
}
Qwen3MoeLayer::Qwen3MoeLayer(std::vector<int> numa_list, int layer_id, std::string weight_quant,
std::string input_quant, int nrc, const Qwen3MoeConfig& config){
if(!is_exp_table_fp16_init){
is_exp_table_fp16_init = true;
for(int i = 0; i < (1 << 16); ++i) {
float f = (float)(*(float16_t*)(&i));
exp_table_fp16[i] = (float16_t)(expf(f));
}
}
this->mergeSilu = new MergeSilu(numa_list);
this->numa_list = numa_list;
this->layer_id = layer_id;
this->weight_quant = weight_quant;
this->input_quant = input_quant;
this->nrc = nrc;
this->config = config;
if(weight_quant == "fp16" && input_quant == "fp16"){
this->matMul = new MatMulF16(nrc);
}
else if (weight_quant == "q8_0" && input_quant == "q8_0"){
this->matMul = new MatMulQ8(nrc);
}
else if (weight_quant == "q4_0" && input_quant == "q8_0"){
this->matMul = new MatMulQ4Q8(nrc);
}
else if (weight_quant == "q8align" && input_quant == "q8align"){
this->matMul = new MatMulQ8Align(nrc);
}
else {
throw std::runtime_error(std::string("not supported matmul type"));
}
if(input_quant == "fp16"){
this->elem_cnt_in_block = QuantizationBase<float16_t>::elem_cnt_in_block;
this->block_byte_size = QuantizationBase<float16_t>::block_byte_size;
}
else if(input_quant == "q8_0"){
this->elem_cnt_in_block = QuantizationBase<block_q8_0>::elem_cnt_in_block;
this->block_byte_size = QuantizationBase<block_q8_0>::block_byte_size;
}
else if(input_quant == "q8align"){
this->elem_cnt_in_block = Q8Align::elem_cnt_in_block;
this->block_byte_size = Q8Align::block_byte_size;
}
else {
throw std::runtime_error(std::string("not supported input quant type") + input_quant);
}
}
Qwen3MoeLayer::~Qwen3MoeLayer(){
if(this->matMul){
delete this->matMul;
}
int numa_cnt = numa_list.size();
if (gate_weight) numa_free(gate_weight, numa_cnt * sizeof(void*));
if (up_weight) numa_free(up_weight, numa_cnt * sizeof(void*));
if (down_weight) numa_free(down_weight, numa_cnt * sizeof(void*));
}
void Qwen3MoeLayer::load_model(ModelWeightBase* model_weight){
std::string gate_name = std::to_string(this->layer_id) + "_" + "gate_proj.weight";
std::string up_name = std::to_string(this->layer_id) + "_" + "up_proj.weight";
std::string down_name = std::to_string(this->layer_id) + "_" + "down_proj.weight";
int numa_cnt = numa_list.size();
this->gate_weight = (void**)numa_alloc_onnode(numa_cnt * sizeof(void*), 0);
this->up_weight = (void**)numa_alloc_onnode(numa_cnt * sizeof(void*), 0);
this->down_weight = (void**)numa_alloc_onnode(numa_cnt * sizeof(void*), 0);
for (int i = 0; i < numa_cnt; i++){
this->gate_weight[i] = model_weight->get_weight(gate_name).memory_map.at(i);
this->up_weight[i] = model_weight->get_weight(up_name).memory_map.at(i);
this->down_weight[i] = model_weight->get_weight(down_name).memory_map.at(i);
}
}
void Qwen3MoeLayer::softmax_topk(
const WorkDivider* work,
std::vector<std::vector<int>>& expert_tokens,
std::vector<std::vector<float>>& expert_weights_list,
std::vector<int>& experts,
float16_t* router_logits,
int token_cnt,
int* max_idx_record,
f32* max_val_record){
MultiNumaWorkRange multiRange;
divide_work_all_numas(work, token_cnt, &multiRange);
int num_experts = this->config.num_experts;
int top_k = this->config.num_experts_per_tok;
for (int token_idx = multiRange.begin_numa + multiRange.begin_thread; token_idx < multiRange.begin_numa + multiRange.end_thread; token_idx++) {
int* max_idx_arr = (int*)max_idx_record + token_idx * top_k;
f32* max_val_arr = (f32*)max_val_record + token_idx * top_k;
for (int k = 0; k < top_k; k++) {
max_idx_arr[k] = -1;
max_val_arr[k] = -FLT_MAX;
}
for (int j = 0; j < num_experts; j++) {
f32 val = (f32)(router_logits[token_idx * num_experts + j]);
if(val > max_val_arr[0]){
max_idx_arr[0] = j;
max_val_arr[0] = val;
}
else {
continue;
}
int heap_idx = 0;
while(1){
int left_child_idx = (heap_idx << 1) + 1;
int right_child_idx = (heap_idx << 1) + 2;
int swap_idx = -1;
if(right_child_idx < top_k){
if(max_val_arr[left_child_idx] < max_val_arr[right_child_idx]){
swap_idx = left_child_idx;
}
else{
swap_idx = right_child_idx;
}
}
else if(left_child_idx < top_k){
swap_idx = left_child_idx;
}
else {
break;
}
if(max_val_arr[swap_idx] < max_val_arr[heap_idx]){
std::swap(max_idx_arr[swap_idx], max_idx_arr[heap_idx]);
std::swap(max_val_arr[swap_idx], max_val_arr[heap_idx]);
heap_idx = swap_idx;
}
else{
break;
}
}
}
f32 max_val = max_val_arr[top_k - 1];
for(int i = 0;i < top_k;i++){
if(max_val_arr[i] > max_val){
max_val = max_val_arr[i];
}
}
float max_val_sum = 0;
for(int i = 0;i < top_k;i++){
float16_t diff = ((float16_t)(max_val_arr[i] - max_val));
max_val_arr[i] = (float)exp_table_fp16[*(uint16_t *)&diff];
max_val_sum += max_val_arr[i];
}
for(int i = 0;i < top_k;i++){
max_val_arr[i] /= max_val_sum;
}
}
#pragma omp barrier
if(work->global_tid == 0){
for(int token_idx = 0;token_idx < token_cnt;token_idx++){
for (int k = 0; k < top_k; k++) {
int exp_idx = max_idx_record[token_idx * top_k + k];
f32 exp_weight = max_val_record[token_idx * top_k + k];
expert_tokens[exp_idx].push_back(token_idx);
expert_weights_list[exp_idx].push_back(exp_weight);
}
}
experts.resize(num_experts);
for (int exp = 0; exp < num_experts; exp++){
experts[exp] = exp;
}
sort(experts.begin(), experts.end(), [&](int a, int b) {
return expert_tokens[a].size() > expert_tokens[b].size();
});
}
#pragma omp barrier
}
void Qwen3MoeLayer::forward(Qwen3MoeRunState* run_state, torch::Tensor& output,
const torch::Tensor& hidden_states, const torch::Tensor& router_logits){
int64_t token_cnt = hidden_states.size(0);
int first_numa = numa_list[0];
int numa_cnt = numa_list.size();
int top_k = this->config.num_experts_per_tok;
int num_experts = this->config.num_experts;
int hidden_size = this->config.hidden_size;
int moe_intermediate_size = this->config.moe_intermediate_size;
float16_t* expert_output = (float16_t*)output.data_ptr();
memset(expert_output, 0, token_cnt * hidden_size * sizeof(float16_t));
float16_t* hidden_states_ptr = (float16_t*)hidden_states.data_ptr();
float16_t* router_logits_ptr = (float16_t*)router_logits.data_ptr();
std::vector<std::vector<int>> expert_tokens(num_experts);
std::vector<std::vector<f32>> expert_weights_list(num_experts);
std::vector<int> experts;
int max_idx_record[token_cnt * top_k];
f32 max_val_record[token_cnt * top_k];
#pragma omp parallel
{
WorkDivider work;
init_work_divider(&work, numa_cnt);
softmax_topk(&work, expert_tokens, expert_weights_list, experts, router_logits_ptr, token_cnt, max_idx_record, max_val_record);
init_work_divider(&work, numa_cnt, top_k, true);
for (int base = 0; base < experts.size(); base += top_k){
if (expert_tokens[experts[base]].size() == 0) break;
SingleNumaWorkRange srange;
int I = work.global_tid / work.threads_per_numa;
int exp = experts[base + I];
int num_tokens = expert_tokens[exp].size();
float16_t *expert_input = static_cast<float16_t*>(run_state->expert_input[I]);
float16_t *expert_gate = static_cast<float16_t*>(run_state->expert_gate[I]);
float16_t *expert_up = static_cast<float16_t*>(run_state->expert_up[I]);
float16_t *expert_down = static_cast<float16_t*>(run_state->expert_down[I]);
float16_t *expert_fused = static_cast<float16_t*>(run_state->expert_fused[I]);
void** quant_expert_input = (void**)run_state->quant_expert_input[I];
void** quant_expert_fused = (void**)run_state->quant_expert_fused[I];
divide_all_work(&work, hidden_size, &srange);
if (num_tokens)
for (int i = 0; i < num_tokens; i++) {
int token_idx = expert_tokens[exp][i];
memcpy(expert_input + i * hidden_size + srange.begin_thread,
hidden_states_ptr + token_idx * hidden_size + srange.begin_thread,
srange.work_per_thread * sizeof(float16_t));
}
std::string quant_type = this->input_quant;
size_t total_elem_size = num_tokens * hidden_size;
int dstBlockNum = this->elem_cnt_in_block;
int dstBlockSize = this->block_byte_size;
#pragma omp barrier
divide_all_work(&work, total_elem_size / dstBlockNum, &srange);
if (num_tokens){
if (quant_type == "q8align"){
Q8Align::q8align_quantize((float16_t*)expert_input,
(int8_t*)((void**)quant_expert_input[work.my_numa])[0],
(float*)((void**)quant_expert_input[work.my_numa])[1],
srange.begin_thread, srange.work_per_thread, hidden_size / dstBlockNum, true);
}else{
Quantize((char *)quant_expert_input[work.my_numa] + srange.begin_thread * dstBlockSize,
expert_input + srange.begin_thread * dstBlockNum,
quant_type, srange.work_per_thread * dstBlockNum);
}
}
if (num_tokens) matMul->matmul(&work, expert_gate, this->gate_weight, exp,
quant_expert_input, 0, num_tokens, hidden_size, moe_intermediate_size);
if (num_tokens) matMul->matmul(&work, expert_up, this->up_weight, exp,
quant_expert_input, 0, num_tokens, hidden_size, moe_intermediate_size);
this->mergeSilu->forward(work, expert_fused, expert_gate,
expert_up, num_tokens, moe_intermediate_size);
#pragma omp barrier
total_elem_size = num_tokens * moe_intermediate_size;
divide_all_work(&work, total_elem_size / dstBlockNum, &srange);
if (num_tokens){
if (quant_type == "q8align"){
Q8Align::q8align_quantize((float16_t*)expert_fused,
(int8_t*)((void**)quant_expert_fused[work.my_numa])[0],
(float*)((void**)quant_expert_fused[work.my_numa])[1],
srange.begin_thread, srange.work_per_thread, moe_intermediate_size / dstBlockNum, true);
}else{
Quantize((char *)quant_expert_fused[work.my_numa] + srange.begin_thread * dstBlockSize,
expert_fused + srange.begin_thread * dstBlockNum,
quant_type, srange.work_per_thread * dstBlockNum);
}
}
if (num_tokens) matMul->matmul(&work, expert_down, this->down_weight, exp,
quant_expert_fused, 0, num_tokens, moe_intermediate_size, hidden_size);
divide_all_work(&work, num_tokens * hidden_size, &srange);
if (num_tokens)
for (int i = srange.begin_thread; i < srange.end_thread; i++){
float16_t *output_ptr = expert_output + expert_tokens[exp][i / hidden_size] * hidden_size + (i % hidden_size);
*output_ptr += expert_weights_list[exp][i / hidden_size] * expert_down[i];
}
}
}
}
Qwen3MoeModel::Qwen3MoeModel(std::vector<int> numa_list, std::string weight_quant,
std::string input_quant, int nrc, const Qwen3MoeConfig& config){
this->numa_list = numa_list;
this->weight_quant = weight_quant;
this->input_quant = input_quant;
this->config = config;
for(int i = 0;i < config.num_hidden_layers;i++){
Qwen3MoeLayer* layer = new Qwen3MoeLayer(numa_list, i, weight_quant, input_quant,
nrc, config);
layers.push_back(layer);
}
}
Qwen3MoeModel::~Qwen3MoeModel(){
for(int i = 0;i < layers.size();i++){
if(layers[i]){
delete layers[i];
layers[i] = nullptr;
}
}
}
void Qwen3MoeModel::load_model(ModelWeightBase* model_weight){
for(const auto& layer_ptr: layers){
layer_ptr->load_model(model_weight);
}
}
Qwen3MoeRunState::Qwen3MoeRunState(
const Qwen3MoeConfig& config,
const std::vector<int>& numa_list,
const std::string& quant_type,
int token_cnt){
if (token_cnt&1) token_cnt++;
this->config = config;
this->token_cnt = token_cnt;
int hidden_size = config.hidden_size;
int num_experts = config.num_experts;
int moe_intermediate_size = config.moe_intermediate_size;
int top_k = config.num_experts_per_tok;
this->numa_cnt = numa_list.size();
this->quant_type = quant_type;
if (num_experts > 0) {
std::vector<int>expert_numa_id;
for (int i = 0; i < top_k; i++){
expert_numa_id.push_back(i / (top_k / numa_cnt));
}
int max_expert_tokens = token_cnt;
this->expert_input = (void**)numa_alloc_onnode(top_k * sizeof(void**), 0);
this->expert_gate = (void**)numa_alloc_onnode(top_k * sizeof(void**), 0);
this->expert_up = (void**)numa_alloc_onnode(top_k * sizeof(void**), 0);
this->expert_fused = (void**)numa_alloc_onnode(top_k * sizeof(void**), 0);
this->expert_down = (void**)numa_alloc_onnode(top_k * sizeof(void**), 0);
for (int i = 0; i < top_k; i++){
this->expert_input[i] = (float16_t*)numa_alloc_onnode(max_expert_tokens * hidden_size * sizeof(float16_t), expert_numa_id[i]);
this->expert_gate[i] = (float16_t*)numa_alloc_onnode(max_expert_tokens * moe_intermediate_size * sizeof(float16_t), expert_numa_id[i]);
this->expert_up[i] = (float16_t*)numa_alloc_onnode(max_expert_tokens * moe_intermediate_size * sizeof(float16_t), expert_numa_id[i]);
this->expert_fused[i] = (float16_t*)numa_alloc_onnode(max_expert_tokens * moe_intermediate_size * sizeof(float16_t), expert_numa_id[i]);
this->expert_down[i] = (float16_t*)numa_alloc_onnode(max_expert_tokens * hidden_size * sizeof(float16_t), expert_numa_id[i]);
}
if (quant_type == "q8align"){
void**** quant_expert_input_ptr = (void****)numa_alloc_onnode(top_k * sizeof(void***), 0);
void**** quant_expert_fused_ptr = (void****)numa_alloc_onnode(top_k * sizeof(void***), 0);
for (int j = 0; j < top_k; j++){
quant_expert_input_ptr[j] = (void***)numa_alloc_onnode(numa_cnt * sizeof(void**), expert_numa_id[j]);
quant_expert_fused_ptr[j] = (void***)numa_alloc_onnode(numa_cnt * sizeof(void**), expert_numa_id[j]);
for (int i = 0; i < numa_cnt; i++) {
quant_expert_input_ptr[j][i] = (void**)numa_alloc_onnode(2 * sizeof(void*), i);
quant_expert_input_ptr[j][i][0] = (void*)numa_alloc_onnode(max_expert_tokens * hidden_size * sizeof(int8_t), i);
quant_expert_input_ptr[j][i][1] = (void*)numa_alloc_onnode(max_expert_tokens * hidden_size / QK8_ALIGN * 2 * sizeof(float), i);
quant_expert_fused_ptr[j][i] = (void**)numa_alloc_onnode(2 * sizeof(void*), i);
quant_expert_fused_ptr[j][i][0] = (void*)numa_alloc_onnode(max_expert_tokens * moe_intermediate_size * sizeof(int8_t), i);
quant_expert_fused_ptr[j][i][1] = (void*)numa_alloc_onnode(max_expert_tokens * moe_intermediate_size / QK8_ALIGN * 2 * sizeof(float), i);
}
}
this->quant_expert_input = (void***)quant_expert_input_ptr;
this->quant_expert_fused = (void***)quant_expert_fused_ptr;
}else{
this->quant_expert_input = (void***)numa_alloc_onnode(top_k * sizeof(void***), 0);
this->quant_expert_fused = (void***)numa_alloc_onnode(top_k * sizeof(void***), 0);
for (int j = 0; j < top_k; j++){
this->quant_expert_input[j] = (void**)numa_alloc_onnode(numa_cnt * sizeof(void**), expert_numa_id[j]);
this->quant_expert_fused[j] = (void**)numa_alloc_onnode(numa_cnt * sizeof(void**), expert_numa_id[j]);
for (int i = 0; i < numa_cnt; i++) {
this->quant_expert_input[j][i] = (void*)numa_alloc_onnode(max_expert_tokens * hidden_size * sizeof(f32), i);
this->quant_expert_fused[j][i] = (void*)numa_alloc_onnode(max_expert_tokens * moe_intermediate_size * sizeof(f32), i);
}
}
}
}
}
Qwen3MoeRunState::~Qwen3MoeRunState(){
int hidden_size = config.hidden_size;
int num_experts = config.num_experts;
int moe_intermediate_size = config.moe_intermediate_size;
int top_k = config.num_experts_per_tok;
if (num_experts > 0) {
for (int j = 0; j < top_k; j++){
if (expert_input[j]) numa_free(expert_input[j], token_cnt * hidden_size * sizeof(float16_t));
if (expert_gate[j]) numa_free(expert_gate[j], token_cnt * moe_intermediate_size* sizeof(float16_t));
if (expert_up[j]) numa_free(expert_up[j], token_cnt * moe_intermediate_size * sizeof(float16_t));
if (expert_fused[j]) numa_free(expert_fused[j], token_cnt * moe_intermediate_size * sizeof(float16_t));
if (expert_down[j]) numa_free(expert_down[j], token_cnt * hidden_size * sizeof(float16_t));
if(quant_type == "q8align"){
void**** quant_expert_input_ptr = (void****)quant_expert_input;
void**** quant_expert_fused_ptr = (void****)quant_expert_fused;
if (quant_expert_input_ptr[j]) {
for (int i = 0; i < numa_cnt; i++) {
if (quant_expert_input_ptr[j][i]) {
numa_free(quant_expert_input_ptr[j][i][0], token_cnt * hidden_size * sizeof(int8_t));
numa_free(quant_expert_input_ptr[j][i][1], token_cnt * hidden_size / QK8_ALIGN * 2 * sizeof(float));
}
numa_free(quant_expert_input_ptr[j][i], 2 * sizeof(void**));
}
numa_free(quant_expert_input_ptr[j], numa_cnt * sizeof(void***));
}
if (quant_expert_fused_ptr[j]) {
for (int i = 0; i < numa_cnt; i++) {
if (quant_expert_fused_ptr[j][i]) {
numa_free(quant_expert_fused_ptr[j][i][0], token_cnt * moe_intermediate_size * sizeof(int8_t));
numa_free(quant_expert_fused_ptr[j][i][1], token_cnt * moe_intermediate_size / QK8_ALIGN * 2 * sizeof(float));
}
numa_free(quant_expert_fused_ptr[j][i], 2 * sizeof(void**));
}
numa_free(quant_expert_fused_ptr[j], numa_cnt * sizeof(void***));
}
}else{
if (quant_expert_input[j]) {
for (int i = 0; i < numa_cnt; i++) {
if (quant_expert_input[j][i]) {
numa_free(quant_expert_input[j][i], token_cnt * hidden_size * sizeof(f32));
}
}
numa_free(quant_expert_input[j], numa_cnt * sizeof(void**));
}
if (quant_expert_fused[j]) {
for (int i = 0; i < numa_cnt; i++) {
if (quant_expert_fused[j][i]) {
numa_free(quant_expert_fused[j][i], token_cnt * moe_intermediate_size * sizeof(f32));
}
}
numa_free(quant_expert_fused[j], numa_cnt * sizeof(void**));
}
}
}
numa_free(quant_expert_input, top_k * sizeof(void***));
numa_free(quant_expert_fused, top_k * sizeof(void***));
numa_free(expert_input, top_k * sizeof(void**));
numa_free(expert_gate, top_k * sizeof(void**));
numa_free(expert_up, top_k * sizeof(void**));
numa_free(expert_fused, top_k * sizeof(void**));
numa_free(expert_down, top_k * sizeof(void**));
}
}
void Qwen3MoeModel::forward(torch::Tensor& expert_output, const torch::Tensor& hidden_states,
const torch::Tensor& router_logits, int64_t layer_id){
if(layer_id == 0){
if(this->run_state){
delete this->run_state;
this->run_state = nullptr;
}
int token_cnt = hidden_states.size(0);
this->run_state = new Qwen3MoeRunState(this->config, this->numa_list,
this->input_quant, token_cnt);
}
this->layers[layer_id]->forward(this->run_state, expert_output, hidden_states, router_logits);
if(layer_id + 1 == this->config.num_hidden_layers){
delete this->run_state;
this->run_state = nullptr;
}
}
};