@@ -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