Skip to content
Open
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
1 change: 1 addition & 0 deletions server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ add_library(dflash_common STATIC
src/qwen35/graph_builders.cpp
src/qwen35moe/qwen35moe_ffn.cpp
src/qwen35moe/qwen35moe_backend.cpp
src/qwen35moe/qwen35moe_layer_split_adapter.cpp
src/qwen35moe/qwen35moe_daemon.cpp
src/qwen35moe/qwen35moe_pipelined_decode.cpp
# ── Common MoE hybrid infrastructure ──
Expand Down
31 changes: 30 additions & 1 deletion server/src/common/backend_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "qwen35_backend.h"
#include "qwen35moe_backend.h"
#include "qwen35moe_layer_split_adapter.h"
#include "laguna_backend.h"
#include "laguna_layer_split_adapter.h"
#include "qwen3_backend.h"
Expand All @@ -24,7 +25,7 @@ std::string detect_arch(const char * model_path) {
}

bool arch_supports_remote_draft(const std::string & arch) {
return arch == "qwen35";
return arch == "qwen35" || arch == "qwen35moe";
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}

bool arch_supports_pflash_compression(const std::string & arch) {
Expand Down Expand Up @@ -101,11 +102,39 @@ std::unique_ptr<ModelBackend> create_backend(const BackendArgs & args) {
return backend;

} else if (arch == "qwen35moe") {
if (args.device.is_layer_split()) {
Qwen35MoeLayerSplitAdapterConfig cfg;
cfg.target_path = args.model_path;
cfg.draft_path = args.draft_path;
cfg.device = args.device;
cfg.draft_gpu = args.draft_device.gpu;
cfg.remote_draft = args.remote_draft;
cfg.remote_target_shard = args.remote_target_shard;
cfg.fa_window = args.fa_window;
cfg.kq_stride_pad = args.kq_stride_pad;
cfg.draft_swa_window = args.draft_swa_window;
cfg.draft_ctx_max = args.draft_ctx_max;
cfg.chunk = args.chunk;
cfg.max_verify_tokens = args.ddtree_mode
? std::max<int>(DFLASH27B_DRAFT_BLOCK_SIZE, args.ddtree_budget + 1)
: DFLASH27B_DRAFT_BLOCK_SIZE;
cfg.run_dflash = args.draft_path != nullptr;

auto adapter = std::make_unique<Qwen35MoeLayerSplitAdapter>(cfg);
auto backend = std::make_unique<LayerSplitBackend>(std::move(adapter));
if (!backend->init()) {
std::fprintf(stderr, "[backend_factory] LayerSplitBackend(qwen35moe) init failed\n");
return nullptr;
}
return backend;
}

Qwen35Config cfg;
cfg.target_path = args.model_path;
cfg.draft_path = args.draft_path;
cfg.device = args.device;
cfg.draft_gpu = args.draft_device.gpu;
cfg.remote_draft = args.remote_draft;
cfg.stream_fd = args.stream_fd;
cfg.fa_window = args.fa_window;
cfg.kq_stride_pad = args.kq_stride_pad;
Expand Down
95 changes: 95 additions & 0 deletions server/src/qwen35/graph_builders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,101 @@ bool build_layer_step(
return ggml_gallocr_alloc_graph(sg.alloc, sg.gf);
}

bool build_layer_range_step(
StepGraph & sg,
const TargetWeights & w,
TargetCache & cache,
ggml_backend_t backend,
int layer_begin,
int layer_end,
ggml_tensor * act_in,
ggml_tensor * act_out,
int chunk_start,
int n_tokens,
int kv_start,
bool with_mask,
int fa_window,
int kq_stride_pad,
bool kvflash) {
if (kvflash) with_mask = true;
if (layer_begin < 0 || layer_end > w.n_layer || layer_begin >= layer_end) {
return false;
}
step_graph_free(sg);

ggml_init_params ip{};
ip.mem_size = 512 * 1024 * 1024;
ip.mem_buffer = nullptr;
ip.no_alloc = true;
sg.ctx = ggml_init(ip);
if (!sg.ctx) return false;

const int hidden = w.n_embd;
bool has_attn = false;
for (int il = layer_begin; il < layer_end; ++il) {
if (((il + 1) % w.full_attention_interval) == 0) {
has_attn = true;
break;
}
}

sg.inp_embed = ggml_view_2d(sg.ctx, act_in,
hidden, n_tokens,
act_in->nb[1], (size_t)chunk_start * act_in->nb[1]);
ggml_set_name(sg.inp_embed, "inp_embed");
ggml_set_input(sg.inp_embed);

if (has_attn) {
sg.positions = ggml_new_tensor_1d(sg.ctx, GGML_TYPE_I32, 4 * n_tokens);
ggml_set_name(sg.positions, "positions");
ggml_set_input(sg.positions);

if (with_mask) {
int phys_ctx = cache.max_ctx;
if (kvflash) {
for (ggml_tensor * t : cache.attn_k) {
if (t) { phys_ctx = std::min(phys_ctx, (int)t->ne[1]); break; }
}
}
const int max_win_len = phys_ctx + n_tokens;
const int kv_pad = align_up(max_win_len, kq_stride_pad);
const int q_pad = align_up(n_tokens, KQ_MASK_PAD);
sg.attn_mask = ggml_new_tensor_2d(sg.ctx, GGML_TYPE_F16, kv_pad, q_pad);
ggml_set_name(sg.attn_mask, "attn_mask");
ggml_set_input(sg.attn_mask);
}
if (kvflash) {
sg.kv_write_rows = ggml_new_tensor_2d(sg.ctx, GGML_TYPE_I64,
n_tokens, w.n_head_kv);
ggml_set_name(sg.kv_write_rows, "kv_write_rows");
ggml_set_input(sg.kv_write_rows);
}
}

sg.gf = ggml_new_graph_custom(sg.ctx, 16384, false);

ggml_tensor * cur = sg.inp_embed;
for (int il = layer_begin; il < layer_end; ++il) {
cur = build_qwen35_layer(
sg.ctx, sg.gf, w, cache, il,
cur, sg.positions, sg.attn_mask,
kv_start, n_tokens, /*capture=*/false, fa_window,
/*q_tail_capture=*/nullptr, /*q_tail_start=*/0,
nullptr, sg.kv_write_rows);
if (!cur) return false;
}

ggml_tensor * out_view = ggml_view_2d(sg.ctx, act_out,
hidden, n_tokens,
act_out->nb[1], (size_t)chunk_start * act_out->nb[1]);
ggml_build_forward_expand(sg.gf, ggml_cpy(sg.ctx, cur, out_view));

if (!sg.alloc) {
sg.alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
}
return ggml_gallocr_alloc_graph(sg.alloc, sg.gf);
}

bool build_layer_prefn_step(
StepGraph & sg,
const TargetWeights & w,
Expand Down
20 changes: 20 additions & 0 deletions server/src/qwen35/graph_builders.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ bool build_layer_step(
int kq_stride_pad = KQ_MASK_PAD,
bool kvflash = false);

// Layer-segmented prefill: process a contiguous layer range in one graph.
// This keeps simple layer-split semantics but avoids rebuilding/computing one
// ggml graph per layer when no per-layer feature capture is required.
bool build_layer_range_step(
StepGraph & sg,
const TargetWeights & w,
TargetCache & cache,
ggml_backend_t backend,
int layer_begin,
int layer_end,
ggml_tensor * act_in,
ggml_tensor * act_out,
int chunk_start,
int n_tokens,
int kv_start,
bool with_mask,
int fa_window = 0,
int kq_stride_pad = KQ_MASK_PAD,
bool kvflash = false);

// `kvflash`: pooled mode — KV rows go through a set_rows input
// (sg.kv_write_rows, [n_tokens, n_head_kv] ne0-major slots) and the mask
// (forced on) is sized to the PHYSICAL tensor capacity so the caller can
Expand Down
88 changes: 88 additions & 0 deletions server/src/qwen35/layer_split_forward.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,94 @@ bool run_qwen35_layer_split_layers_from_activation(
std::vector<uint16_t> mask_buf;
std::vector<int32_t> pos_buf;

const bool needs_layer_capture = captures_out || feature_ring || remote_draft;
if (!needs_layer_capture) {
for (auto & shard : shards) {
if (shard.layer_begin >= shard.layer_end) continue;
if (&shard != current_shard) {
ActivationPair next_acts;
if (!activation_pair_init(next_acts, shard.backend, hidden,
n_tokens_total, acts.type)) {
std::fprintf(stderr, "target-split activation alloc failed on gpu %d\n",
shard.gpu);
return false;
}
ggml_backend_synchronize(current_shard->backend);
ggml_backend_tensor_copy(act_in, next_acts.a);
ggml_backend_synchronize(shard.backend);
activation_pair_free(acts);
acts = next_acts;
act_in = acts.a;
act_out = acts.b;
current_shard = &shard;
}

for (int start = 0; start < n_tokens_total; start += ubatch) {
const int n = std::min(ubatch, n_tokens_total - start);
const int kv_start = base_pos + start;
const int kv_len = kv_start + n;
const bool with_mask = kvflash ||
(kq_stride_pad > KQ_MASK_PAD) || (n > 1);
if (kvflash && !kvflash_preallocated &&
!kvflash->alloc_span(kv_start, n)) {
return false;
}
if (!build_layer_range_step(
shard.layer_graph, shard.weights, shard.cache,
shard.backend, shard.layer_begin, shard.layer_end,
act_in, act_out, start, n, kv_start, with_mask,
fa_window, kq_stride_pad, kvflash != nullptr)) {
std::fprintf(stderr,
"target-split build layers=[%d,%d) @%d gpu=%d\n",
shard.layer_begin, shard.layer_end, start, shard.gpu);
return false;
}
if (shard.layer_graph.positions) {
pos_buf.assign((size_t)4 * n, 0);
for (int i = 0; i < n; i++) {
const int p = kv_start + i;
pos_buf[0 * n + i] = p;
pos_buf[1 * n + i] = p;
pos_buf[2 * n + i] = p;
pos_buf[3 * n + i] = 0;
}
ggml_backend_tensor_set(shard.layer_graph.positions, pos_buf.data(), 0,
sizeof(int32_t) * pos_buf.size());
}
if (kvflash) {
if (!fill_qwen35_kvflash_inputs(
shard.layer_graph, shard.weights, *kvflash,
kv_start, n)) {
return false;
}
} else if (with_mask && shard.layer_graph.attn_mask) {
const int win_start_l = (fa_window > 0 && kv_start > fa_window)
? (kv_start - fa_window) : 0;
const int win_len_l = kv_len - win_start_l;
const int kv_pad_override = (int)shard.layer_graph.attn_mask->ne[0];
build_causal_mask(mask_buf, win_len_l, n, kv_start, kq_stride_pad,
win_start_l, kv_pad_override);
ggml_backend_tensor_set(shard.layer_graph.attn_mask, mask_buf.data(), 0,
sizeof(uint16_t) * mask_buf.size());
}
auto st = ggml_backend_graph_compute(shard.backend, shard.layer_graph.gf);
if (st != GGML_STATUS_SUCCESS) {
std::fprintf(stderr,
"target-split compute layers=[%d,%d) @%d gpu=%d status=%d\n",
shard.layer_begin, shard.layer_end, start, shard.gpu,
(int)st);
return false;
}
}
std::swap(act_in, act_out);
}

if (act_in != acts.a) {
std::swap(acts.a, acts.b);
}
return true;
}

for (int il = shards.front().layer_begin; il < shards.back().layer_end; ++il) {
Qwen35LayerSplitShard * shard = find_layer_split_shard(shards, il);
if (!shard) {
Expand Down
Loading
Loading