Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ add_library(dflash_common STATIC
src/common/dflash_spec_decode.cpp
src/common/layer_split_backend.cpp
src/common/layer_split_runtime.cpp
src/common/expert_split_plan.cpp
src/common/expert_split_state.cpp
src/common/expert_split_runtime.cpp
src/common/expert_split_compute_runtime.cpp
src/common/expert_split_materialization.cpp
src/common/expert_split_target_config.cpp
src/common/gguf_tensor_data.cpp
src/qwen35/graph_builders.cpp
src/qwen35moe/qwen35moe_ffn.cpp
src/qwen35moe/qwen35moe_backend.cpp
Expand All @@ -281,7 +288,9 @@ add_library(dflash_common STATIC
src/common/spark_corpus.cpp
src/common/moe_hybrid_ffn_eval.cpp
src/common/moe_hybrid_stream.cpp
src/common/cold_ffn_cpu.cpp
src/common/moe_expert_compute.cpp
src/common/moe_expert_compute_cpu.cpp
src/common/moe_expert_compute_ipc.cpp
src/common/moe_hybrid_swap_manager.cpp
src/common/moe_routing_collector.cpp
src/qwen35/layer_split_forward.cpp
Expand Down Expand Up @@ -728,6 +737,38 @@ if(DFLASH27B_TESTS)
endif()
target_link_libraries(test_qwen35moe_swap_manager PRIVATE dflash_common)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_expert_split_plan.cpp")
add_executable(test_expert_split_plan test/test_expert_split_plan.cpp)
target_include_directories(test_expert_split_plan PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS})
if(DFLASH27B_GPU_BACKEND STREQUAL "cuda")
target_include_directories(test_expert_split_plan PRIVATE ${CUDAToolkit_INCLUDE_DIRS})
endif()
target_link_libraries(test_expert_split_plan PRIVATE dflash_common)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_expert_split_runtime.cpp")
add_executable(test_expert_split_runtime test/test_expert_split_runtime.cpp)
target_include_directories(test_expert_split_runtime PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS})
if(DFLASH27B_GPU_BACKEND STREQUAL "cuda")
target_include_directories(test_expert_split_runtime PRIVATE ${CUDAToolkit_INCLUDE_DIRS})
endif()
target_link_libraries(test_expert_split_runtime PRIVATE dflash_common)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_expert_split_target_config.cpp")
add_executable(test_expert_split_target_config test/test_expert_split_target_config.cpp)
target_include_directories(test_expert_split_target_config PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS})
if(DFLASH27B_GPU_BACKEND STREQUAL "cuda")
target_include_directories(test_expert_split_target_config PRIVATE ${CUDAToolkit_INCLUDE_DIRS})
endif()
target_link_libraries(test_expert_split_target_config PRIVATE dflash_common)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_moe_expert_compute_multi_target.cpp")
add_executable(test_moe_expert_compute_multi_target test/test_moe_expert_compute_multi_target.cpp)
target_include_directories(test_moe_expert_compute_multi_target PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS})
if(DFLASH27B_GPU_BACKEND STREQUAL "cuda")
target_include_directories(test_moe_expert_compute_multi_target PRIVATE ${CUDAToolkit_INCLUDE_DIRS})
endif()
target_link_libraries(test_moe_expert_compute_multi_target PRIVATE dflash_common)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/smoke_load_draft.cpp")
add_executable(smoke_load_draft test/smoke_load_draft.cpp)
target_include_directories(smoke_load_draft PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS})
Expand Down
54 changes: 48 additions & 6 deletions server/src/common/backend_ipc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const char * backend_ipc_mode_name(BackendIpcMode mode) {
case BackendIpcMode::Qwen35TargetShard: return "qwen35-target-shard";
case BackendIpcMode::Gemma4TargetShard: return "gemma4-target-shard";
case BackendIpcMode::LagunaTargetShard: return "laguna-target-shard";
case BackendIpcMode::MoeExpertCompute: return "moe-expert-compute";
}
return "unknown";
}
Expand All @@ -57,6 +58,10 @@ bool parse_backend_ipc_mode(const std::string & value, BackendIpcMode & out) {
out = BackendIpcMode::LagunaTargetShard;
return true;
}
if (value == "moe-expert-compute") {
out = BackendIpcMode::MoeExpertCompute;
return true;
}
return false;
}

