Skip to content

Commit d33c4c0

Browse files
committed
server: stabilize DFlash np2 scheduling
Prioritize pending DFlash prompts and initial profit baseline cycles before target-generation rows. Restrict multi-seq target verification to one row per slot because multi-row capture only preserves the final internal ubatch, and rotate multi-row verifier cycles across slots to keep np2 runs fair.
1 parent 61f3bb4 commit d33c4c0

2 files changed

Lines changed: 122 additions & 11 deletions

File tree

tests/test-dflash-plumbing.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,27 @@ int main(int argc, char ** argv) {
17661766
ok &= expect(server_context.find("dflash_multiseq_rows") != std::string::npos &&
17671767
server_context.find("uneven-rows") != std::string::npos,
17681768
"server must not multi-seq batch DFlash target views with uneven per-slot rows because GPU hidden capture is per-ubatch");
1769+
ok &= expect(server_context.find("dflash_batch_has_tg") != std::string::npos &&
1770+
server_context.find("!dflash_batch_has_tg && (params_base.cont_batching || batch.n_tokens == 0)") != std::string::npos,
1771+
"server must defer prompt batching when the current DFlash batch already contains TG verifier rows");
1772+
ok &= expect(server_context.find("dflash_try_reserve_tg_rows") != std::string::npos &&
1773+
server_context.find("dflash_skip_tg_slot_for_next_cycle") != std::string::npos &&
1774+
server_context.find("dflash uneven TG rows deferred") != std::string::npos,
1775+
"server must defer uneven DFlash TG slots instead of decoding a combined view with partial hidden capture");
1776+
ok &= expect(server_context.find("dflash_multi_seq_tg_rows_safe") != std::string::npos &&
1777+
server_context.find("rows == 1") != std::string::npos,
1778+
"server must only multi-seq batch DFlash target verification when each slot contributes one row");
1779+
ok &= expect(server_context.find("dflash_next_tg_slot") != std::string::npos &&
1780+
server_context.find("dflash_tg_slot_start") != std::string::npos,
1781+
"server must fairly rotate one-slot DFlash multi-row verifier cycles instead of always starting at slot 0");
1782+
ok &= expect(server_context.find("dflash_has_pending_prompt") != std::string::npos &&
1783+
server_context.find("dflash_has_pending_prompt && slot.can_speculate()") != std::string::npos &&
1784+
server_context.find("params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&\n needs_reeval &&\n std::any_of(slots.begin(), slots.end(), [](const server_slot & slot) {\n return slot.state == SLOT_STATE_STARTED || slot.state == SLOT_STATE_PROCESSING_PROMPT;\n })") == std::string::npos,
1785+
"DFlash must prioritize unfinished prompt prefill before TG rows on all target types, not only recurrent/hybrid targets");
1786+
ok &= expect(server_context.find("dflash_has_profit_baseline_slot") != std::string::npos &&
1787+
server_context.find("dflash_force_profit_baseline_cycle") != std::string::npos &&
1788+
server_context.find("dflash_force_profit_baseline_cycle && n_draft_max > 0") != std::string::npos,
1789+
"DFlash adaptive profit baseline slots must get one-row target cycles instead of starving behind multi-row verifier batches");
17691790
ok &= expect(common_h.find("int32_t dflash_max_slots = 0") != std::string::npos &&
17701791
server_context.find("params_base.speculative.dflash_max_slots > 0") != std::string::npos &&
17711792
server_context.find("DFlash enabled for all %d slots") != std::string::npos,
@@ -1786,7 +1807,7 @@ int main(int argc, char ** argv) {
17861807
ok &= expect(server_context.find("dflash_multislot_flat_accept_barrier") != std::string::npos &&
17871808
server_context.find("struct dflash_flat_accept_prefetch") != std::string::npos &&
17881809
server_context.find("common_speculative_update_logits_deferred_dflash_kv(slot.get_spec(), ctx_tgt, batch_tokens, prefetched.n_hidden_keep);") != std::string::npos &&
1789-
server_context.find("dflash_recurrent_has_pending_prompt") != std::string::npos &&
1810+
server_context.find("dflash_has_pending_prompt") != std::string::npos &&
17901811
server_context.find("dflash_recurrent_draft_rr") == std::string::npos &&
17911812
server_context.find("dflash_recurrent_cycle_slot_id") == std::string::npos,
17921813
"DFlash recurrent/hybrid multi-slot scheduling must isolate prompt prefill, pre-sample every flat speculative slot before rollback, and remove the old one-slot recurrent round-robin");

tools/server/server-context.cpp

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,7 @@ struct server_context_impl {
18791879
int trace = 0;
18801880
int slots_debug = 0;
18811881
int n_empty_consecutive = 0;
1882+
int dflash_next_tg_slot = 0;
18821883

18831884
std::unique_ptr<server_prompt_cache> prompt_cache;
18841885

@@ -4421,16 +4422,64 @@ struct server_context_impl {
44214422
std::vector<llama_tokens> batched_drafts(slots.size());
44224423
std::vector<std::vector<float>> batched_log_probs(slots.size());
44234424
const bool use_rejection_sampling = params_base.speculative.sample_temp > 0.0f && params_base.sampling.temp > 0.0f;
4424-
const bool dflash_recurrent_has_pending_prompt =
4425+
int dflash_tg_batch_rows = -1;
4426+
auto dflash_multi_seq_tg_rows_safe = [](int rows) {
4427+
return rows == 1;
4428+
};
4429+
auto dflash_try_reserve_tg_rows = [&](const server_slot & slot, int rows) {
4430+
if (params_base.speculative.type() != COMMON_SPECULATIVE_TYPE_DFLASH ||
4431+
params_base.speculative.branch_budget != 0 ||
4432+
rows <= 0) {
4433+
return true;
4434+
}
4435+
4436+
if (batch.n_tokens == 0 || dflash_tg_batch_rows < 0) {
4437+
dflash_tg_batch_rows = rows;
4438+
return true;
4439+
}
4440+
4441+
if (dflash_tg_batch_rows == rows && dflash_multi_seq_tg_rows_safe(rows)) {
4442+
return true;
4443+
}
4444+
4445+
if (dflash_server_crash_trace_enabled()) {
4446+
SLT_INF(slot, "dflash uneven TG rows deferred: reason=%s batch_rows=%d slot_rows=%d batch_tokens=%d\n",
4447+
dflash_tg_batch_rows == rows ? "multi-row" : "uneven",
4448+
dflash_tg_batch_rows, rows, batch.n_tokens);
4449+
}
4450+
return false;
4451+
};
4452+
auto dflash_skip_tg_slot_for_next_cycle = [](server_slot & slot) {
4453+
slot.spec_i_batch.clear();
4454+
slot.spec_pad_i_batch.clear();
4455+
slot.spec_draft.clear();
4456+
slot.draft_log_probs.clear();
4457+
};
4458+
const bool dflash_has_pending_prompt =
44254459
params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
4426-
needs_reeval &&
44274460
std::any_of(slots.begin(), slots.end(), [](const server_slot & slot) {
4428-
return slot.state == SLOT_STATE_STARTED || slot.state == SLOT_STATE_PROCESSING_PROMPT;
4461+
return slot.can_speculate() &&
4462+
(slot.state == SLOT_STATE_STARTED || slot.state == SLOT_STATE_PROCESSING_PROMPT);
44294463
});
4464+
const bool dflash_has_profit_baseline_slot =
4465+
params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
4466+
params_base.speculative.branch_budget == 0 &&
4467+
std::any_of(slots.begin(), slots.end(), [](const server_slot & slot) {
4468+
return slot.state == SLOT_STATE_GENERATING &&
4469+
slot.can_speculate() &&
4470+
slot.dm_adaptive &&
4471+
server_adaptive_dm_uses_profit_controller(slot.dm_controller) &&
4472+
(slot.profit_baseline_probe_pending ||
4473+
!slot.profit_baseline_ready() ||
4474+
slot.adaptive_n_max < 0);
4475+
});
4476+
const bool dflash_force_profit_baseline_cycle =
4477+
dflash_has_profit_baseline_slot && !dflash_has_pending_prompt;
44304478
std::vector<int> dflash_cohort_n_draft_max(slots.size(), -1);
44314479
if (params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
44324480
params_base.speculative.branch_budget == 0 &&
4433-
!dflash_recurrent_has_pending_prompt) {
4481+
!dflash_has_pending_prompt &&
4482+
!dflash_force_profit_baseline_cycle) {
44344483
std::vector<int> normal_n_max(slots.size(), 0);
44354484
std::vector<int> cohort_cap_n_max(slots.size(), 0);
44364485

@@ -4474,7 +4523,8 @@ struct server_context_impl {
44744523
if (slot.state == SLOT_STATE_GENERATING &&
44754524
slot.can_speculate() &&
44764525
dflash_scheduler_n_draft_max(slot, false) > 0 &&
4477-
!dflash_recurrent_has_pending_prompt) {
4526+
!dflash_has_pending_prompt &&
4527+
!dflash_force_profit_baseline_cycle) {
44784528
n_drafting++;
44794529
}
44804530
}
@@ -4491,7 +4541,8 @@ struct server_context_impl {
44914541
if (slot.state == SLOT_STATE_GENERATING &&
44924542
slot.can_speculate() &&
44934543
dflash_scheduler_n_draft_max(slot, false) > 0 &&
4494-
!dflash_recurrent_has_pending_prompt) {
4544+
!dflash_has_pending_prompt &&
4545+
!dflash_force_profit_baseline_cycle) {
44954546
batch_specs.push_back(slot.get_spec());
44964547
batch_id_lasts.push_back(slot.sampled);
44974548
batch_slot_ids.push_back(slot.id);
@@ -4531,14 +4582,26 @@ struct server_context_impl {
45314582
bool ddtree_batch_active = false;
45324583
std::vector<server_slot *> mtp_drafting;
45334584
std::vector<server_slot *> mtp_batching; // slots with existing draft that need batch building but not re-drafting
4585+
const bool dflash_rotate_tg_slots =
4586+
params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
4587+
params_base.speculative.branch_budget == 0 &&
4588+
!slots.empty();
4589+
const int dflash_tg_slot_start = dflash_rotate_tg_slots
4590+
? std::clamp(dflash_next_tg_slot, 0, (int) slots.size() - 1)
4591+
: 0;
45344592

45354593
// first, add sampled tokens from any ongoing sequences
4536-
for (auto & slot : slots) {
4594+
for (int slot_offset = 0; slot_offset < (int) slots.size(); ++slot_offset) {
4595+
const int slot_index = dflash_rotate_tg_slots
4596+
? (dflash_tg_slot_start + slot_offset) % (int) slots.size()
4597+
: slot_offset;
4598+
auto & slot = slots[slot_index];
4599+
45374600
if (slot.state != SLOT_STATE_GENERATING) {
45384601
continue;
45394602
}
45404603

4541-
if (dflash_recurrent_has_pending_prompt && slot.can_speculate()) {
4604+
if (dflash_has_pending_prompt && slot.can_speculate()) {
45424605
continue;
45434606
}
45444607

@@ -4554,6 +4617,9 @@ struct server_context_impl {
45544617
params_base.speculative.branch_budget == 0) {
45554618
n_draft_max = dflash_flat_effective_draft_max(ctx_dft_shared.get(), n_draft_max);
45564619
}
4620+
if (dflash_force_profit_baseline_cycle && n_draft_max > 0) {
4621+
n_draft_max = 0;
4622+
}
45574623
const bool dflash_active_grammar =
45584624
params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
45594625
slot.smpl && common_sampler_has_active_grammar(slot.smpl.get());
@@ -4730,11 +4796,20 @@ struct server_context_impl {
47304796

47314797
// MTP batch building is deferred to the post-loop phase below
47324798
if (!use_mtp_spec) {
4799+
const bool small_draft =
4800+
slot.spec_draft.empty() ||
4801+
slot.task->params.speculative.n_min > (int) slot.spec_draft.size();
4802+
const int dflash_slot_tg_rows = small_draft ? 1 : 1 + (int) slot.spec_draft.size();
4803+
if (!dflash_try_reserve_tg_rows(slot, dflash_slot_tg_rows)) {
4804+
dflash_skip_tg_slot_for_next_cycle(slot);
4805+
continue;
4806+
}
4807+
47334808
slot.spec_i_batch.push_back(batch.n_tokens);
47344809
common_batch_add(batch, slot.sampled, slot.prompt.tokens.pos_next(), { slot.id }, true);
47354810
slot.prompt.tokens.push_back(slot.sampled);
47364811

4737-
if (slot.spec_draft.empty() || slot.task->params.speculative.n_min > (int) slot.spec_draft.size()) {
4812+
if (small_draft) {
47384813
if (!slot.spec_draft.empty()) {
47394814
SLT_DBG(slot, "ignoring small draft: %d < %d\n", (int) slot.spec_draft.size(), slot.task->params.speculative.n_min);
47404815
}
@@ -4828,6 +4903,11 @@ struct server_context_impl {
48284903
t_draft_total += ggml_time_us() - t_draft_slot_start;
48294904
n_slots_drafted++;
48304905
} else {
4906+
if (!dflash_try_reserve_tg_rows(slot, 1)) {
4907+
dflash_skip_tg_slot_for_next_cycle(slot);
4908+
continue;
4909+
}
4910+
48314911
if (slot.can_speculate() && slot.dm_adaptive &&
48324912
server_adaptive_dm_uses_profit_controller(slot.dm_controller) &&
48334913
slot.profit_expects_baseline_sample()) {
@@ -4920,12 +5000,15 @@ struct server_context_impl {
49205000
// track how many TG tokens are in the batch vs total, to detect
49215001
// pure-verify batches where multi-seq batching is safe.
49225002
const int32_t n_tg_tokens = batch.n_tokens;
5003+
const bool dflash_batch_has_tg =
5004+
params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
5005+
n_tg_tokens > 0;
49235006

49245007
float alora_scale = -1.0f;
49255008
size_t alora_disabled_id = 0;
49265009

49275010
// next, batch any pending prompts without exceeding n_batch
4928-
if (!ddtree_batch_active && (params_base.cont_batching || batch.n_tokens == 0)) {
5011+
if (!ddtree_batch_active && !dflash_batch_has_tg && (params_base.cont_batching || batch.n_tokens == 0)) {
49295012
for (auto & slot : slots) {
49305013
if (!slot.is_processing()) {
49315014
continue;
@@ -7142,6 +7225,13 @@ struct server_context_impl {
71427225
if (n_slots_drafted > 0) {
71437226
const int64_t t_cycle_total = ggml_time_us() - t_cycle_start;
71447227
const int64_t t_other = t_cycle_total - t_draft_total - t_verify_total - t_accept_total;
7228+
if (params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH &&
7229+
params_base.speculative.branch_budget == 0 &&
7230+
dflash_cycle_slot &&
7231+
dflash_cycle_slot->id >= 0 &&
7232+
!slots.empty()) {
7233+
dflash_next_tg_slot = (dflash_cycle_slot->id + 1) % (int) slots.size();
7234+
}
71457235
if (profile_dflash_cycle && n_slots_drafted == 1 && dflash_cycle_slot &&
71467236
params_base.speculative.type() == COMMON_SPECULATIVE_TYPE_DFLASH) {
71477237
const common_dflash_ring_stats ring_stats =

0 commit comments

Comments
 (0)