Skip to content

Commit 2c5a844

Browse files
committed
feat: add MoE inference and expert parallel support
Add reusable MoE routing, dispatch, expert, and runner layers for sparse MoE models. Wire Qwen3/DeepSeek MoE implementations to the shared MoE path, expose EP backend configuration through DistConfig-backed bench/server/LLM entrypoints, and keep graph replay output handling compatible with MoE execution.
1 parent afb5f66 commit 2c5a844

63 files changed

Lines changed: 1907 additions & 220 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎csrc/config/model_config.hpp‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
#include "infinicore/ops.hpp"
66
#include "quant_config.hpp"
77
#include <fstream>
8+
#include <initializer_list>
89
#include <iostream>
910
#include <string>
11+
#include <string_view>
1012
#include <vector>
1113

1214
namespace infinilm::config {
@@ -38,16 +40,33 @@ class ModelConfig {
3840

3941
template <typename T>
4042
T get_or(const std::string &key, const T &default_value) const {
41-
if (!config_json.contains(key) || config_json.at(key).is_null()) {
42-
return default_value;
43-
}
44-
try {
45-
return config_json.at(key).get<T>();
46-
} catch (const nlohmann::json::type_error &) {
47-
// If type conversion fails, return default value
48-
return default_value;
43+
return get_or<T>({std::string_view(key)}, default_value);
44+
}
45+
46+
template <typename T>
47+
T get_or(std::initializer_list<std::string_view> keys, const T &default_value) const {
48+
for (std::string_view key : keys) {
49+
if (key.empty()) {
50+
continue;
51+
}
52+
auto it = config_json.find(std::string(key));
53+
if (it == config_json.end() || it->is_null()) {
54+
continue;
55+
}
56+
try {
57+
return it->get<T>();
58+
} catch (const nlohmann::json::type_error &) {
59+
return default_value;
60+
}
4961
}
62+
return default_value;
5063
}
64+
65+
template <typename T>
66+
T get_or_alias(const std::string &key, const std::string &alias, const T &default_value) const {
67+
return get_or<T>({key, alias}, default_value);
68+
}
69+
5170
size_t get_kv_dim() const {
5271
return get<size_t>("hidden_size") * get<size_t>("num_key_value_heads") / get<size_t>("num_attention_heads");
5372
}

‎csrc/engine/compiler/paged_compiler.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,15 @@ void PagedCompiler::compile() {
8080
auto input = make_decode_input(b);
8181

8282
barrier_->wait();
83+
(void)model_->forward(input);
84+
infinicore::context::syncStream();
8385
// Capture must not start with stale Marlin locks from previous
8486
// warmup/capture attempts. This reset is intentionally outside
8587
// graph capture; the current implementation still pays a memset
8688
// before every graph replay in get_compiled().
8789
model_->reset_runtime_state();
8890
infinicore::context::syncStream();
91+
8992
infinicore::context::startGraphRecording();
9093
auto output = model_->forward(input);
9194
auto graph = infinicore::context::stopGraphRecording();

‎csrc/engine/compiler/static_batching_compiler.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,18 @@ void StaticBatchingCompiler::compile() {
2929
};
3030

3131
barrier_->wait();
32+
(void)model_->forward(input);
33+
infinicore::context::syncStream();
34+
3235
infinicore::context::startGraphRecording();
3336
auto output = model_->forward(input);
3437
auto graph = infinicore::context::stopGraphRecording();
3538
barrier_->wait();
3639

3740
auto shared_output = std::shared_ptr<InfinilmModel::Output>(new InfinilmModel::Output{infinicore::graph::GraphTensor(output.logits)});
41+
auto replay_output = std::shared_ptr<InfinilmModel::Output>(new InfinilmModel::Output{shared_output->logits->resume_from_blob_()});
3842

39-
compiled_map_[std::make_tuple(b, 1)] = CompiledResult{std::move(input), std::make_tuple(graph, shared_output)};
43+
compiled_map_[std::make_tuple(b, 1)] = CompiledResult{std::move(input), std::make_tuple(graph, shared_output), replay_output};
4044
}
4145
}
4246

@@ -56,7 +60,7 @@ StaticBatchingCompiler::Compiled StaticBatchingCompiler::get_compiled(
5660
graph_input.total_sequence_lengths.value()->copy_from(input.total_sequence_lengths.value());
5761

5862
auto graph = std::get<0>(result->second.compiled);
59-
auto shared_output = std::shared_ptr<InfinilmModel::Output>(new InfinilmModel::Output{std::get<1>(result->second.compiled)->logits->resume_from_blob_()});
63+
auto shared_output = result->second.replay_output;
6064
return std::make_tuple(graph, shared_output);
6165
}
6266
} else {

‎csrc/engine/compiler/static_batching_compiler.hpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class StaticBatchingCompiler : public GraphCompiler {
2525
struct CompiledResult {
2626
InfinilmModel::Input input;
2727
Compiled compiled;
28+
// Graph capture stores a GraphTensor in compiled. Replay returns a
29+
// normal output handle restored from the same graph output blob.
30+
std::shared_ptr<InfinilmModel::Output> replay_output;
2831
};
2932

3033
std::unordered_map<

‎csrc/engine/distributed/dist_config.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ DistConfig::operator std::string() const {
2222
repr += ", ";
2323
}
2424
}
25-
repr += "])";
25+
repr += "], moe_ep_backend=" + moe_ep_backend + ", moe_ep_size=" + std::to_string(moe_ep_size) + ")";
2626
return repr;
2727
}
2828

‎csrc/engine/distributed/dist_config.hpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <cstddef>
34
#include <string>
45
#include <vector>
56

@@ -8,6 +9,8 @@ namespace infinilm::engine::distributed {
89
struct DistConfig {
910
// Device IDs for each rank in tensor parallelism
1011
std::vector<int> tp_device_ids;
12+
std::string moe_ep_backend{"disabled"};
13+
size_t moe_ep_size{1};
1114

1215
DistConfig();
1316
explicit DistConfig(int tp_size);

‎csrc/engine/infer_engine.cpp‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "spdlog/spdlog.h"
44
#include <future>
55
#include <stdexcept>
6+
#include <unordered_set>
67

78
namespace infinilm::engine {
89

@@ -32,7 +33,12 @@ InferEngine::InferEngine(
3233

3334
// Load model config if model_path is provided, model_path must be valid, and config.json exists
3435
this->model_config_ = infinilm::config::ConfigFactory::createConfig(config_str);
35-
auto infinilm_config = std::make_shared<infinilm::global_state::InfinilmConfig>(attention_backend, this->model_config_, use_mla);
36+
auto infinilm_config = std::make_shared<infinilm::global_state::InfinilmConfig>(
37+
attention_backend,
38+
this->model_config_,
39+
use_mla,
40+
distributed_config.moe_ep_backend,
41+
distributed_config.moe_ep_size);
3642

3743
// Only support offline int8 kv cache quantization in this version
3844
if (kv_cache_dtype.has_value()) {
@@ -67,19 +73,19 @@ void InferEngine::load_param(const std::string &name, const infinicore::Tensor &
6773
}
6874
}
6975

70-
void InferEngine::load_params(const std::unordered_map<std::string, infinicore::Tensor> &params) {
76+
void InferEngine::load_params(const std::unordered_map<std::string, infinicore::Tensor> &params, bool strict) {
7177
if (workers_.size() <= 1 || weight_load_mode_ == "sync") {
7278
for (auto &worker : workers_) {
73-
worker->load_params(params);
79+
worker->load_params(params, strict);
7480
}
7581
return;
7682
}
7783

7884
std::vector<std::future<void>> futures;
7985
futures.reserve(workers_.size());
8086
for (auto &worker : workers_) {
81-
futures.emplace_back(std::async(std::launch::async, [&worker, &params] {
82-
worker->load_params(params);
87+
futures.emplace_back(std::async(std::launch::async, [&worker, &params, strict] {
88+
worker->load_params(params, strict);
8389
}));
8490
}
8591
for (auto &future : futures) {
@@ -118,7 +124,17 @@ std::vector<std::string> InferEngine::state_dict_keys() {
118124
if (0 == workers_.size()) {
119125
throw std::runtime_error(" Model object not found. ");
120126
}
121-
return workers_.front()->state_dict_keys();
127+
std::vector<std::string> ordered_keys;
128+
std::unordered_set<std::string> seen_keys;
129+
for (auto &worker : workers_) {
130+
for (const auto &key : worker->state_dict_keys()) {
131+
// Preserve first-seen worker order while removing duplicate TP keys.
132+
if (seen_keys.emplace(key).second) {
133+
ordered_keys.push_back(key);
134+
}
135+
}
136+
}
137+
return ordered_keys;
122138
}
123139

124140
//------------------------------------------------------

‎csrc/engine/infer_engine.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "rank_barrier.hpp"
99
#include "rank_worker.hpp"
1010

11+
#include <cstddef>
1112
#include <optional>
1213
#include <string>
1314
#include <unordered_map>
@@ -37,7 +38,7 @@ class InferEngine {
3738
void load_param(const std::string &name, const infinicore::Tensor &param);
3839

3940
// Load a batch of parameters to all workers, syncing each worker once after the batch.
40-
void load_params(const std::unordered_map<std::string, infinicore::Tensor> &params);
41+
void load_params(const std::unordered_map<std::string, infinicore::Tensor> &params, bool strict = true);
4142

4243
// process the weights after loading on all workers (e.g., for quantization)
4344
void process_weights_after_loading();

‎csrc/engine/rank_worker.cpp‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ void RankWorker::load_param(const std::string &name,
8787
//------------------------------------------------------
8888
// load_params -- synchronous batch load
8989
//------------------------------------------------------
90-
void RankWorker::load_params(const std::unordered_map<std::string, infinicore::Tensor> &params) {
90+
void RankWorker::load_params(const std::unordered_map<std::string, infinicore::Tensor> &params, bool strict) {
9191
{
9292
std::lock_guard<std::mutex> lock(mutex_);
9393
if (should_exit_) {
9494
throw std::runtime_error("RankWorker is closing; cannot load_params");
9595
}
9696

9797
pending_params_ = params;
98+
pending_params_strict_ = strict;
9899
job_cmd_ = Command::LOAD_BATCH;
99100
has_job_ = true;
100101
job_done_ = false;
@@ -295,6 +296,7 @@ void RankWorker::thread_loop() {
295296
std::string local_param_name;
296297
infinicore::Tensor local_param;
297298
std::unordered_map<std::string, infinicore::Tensor> local_params;
299+
bool local_params_strict = true;
298300
Input local_args;
299301
std::unique_ptr<cache::CacheConfig> local_cache_config;
300302

@@ -314,6 +316,10 @@ void RankWorker::thread_loop() {
314316
local_param = pending_param_;
315317
} else if (local_cmd == Command::LOAD_BATCH) {
316318
local_params = std::move(pending_params_);
319+
// strict is copied with the batch because loading runs on
320+
// the worker thread after the caller releases the mutex.
321+
local_params_strict = pending_params_strict_;
322+
pending_params_strict_ = true;
317323
pending_params_.clear();
318324
} else if (local_cmd == Command::PREPROCESS) {
319325

@@ -353,7 +359,7 @@ void RankWorker::thread_loop() {
353359

354360
} else if (local_cmd == Command::LOAD_BATCH) {
355361
try {
356-
model_->load_parameters_no_sync(local_params);
362+
model_->load_parameters_no_sync(local_params, local_params_strict);
357363
infinicore::context::syncStream();
358364
} catch (const std::exception &e) {
359365
{

‎csrc/engine/rank_worker.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class RankWorker {
8787
void load_param(const std::string &name,
8888
const infinicore::Tensor &param);
8989

90-
void load_params(const std::unordered_map<std::string, infinicore::Tensor> &params);
90+
void load_params(const std::unordered_map<std::string, infinicore::Tensor> &params, bool strict = true);
9191

9292
void process_weights_after_loading();
9393

@@ -150,6 +150,7 @@ class RankWorker {
150150
std::string pending_param_name_;
151151
infinicore::Tensor pending_param_;
152152
std::unordered_map<std::string, infinicore::Tensor> pending_params_;
153+
bool pending_params_strict_ = true;
153154
Input pending_args_;
154155
std::unique_ptr<cache::CacheConfig> pending_cache_config_;
155156

0 commit comments

Comments
 (0)