From 0863f35bae15b23f0ee935725316358ade3845df Mon Sep 17 00:00:00 2001 From: phantomlei3 Date: Thu, 16 Jul 2026 11:19:01 +0800 Subject: [PATCH] bugfix: mark prefill complete after empty token decoding. --- .../scheduler/continuous_scheduler_test.cpp | 30 +++++++++++++++++++ .../scheduler/async_response_processor.cpp | 12 ++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/core/scheduler/continuous_scheduler_test.cpp b/tests/core/scheduler/continuous_scheduler_test.cpp index 6535de92ab..297444724e 100644 --- a/tests/core/scheduler/continuous_scheduler_test.cpp +++ b/tests/core/scheduler/continuous_scheduler_test.cpp @@ -1011,6 +1011,36 @@ TEST(ContinuousSchedulerTest, BatchRejectedStreamsCancelAtSchedulingBoundary) { EXPECT_TRUE(requests[1]->cancelled()); EXPECT_TRUE(scheduler->get_running_requests().empty()); } + +TEST(ContinuousSchedulerTest, + BatchStreamMarksPrefillFinishedWhenFirstTokenDecodesEmpty) { + ContinuousScheduler::Options opt = + create_scheduler_options(1024, 16, 0, 1024, 1); + auto engine = std::make_unique(32, 4); + auto scheduler = std::make_unique(engine.get(), opt); + + auto request = generate_request_with_prompt_tokens({1, 2, 3, 4}, 4, 30000); + request->state().stream = true; + make_request_decode_ready(request); + + bool callback_called = false; + bool outputs_empty = false; + bool finished_on_prefill_instance = false; + request->state().outputs_func = + [&](const std::vector& outputs) { + callback_called = true; + outputs_empty = outputs.size() == 1 && outputs[0].outputs.empty(); + finished_on_prefill_instance = + outputs.size() == 1 && outputs[0].finished_on_prefill_instance; + return std::vector{true}; + }; + + scheduler->reject_streams({request}); + + EXPECT_TRUE(callback_called); + EXPECT_TRUE(outputs_empty); + EXPECT_TRUE(finished_on_prefill_instance); +} // ============== Async RL training: Pause/Resume tests ============== // TEST: pause()/resume() state transitions are correct and idempotent. diff --git a/xllm/core/scheduler/async_response_processor.cpp b/xllm/core/scheduler/async_response_processor.cpp index ab1aa157aa..635006fc2c 100644 --- a/xllm/core/scheduler/async_response_processor.cpp +++ b/xllm/core/scheduler/async_response_processor.cpp @@ -302,12 +302,12 @@ void AsyncResponseProcessor::batch_process_stream_requests( auto seq_output = seq->generate_streaming_output(size, *tokenizer_); if (seq_output.has_value()) { req_output->outputs.push_back(std::move(seq_output.value())); - if (seq->num_generated_tokens() == 1) { - // currently only support one sequence when enable_service_routing - // IMPROVE LATER: support enable_schedule_overlap in Default mode - // for stream request - req_output->finished_on_prefill_instance = true; - } + } + if (seq->num_generated_tokens() == 1) { + // currently only support one sequence when enable_service_routing + // IMPROVE LATER: support enable_schedule_overlap in Default mode + // for stream request + req_output->finished_on_prefill_instance = true; } } counter->decrement_count();