MOE路由:路由(logits: top-k/8)

路由.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 结果;
}
相关推荐
用户9385156350713 分钟前
手写一个 LLM Harness 框架:用工程化手段把大模型幻觉踩在脚下
javascript·人工智能·后端
前端开发江鸟16 分钟前
我能解释 RAG、MCP 和 Eval,却画不出一条完整的 Agent 链路
人工智能
C++ 老炮儿的技术栈1 小时前
从 Qt Designer 属性编辑器的层级可以看到继承链
c语言·数据库·c++·qt·sqlite·visual studio
ivywriter1 小时前
【具身智能】物理AI具体指什么,和具身智能是什么关系?
人工智能
new_zhou1 小时前
C++ 项目 AI 协作指南(Windows / MSVC 环境)
c++·人工智能·windows
地平线开发者1 小时前
征程6工具链模型X86推理方式说明
算法
洛阳泰山1 小时前
AI 应用层被 Python 卷成红海,为什么我偏要用 Java 造一个 RAG + 工作流引擎?
java·人工智能·后端
地平线开发者1 小时前
【征程6】校准量化中HistogramObserver解析
算法
wangray1997droid2 小时前
让 AI 拥有“真实记忆“:一次从碎片到叙事的记忆系统质变
人工智能
一次旅行2 小时前
AI 前沿日报 | 2026年08月08日 星期六
人工智能