路由.h
cpp
// 路由.h ------ MoE 专家路由选择声明
// 用途:从路由门控输出(256 维 logits)选择 top-8 专家并计算权重
// 说明:路由是 MoE 的核心决策------每个 token 只激活一小部分专家(8/256),
// 权重 = softmax 后 top-k 的重新归一化
#pragma once
// 引入基础类型(浮点/无符号/向量)
#include "公共/基础定义.h"
// 路由条目:一个被选中的专家及其权重
struct 路由条目 {
// 专家索引:被选中的专家编号(0..专家数-1)
无符号 专家索引 = 0;
// 权重:该专家输出的加权系数(top-k 归一化后,总和为 1)
浮点 权重 = 0.0f;
};
// Softmax朴素:数值稳定的 softmax(先减最大值再指数)
// 公式:softmax(z)_i = e^{z_i - max(z)} / Σ_j e^{z_j - max(z)}
// 参数:输入 = logits 数组;输出 = 概率数组(可与输入同一缓冲);数量 = 元素个数
void Softmax朴素(const float* 输入, float* 输出, size_t 数量);
// 路由选择:从门控输出选 top-k 专家并归一化权重
// 参数:门控输出 = 路由 logits(长度 = 专家数);专家数 = 全部专家个数(256);
// 激活数 = 要选择的专家个数(8)
// 返回:按权重降序的路由条目列表(长度 = 激活数),权重总和为 1
// 步骤:1. softmax → 2. 选 top-k(部分选择)→ 3. top-k 权重重新归一化(和为 1)
向量<路由条目> 路由选择(const float* 门控输出, size_t 专家数, size_t 激活数);
路由.cpp
cpp
// 路由.cpp ------ MoE 专家路由选择实现
// 用途:从路由门控输出(256 维 logits)选择 top-8 专家并计算归一化权重
// 说明:路由决策是 MoE 的核心------每个 token 只激活一小部分专家(8/256),
// 其余专家权重不参与计算(稀疏计算,这是 MoE 效率的关键)
// 数学:
// p = softmax(W_gate · x) ------ 路由概率分布(256 维)
// 选 top-k(k=8):保留概率最大的 8 个专家
// 权重重新归一化:w_e = p_e / Σ_{e∈top8} p_e ------ 保证权重和为 1
#include "内核/MoE/路由.h"
// 引入算法库(部分排序)
#include <algorithm>
// 引入标准头:数学函数(指数/最大)
#include <cmath>
// 引入错误处理(参数校验)
#include "公共/错误处理.h"
// Softmax朴素:数值稳定的 softmax(先减最大值再指数)
// 说明:减最大值保证 exp 不溢出(e^{大数} 会变 inf,减 max 后指数 ≤1)
void Softmax朴素(const float* 输入, float* 输出, size_t 数量) {
// 防御:数量必须 ≥1
if (数量 == 0) {
抛出运行错误("Softmax朴素:数量必须大于 0");
}
// 找最大值(数值稳定:减最大值后再指数)
float 最大值 = 输入[0];
for (size_t i = 1; i < 数量; ++i) {
if (输入[i] > 最大值) {
最大值 = 输入[i];
}
}
// 指数并求和
float 总和 = 0.0f;
for (size_t i = 0; i < 数量; ++i) {
输出[i] = std::exp(输入[i] - 最大值);
总和 += 输出[i];
}
// 归一化(总和可能为 0 仅当全部输入为 -inf,防御除零)
if (总和 > 0.0f) {
for (size_t i = 0; i < 数量; ++i) {
输出[i] /= 总和;
}
} else {
// 全部 -inf 的退化情况:均匀分布(防御)
for (size_t i = 0; i < 数量; ++i) {
输出[i] = 1.0f / static_cast<float>(数量);
}
}
}
// 路由选择:从门控输出选 top-k 专家并归一化权重
// 步骤:1. softmax → 2. 选 top-k(部分选择)→ 3. top-k 权重重新归一化(和为 1)
向量<路由条目> 路由选择(const float* 门控输出, size_t 专家数, size_t 激活数) {
// 防御:参数合法
if (专家数 == 0 || 激活数 == 0) {
抛出运行错误("路由选择:专家数与激活数必须大于 0");
}
// 激活数不能超过专家总数
const size_t 有效激活数 = std::min(激活数, 专家数);
// 步骤 1:softmax 得到路由概率
向量<浮点> 概率(专家数);
Softmax朴素(门控输出, 概率.data(), 专家数);
// 步骤 2:选 top-k(按概率降序取前 k 个专家的索引)
// 用部分排序:构造 (概率, 索引) 对,排序后取前 k
向量<std::pair<浮点, size_t>> 候选(专家数);
for (size_t i = 0; i < 专家数; ++i) {
候选[i] = {概率[i], i};
}
// 降序部分排序(最大的 有效激活数 个在前)
std::partial_sort(候选.begin(), 候选.begin() + 有效激活数, 候选.end(),
[](const auto& a, const auto& b) { return a.first > b.first; });
// 步骤 3:top-k 权重重新归一化(和为 1)
浮点 权重总和 = 0.0f;
for (size_t i = 0; i < 有效激活数; ++i) {
权重总和 += 候选[i].first;
}
// 防御:权重总和为 0(全 -inf 退化)时均匀分配
const 浮点 归一化系数 = (权重总和 > 0.0f) ? 1.0f / 权重总和 : 1.0f / static_cast<浮点>(有效激活数);
// 构建返回列表(按权重降序)
向量<路由条目> 结果(有效激活数);
for (size_t i = 0; i < 有效激活数; ++i) {
结果[i].专家索引 = static_cast<无符号>(候选[i].second);
结果[i].权重 = 候选[i].first * 归一化系数;
}
return 结果;
}