|
4 | 4 |
|
5 | 5 | #include <algorithm> |
6 | 6 | #include <cassert> |
| 7 | +#include <cstdint> |
7 | 8 | #include <map> |
8 | 9 | #include <memory> |
9 | 10 | #include <optional> |
|
13 | 14 | #include <utility> |
14 | 15 | #include <vector> |
15 | 16 |
|
| 17 | +#include "vllm/v1/core/sched/utils.h" // check_stop |
| 18 | + |
16 | 19 | namespace vllm::v1 { |
17 | 20 |
|
18 | 21 | namespace { |
@@ -316,6 +319,133 @@ SchedulerOutput Scheduler::schedule() { |
316 | 319 | return scheduler_output; |
317 | 320 | } |
318 | 321 |
|
| 322 | +EngineCoreOutputs Scheduler::update_from_output( |
| 323 | + const SchedulerOutput& scheduler_output, |
| 324 | + const ModelRunnerOutput& model_runner_output) { |
| 325 | + const std::vector<std::vector<int32_t>>& sampled_token_ids = |
| 326 | + model_runner_output.sampled_token_ids; |
| 327 | + const std::map<std::string, int>& num_scheduled_tokens = |
| 328 | + scheduler_output.num_scheduled_tokens; |
| 329 | + |
| 330 | + std::vector<EngineCoreOutput> outputs; |
| 331 | + // Requests that stopped this step, split by the queue they must be removed |
| 332 | + // from (upstream stopped_running_reqs / stopped_preempted_reqs). The KV blocks |
| 333 | + // are freed and finished_req_ids updated inside the loop, but the owning |
| 334 | + // requests-map erase is deferred until after these pointers are used to filter |
| 335 | + // running/waiting (so the Request* stays valid — upstream relies on Python GC). |
| 336 | + std::set<Request*> stopped_running_reqs; |
| 337 | + std::set<Request*> stopped_preempted_reqs; |
| 338 | + std::vector<std::string> finished_ids_to_erase; |
| 339 | + |
| 340 | + // NOTE(woosuk): upstream iterates num_scheduled_tokens.items() (dict/schedule |
| 341 | + // order); std::map iterates in sorted key order. The set of outputs is the |
| 342 | + // same — only their order in the returned vector differs, which is benign |
| 343 | + // (each EngineCoreOutput is keyed by request_id). |
| 344 | + for (const auto& [req_id, num_tokens_scheduled] : num_scheduled_tokens) { |
| 345 | + assert(num_tokens_scheduled > 0); |
| 346 | + (void)num_tokens_scheduled; |
| 347 | + |
| 348 | + auto it = requests.find(req_id); |
| 349 | + if (it == requests.end() || it->second->IsFinished()) { |
| 350 | + // Already finished — e.g. aborted while the model was executing it. |
| 351 | + continue; |
| 352 | + } |
| 353 | + Request* request = it->second.get(); |
| 354 | + |
| 355 | + const int req_index = model_runner_output.req_id_to_index.at(req_id); |
| 356 | + // sampled_token_ids[req_index] if sampled_token_ids else []. A request still |
| 357 | + // being prefilled gets an empty list from the runner. |
| 358 | + std::vector<int32_t> new_token_ids = |
| 359 | + sampled_token_ids.empty() |
| 360 | + ? std::vector<int32_t>{} |
| 361 | + : sampled_token_ids[static_cast<std::size_t>(req_index)]; |
| 362 | + |
| 363 | + // DEFERRED: speculative-decode acceptance / num_computed rollback; encoder- |
| 364 | + // input free. |
| 365 | + |
| 366 | + bool stopped = false; |
| 367 | + const RequestStatus status_before_stop = request->status; |
| 368 | + |
| 369 | + // _update_request_with_output: append each generated token, run check_stop |
| 370 | + // after each, and trim any tokens generated past the stop. |
| 371 | + if (!new_token_ids.empty()) { |
| 372 | + for (std::size_t num_new = 1; num_new <= new_token_ids.size(); ++num_new) { |
| 373 | + request->AppendOutputToken(new_token_ids[num_new - 1]); |
| 374 | + stopped = check_stop(*request, max_model_len); |
| 375 | + if (stopped) { |
| 376 | + new_token_ids.resize(num_new); // del new_token_ids[num_new:] |
| 377 | + break; |
| 378 | + } |
| 379 | + } |
| 380 | + } |
| 381 | + // DEFERRED: pooling stop, structured-output grammar accept. |
| 382 | + |
| 383 | + std::optional<FinishReason> finish_reason; |
| 384 | + if (stopped) { |
| 385 | + // Capture the finish reason before freeing (upstream captures it before |
| 386 | + // _handle_stopped_request, which may reset the status for resumable reqs — |
| 387 | + // resumable/streaming is deferred, so _handle_stopped_request is always |
| 388 | + // "finished" at T0). |
| 389 | + finish_reason = request->GetFinishedReason(); |
| 390 | + // _free_request + _free_blocks (T0 subset): free the KV blocks and record |
| 391 | + // the finished id now; defer the requests-map erase (see above). |
| 392 | + kv_cache_manager->free(*request); |
| 393 | + finished_req_ids.insert(request->request_id); |
| 394 | + finished_ids_to_erase.push_back(request->request_id); |
| 395 | + if (status_before_stop == RequestStatus::kRunning) { |
| 396 | + stopped_running_reqs.insert(request); |
| 397 | + } else { |
| 398 | + stopped_preempted_reqs.insert(request); |
| 399 | + } |
| 400 | + } |
| 401 | + |
| 402 | + // DEFERRED: sample logprobs / prompt logprobs / num_nans_in_logits. |
| 403 | + |
| 404 | + // Emit an EngineCoreOutput only when the request produced tokens or finished |
| 405 | + // (upstream's `if new_token_ids or ... or stopped`). A partial-prefill |
| 406 | + // request that produced neither is skipped: "EngineCore returns no partial |
| 407 | + // prefill outputs". |
| 408 | + if (!new_token_ids.empty() || stopped) { |
| 409 | + EngineCoreOutput out; |
| 410 | + out.request_id = req_id; |
| 411 | + out.new_token_ids = new_token_ids; |
| 412 | + out.finish_reason = finish_reason; |
| 413 | + // stop_reason is int|str|None upstream; our EngineCoreOutput carries an |
| 414 | + // optional<string> (see engine/types.h). Only a stop_token_ids match sets |
| 415 | + // request.stop_reason at T0 — stringify that token id; otherwise nullopt. |
| 416 | + if (request->stop_reason.has_value()) { |
| 417 | + out.stop_reason = std::to_string(*request->stop_reason); |
| 418 | + } |
| 419 | + outputs.push_back(std::move(out)); |
| 420 | + } |
| 421 | + } |
| 422 | + |
| 423 | + // Remove the stopped requests from the running list and the waiting queue. |
| 424 | + if (!stopped_running_reqs.empty()) { |
| 425 | + running.erase( |
| 426 | + std::remove_if(running.begin(), running.end(), |
| 427 | + [&](Request* r) { |
| 428 | + return stopped_running_reqs.count(r) > 0; |
| 429 | + }), |
| 430 | + running.end()); |
| 431 | + } |
| 432 | + if (!stopped_preempted_reqs.empty()) { |
| 433 | + // Rare (a stopped-while-preempted request); remove each from waiting. |
| 434 | + std::vector<Request*> to_remove(stopped_preempted_reqs.begin(), |
| 435 | + stopped_preempted_reqs.end()); |
| 436 | + waiting->remove_requests(to_remove); |
| 437 | + } |
| 438 | + // Now that no queue references them, drop the owning entries (destroys the |
| 439 | + // finished Request objects — upstream _free_blocks' `del self.requests[...]`). |
| 440 | + for (const std::string& id : finished_ids_to_erase) { |
| 441 | + requests.erase(id); |
| 442 | + } |
| 443 | + |
| 444 | + EngineCoreOutputs engine_core_outputs; |
| 445 | + engine_core_outputs.outputs = std::move(outputs); |
| 446 | + return engine_core_outputs; |
| 447 | +} |
| 448 | + |
319 | 449 | CachedRequestData Scheduler::make_cached_request_data( |
320 | 450 | const std::vector<Request*>& running_reqs, |
321 | 451 | const std::vector<Request*>& resumed_reqs, |
|
0 commit comments