Expand Down Expand Up @@ -257,20 +262,57 @@ std::string BackendIpcProcess::next_path(const char * prefix) {
}

bool BackendIpcProcess::write_shared_payload(const void * data, size_t bytes, uint64_t & seq) {
if (!shared_payload_map_ || bytes > shared_payload_capacity_) return false;
if (bytes > 0 && !data) return false;
BackendIpcPayloadSegment segment{data, bytes};
return write_shared_payload_segments(&segment, 1, seq);
}

bool BackendIpcProcess::write_shared_payload_segments(
const BackendIpcPayloadSegment * segments,
size_t n_segments,
uint64_t & seq) {
if (!shared_payload_map_ || (!segments && n_segments > 0)) return false;
size_t bytes = 0;
for (size_t i = 0; i < n_segments; ++i) {
if (segments[i].bytes > 0 && !segments[i].data) return false;
if (!backend_ipc_checked_add_size(bytes, segments[i].bytes, bytes)) {
return false;
}
}
if (bytes > shared_payload_capacity_) return false;
auto * header = static_cast<BackendIpcSharedPayloadHeader *>(shared_payload_map_);
void * payload = static_cast<void *>(
static_cast<char *>(shared_payload_map_) + backend_ipc_shared_payload_header_bytes());
if (bytes > 0) {
std::memcpy(payload, data, bytes);
auto * payload = static_cast<char *>(
static_cast<char *>(shared_payload_map_) +
backend_ipc_shared_payload_header_bytes());
size_t off = 0;
for (size_t i = 0; i < n_segments; ++i) {
if (segments[i].bytes > 0) {
std::memcpy(payload + off, segments[i].data, segments[i].bytes);
off += segments[i].bytes;
}
}
seq = ++shared_payload_seq_;
header->bytes = (uint64_t)bytes;
header->sequence = seq;
return true;
}

bool BackendIpcProcess::read_shared_payload(void * data, size_t bytes, uint64_t seq) const {
if (!shared_payload_map_ || bytes > shared_payload_capacity_) return false;
if (bytes > 0 && !data) return false;
const auto * header =
static_cast<const BackendIpcSharedPayloadHeader *>(shared_payload_map_);
if (header->sequence != seq || header->bytes != (uint64_t)bytes) {
return false;
}
const void * payload = static_cast<const void *>(
static_cast<const char *>(shared_payload_map_) +
backend_ipc_shared_payload_header_bytes());
if (bytes > 0) {
std::memcpy(data, payload, bytes);
}
return true;
}

#if !defined(_WIN32)
bool BackendIpcProcess::init_shared_payload(size_t bytes) {
if (bytes == 0) return false;
Expand Down
10 changes: 10 additions & 0 deletions server/src/common/backend_ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum class BackendIpcMode {
Qwen35TargetShard,
Gemma4TargetShard,
LagunaTargetShard,
MoeExpertCompute,
};

const char * backend_ipc_mode_name(BackendIpcMode mode);
Expand Down Expand Up @@ -82,6 +83,11 @@ struct BackendIpcLaunchConfig {
size_t shared_payload_bytes = 0;
};

struct BackendIpcPayloadSegment {
const void * data = nullptr;
size_t bytes = 0;
};

class BackendIpcProcess {
public:
BackendIpcProcess() = default;
Expand All @@ -105,6 +111,10 @@ class BackendIpcProcess {

std::string next_path(const char * prefix);
bool write_shared_payload(const void * data, size_t bytes, uint64_t & seq);
bool write_shared_payload_segments(const BackendIpcPayloadSegment * segments,
size_t n_segments,
uint64_t & seq);
bool read_shared_payload(void * data, size_t bytes, uint64_t seq) const;

private:
#if !defined(_WIN32)
Expand Down
62 changes: 11 additions & 51 deletions server/src/common/cold_ffn_compute.h
Original file line number Diff line number Diff line change
@@ -1,59 +1,19 @@
// ColdFfnCompute: Direct compute interface for cold expert FFN.
// Bypasses ggml graph dispatch overhead. Shared-memory model (CPU/Halo).
// Compatibility shim for the old cold-FFN name.
//
// New code should include moe_expert_compute.h directly. The aliases keep
// existing call sites and downstream branches buildable while the compute
// abstraction moves from "cold expert fallback" to neutral MoE expert compute.
#pragma once

#include "ggml.h"
#include <cstdint>
#include <memory>
#include "moe_expert_compute.h"

namespace dflash::common {

// Per-layer cold weight metadata — raw pointers into shared memory.
struct ColdFfnLayer {
const void * gate_up_data = nullptr; // fused [n_cold, n_ff*2, n_embd] quantized
const void * gate_data = nullptr; // separate gate [n_cold, n_ff, n_embd]
const void * up_data = nullptr; // separate up [n_cold, n_ff, n_embd]
const void * down_data = nullptr; // [n_cold, n_embd, n_ff] quantized
using ColdFfnLayer = MoeExpertLayer;
using ColdFfnCompute = MoeExpertCompute;

size_t gate_up_stride = 0; // bytes between experts in gate_up tensor
size_t gate_stride = 0; // bytes between experts in gate tensor
size_t up_stride = 0; // bytes between experts in up tensor
size_t down_stride = 0; // bytes between experts in down tensor

ggml_type gate_up_type = GGML_TYPE_Q4_K; // type for fused gate_up
ggml_type gate_type = GGML_TYPE_Q4_K; // type for separate gate
ggml_type up_type = GGML_TYPE_Q4_K; // type for separate up
ggml_type down_type = GGML_TYPE_Q4_K; // type for down projection
bool fused_gate_up = false; // true if gate+up are fused

// Scale factors (applied after matmul). 1.0 = no scaling.
float gate_up_scale = 1.0f;
float gate_scale = 1.0f;
float up_scale = 1.0f;
float down_scale = 1.0f;
};

// Abstract compute interface. Implementations: CPU (now), Halo (future).
struct ColdFfnCompute {
virtual ~ColdFfnCompute() = default;

// Compute cold expert FFN contributions and accumulate into output.
// input: [n_embd] F32 — post-norm hidden state
// ids: [n_cold] I32 — local cold expert indices
// weights: [n_cold] F32 — routing weights for each cold expert
// output: [n_embd] F32 — accumulated weighted expert outputs (zeroed by callee)
virtual void compute(
const ColdFfnLayer & layer,
const float * input,
const int32_t * ids,
const float * weights,
int n_cold,
int n_embd,
int n_ff,
float * output) = 0;
};

// Create CPU-based fused cold FFN compute.
std::unique_ptr<ColdFfnCompute> make_cpu_cold_ffn_compute(int n_ff_max);
inline std::unique_ptr<ColdFfnCompute> make_cpu_cold_ffn_compute(int n_ff_max) {
return make_cpu_moe_expert_compute(n_ff_max);
}

} // namespace dflash::common
122 changes: 122 additions & 0 deletions server/src/common/expert_split_compute_runtime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#include "expert_split_compute_runtime.h"

#include <utility>

namespace dflash::common {

namespace {

uint64_t hash_u64(uint64_t h, uint64_t v) {
h ^= v + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
return h;
}

uint64_t hash_string(uint64_t h, const std::string & s) {
for (unsigned char c : s) {
h = hash_u64(h, (uint64_t)c);
}
return h;
}

} // namespace

bool ExpertSplitComputeRuntime::matches(int n_layer_, int n_expert_, int n_expert_used_) const {
return n_layer == n_layer_ &&
n_expert == n_expert_ &&
n_expert_used == n_expert_used_ &&
(int) target_index_by_global.size() == n_layer * n_expert &&
(int) local_index_by_global.size() == n_layer * n_expert;
}

int ExpertSplitComputeRuntime::index(int layer, int expert) const {
if (layer < 0 || layer >= n_layer || expert < 0 || expert >= n_expert) {
return -1;
}
return layer * n_expert + expert;
}

int ExpertSplitComputeRuntime::target_index(int layer, int expert) const {
const int idx = index(layer, expert);
return idx >= 0 ? target_index_by_global[(size_t)idx] : -1;
}

int ExpertSplitComputeRuntime::local_index(int layer, int expert) const {
const int idx = index(layer, expert);
return idx >= 0 ? local_index_by_global[(size_t)idx] : -1;
}

bool build_expert_split_compute_runtime(const ExpertSplitRuntime & runtime,
int n_expert_used,
ExpertSplitComputeRuntime & out,
std::string * err) {
if (!runtime.matches(runtime.n_layer, runtime.n_expert) ||
runtime.n_layer <= 0 || runtime.n_expert <= 0) {
if (err) *err = "expert split runtime not initialized";
return false;
}
if (n_expert_used <= 0 || n_expert_used > runtime.n_expert) {
if (err) *err = "invalid n_expert_used for expert split compute runtime";
return false;
}

std::vector<ExpertSplitTargetPlacement> placements;
if (!build_all_expert_split_target_placements(runtime, n_expert_used, placements, err)) {
return false;
}
if (placements.size() != runtime.targets.size()) {
if (err) *err = "expert split target placement count mismatch";
return false;
}

ExpertSplitComputeRuntime compute_runtime;
compute_runtime.n_layer = runtime.n_layer;
compute_runtime.n_expert = runtime.n_expert;
compute_runtime.n_expert_used = n_expert_used;
compute_runtime.target_index_by_global = runtime.target_index_by_global;
compute_runtime.local_index_by_global = runtime.local_index_by_global;
compute_runtime.targets.reserve(runtime.targets.size());

for (size_t i = 0; i < runtime.targets.size(); ++i) {
ExpertSplitComputeTargetRuntime target_runtime;
target_runtime.target_index = (int) i;
target_runtime.target = runtime.targets[i].target;
target_runtime.placement = std::move(placements[i].placement);
compute_runtime.targets.push_back(std::move(target_runtime));
}

out = std::move(compute_runtime);
return true;
}

uint64_t expert_split_compute_runtime_fingerprint(
const ExpertSplitComputeRuntime & runtime) {
uint64_t h = 1469598103934665603ULL;
h = hash_u64(h, (uint64_t)runtime.n_layer);
h = hash_u64(h, (uint64_t)runtime.n_expert);
h = hash_u64(h, (uint64_t)runtime.n_expert_used);
h = hash_u64(h, (uint64_t)runtime.targets.size());
for (size_t ti = 0; ti < runtime.targets.size(); ++ti) {
const ExpertSplitComputeTargetRuntime & target = runtime.targets[ti];
h = hash_u64(h, (uint64_t)ti);
h = hash_string(h, target.target.name);
h = hash_string(h, target.target.backend);
h = hash_u64(h, (uint64_t)(int64_t)target.target.device_id);
h = hash_u64(h, target.placement.n_layer);
h = hash_u64(h, target.placement.n_expert);
h = hash_u64(h, target.placement.n_expert_used);
h = hash_u64(h, target.placement.total_hot);
h = hash_u64(h, (uint64_t)target.placement.hot_counts.size());
for (int hot_count : target.placement.hot_counts) {
h = hash_u64(h, (uint64_t)hot_count);
}
for (size_t il = 0; il < target.placement.hot_expert_ids.size(); ++il) {
h = hash_u64(h, (uint64_t)il);
for (int32_t expert : target.placement.hot_expert_ids[il]) {
h = hash_u64(h, (uint64_t)(uint32_t)expert);
}
}
}
return h;
}

} // namespace dflash::common
Loading
Loading