|
| 1 | +// Ported from: vllm/v1/engine/output_processor.py @ e24d1b24 |
| 2 | +// (OutputProcessor + RequestState + OutputProcessorOutput — the T0 synchronous |
| 3 | +// text path: incremental detokenize + string-level stop + RequestOutput |
| 4 | +// assembly.) |
| 5 | +// |
| 6 | +// Scope (M1.8 Task 5): turn the per-step EngineCoreOutputs into RequestOutputs. |
| 7 | +// This is the SYNCHRONOUS LLMEngine path (no queue): process_outputs loops the |
| 8 | +// EngineCoreOutputs, drives our IncrementalDetokenizer.Update() (which does the |
| 9 | +// STRING-level stop match the scheduler's token-level check_stop cannot), builds |
| 10 | +// the streaming-delta vs full CompletionOutput/RequestOutput per RequestOutputKind, |
| 11 | +// removes finished req states, and returns the reqs_to_abort feedback for |
| 12 | +// requests the detokenizer stopped but EngineCore did not (output_processor.py |
| 13 | +// :678). Mirrors OutputProcessor.process_outputs (:576-693), |
| 14 | +// RequestState.from_new_request (:210-270), make_request_output (:272-331), |
| 15 | +// _new_completion_output (:376-411), _new_request_output (:333-374), |
| 16 | +// _finish_request (:695-707). |
| 17 | +// |
| 18 | +// DEVIATIONS vs the pinned API (recorded, use OUR names): |
| 19 | +// - __init__ takes a nullable tokenizer pointer + stream_interval (T0). The |
| 20 | +// upstream log_stats / tracing_enabled knobs are deferred (stats/tracing are |
| 21 | +// deferred below), so they are dropped. |
| 22 | +// - RequestState has no external_req_id field upstream-separate at T0: our |
| 23 | +// EngineCoreRequest deferred external_req_id (see v1/engine/types.h), so the |
| 24 | +// external id == request_id here (no parallel-sampling / streaming-input |
| 25 | +// remap). The external_req_ids map is kept for structural parity with |
| 26 | +// _finish_request even though it degenerates to a 1:1 mapping at T0. |
| 27 | +// - kv_transfer_params is dropped from make_request_output (our EngineCoreOutput |
| 28 | +// has no kv_transfer_params field — deferred in v1/engine/types.h). |
| 29 | +// - make_request_output returns std::optional<RequestOutput> (pooling deferred, |
| 30 | +// so PoolingRequestOutput never occurs; the None return still models |
| 31 | +// FINAL_ONLY / stream_interval hold-back). |
| 32 | +// - The upstream private _new_* helpers are PascalCase here (NewCompletionOutput |
| 33 | +// / NewRequestOutput); _finish_request -> FinishRequest. |
| 34 | +// |
| 35 | +// DEFERRED (marked; matches upstream so re-adding is mechanical): |
| 36 | +// LogprobsProcessor (sample + prompt logprobs), pooling outputs |
| 37 | +// (PoolingOutput / PoolingRequestOutput branch), routed_experts accumulation, |
| 38 | +// parallel sampling (ParentRequest / parent_requests / get_outputs), |
| 39 | +// the async RequestOutputCollector queue (AsyncLLM only), streaming-input |
| 40 | +// chunk queue (StreamingUpdate / apply_streaming_update / resumable), |
| 41 | +// iteration + per-request stats (RequestStateStats / IterationStats / |
| 42 | +// LoRARequestStates), tracing (do_tracing), num_cached_tokens / prefill_stats, |
| 43 | +// LoRA, prompt_embeds, and the client-initiated abort_requests() path (needs |
| 44 | +// the queue/pooling/parent machinery). |
| 45 | +#pragma once |
| 46 | + |
| 47 | +#include <cstdint> |
| 48 | +#include <map> |
| 49 | +#include <memory> |
| 50 | +#include <optional> |
| 51 | +#include <string> |
| 52 | +#include <vector> |
| 53 | + |
| 54 | +#include "vllm/outputs.h" |
| 55 | +#include "vllm/sampling_params.h" |
| 56 | +#include "vllm/v1/engine/detokenizer.h" |
| 57 | +#include "vllm/v1/engine/types.h" |
| 58 | +#include "vllm/v1/request.h" |
| 59 | + |
| 60 | +namespace vllm::tok { |
| 61 | +class Tokenizer; // vllm/tokenizer/tokenizer.h |
| 62 | +} |
| 63 | + |
| 64 | +namespace vllm::v1 { |
| 65 | + |
| 66 | +// OutputProcessorOutput (@dataclass, output_processor.py:109-113). The |
| 67 | +// synchronous return of process_outputs: the RequestOutputs to hand back to the |
| 68 | +// caller + the request ids EngineCore must abort (detokenizer-detected stop that |
| 69 | +// EngineCore did not itself signal). PoolingRequestOutput deferred. |
| 70 | +struct OutputProcessorOutput { |
| 71 | + std::vector<RequestOutput> request_outputs; |
| 72 | + std::vector<std::string> reqs_to_abort; |
| 73 | +}; |
| 74 | + |
| 75 | +// RequestState (output_processor.py:129): per-request accumulation state held by |
| 76 | +// the OutputProcessor for the life of a request. Members are public, mirroring |
| 77 | +// the upstream dataclass-like attributes; the deferred members are omitted (see |
| 78 | +// the file header). |
| 79 | +class RequestState { |
| 80 | + public: |
| 81 | + // from_new_request (:210): build a RequestState with our IncrementalDetokenizer |
| 82 | + // (the detokenizer branch). `tokenizer` may be nullptr (=> no detokenization); |
| 83 | + // if sampling_params.detokenize is false the tokenizer is dropped as upstream |
| 84 | + // (:223). LogprobsProcessor / pooling / parent_req / queue are deferred. |
| 85 | + static RequestState FromNewRequest(const tok::Tokenizer* tokenizer, |
| 86 | + const EngineCoreRequest& request, |
| 87 | + std::optional<std::string> prompt, |
| 88 | + int request_index, int stream_interval); |
| 89 | + |
| 90 | + // make_request_output (:272): assemble the streaming-delta vs full |
| 91 | + // CompletionOutput/RequestOutput honoring output_kind. Returns nullopt when |
| 92 | + // FINAL_ONLY-and-not-finished or a stream_interval hold-back suppresses this |
| 93 | + // step's output. kv_transfer_params deferred (see header). |
| 94 | + std::optional<RequestOutput> make_request_output( |
| 95 | + const std::vector<int32_t>& new_token_ids, |
| 96 | + std::optional<FinishReason> finish_reason, |
| 97 | + std::optional<std::string> stop_reason); |
| 98 | + |
| 99 | + std::string request_id; |
| 100 | + std::string external_req_id; // == request_id at T0 (see header). |
| 101 | + int request_index = 0; |
| 102 | + RequestOutputKind output_kind = RequestOutputKind::kCumulative; |
| 103 | + std::optional<std::string> prompt; |
| 104 | + std::vector<int32_t> prompt_token_ids; |
| 105 | + size_t prompt_len = 0; |
| 106 | + std::unique_ptr<IncrementalDetokenizer> detokenizer; |
| 107 | + std::optional<int> max_tokens_param; |
| 108 | + bool is_prefilling = true; |
| 109 | + int num_cached_tokens = 0; // deferred (no prefill_stats at T0); stays 0. |
| 110 | + int stream_interval = 1; |
| 111 | + size_t sent_tokens_offset = 0; |
| 112 | + |
| 113 | + private: |
| 114 | + // _new_completion_output (:376): text/token_ids in delta vs cumulative mode. |
| 115 | + CompletionOutput NewCompletionOutput(std::vector<int32_t> token_ids, |
| 116 | + std::optional<FinishReason> finish_reason, |
| 117 | + std::optional<std::string> stop_reason); |
| 118 | + // _new_request_output (:333): wrap the CompletionOutput(s) in a RequestOutput. |
| 119 | + RequestOutput NewRequestOutput(const std::string& external_req_id, |
| 120 | + std::vector<CompletionOutput> outputs, |
| 121 | + bool finished); |
| 122 | +}; |
| 123 | + |
| 124 | +// OutputProcessor (output_processor.py:417): process EngineCoreOutputs into |
| 125 | +// RequestOutputs. T0 synchronous path only. |
| 126 | +class OutputProcessor { |
| 127 | + public: |
| 128 | + // __init__ (:420). `tokenizer` may be nullptr (=> no detokenization). It must |
| 129 | + // outlive the OutputProcessor. log_stats / tracing_enabled deferred. |
| 130 | + explicit OutputProcessor(const tok::Tokenizer* tokenizer, |
| 131 | + int stream_interval = 1); |
| 132 | + |
| 133 | + int get_num_unfinished_requests() const { |
| 134 | + return static_cast<int>(request_states_.size()); |
| 135 | + } |
| 136 | + bool has_unfinished_requests() const { return !request_states_.empty(); } |
| 137 | + |
| 138 | + // add_request (:512): build + register a RequestState. parent_req / queue / |
| 139 | + // the streaming-update re-entry are deferred (T0: a request_id appears once). |
| 140 | + void add_request(const EngineCoreRequest& request, |
| 141 | + std::optional<std::string> prompt, int request_index = 0); |
| 142 | + |
| 143 | + // process_outputs (:576): the per-EngineCoreOutput loop — detokenize + stop + |
| 144 | + // RequestOutput assembly + reqs_to_abort feedback. Stats/tracing/timestamp |
| 145 | + // args deferred. |
| 146 | + OutputProcessorOutput process_outputs( |
| 147 | + const EngineCoreOutputs& engine_core_outputs); |
| 148 | + |
| 149 | + private: |
| 150 | + // _finish_request (:695): remove the finished req state from the maps. |
| 151 | + void FinishRequest(RequestState& req_state); |
| 152 | + |
| 153 | + const tok::Tokenizer* tokenizer_; |
| 154 | + int stream_interval_; |
| 155 | + std::map<std::string, std::unique_ptr<RequestState>> request_states_; |
| 156 | + // external_req_id -> [internal request_id, ...] (1:1 at T0, see header). |
| 157 | + std::map<std::string, std::vector<std::string>> external_req_ids_; |
| 158 | +}; |
| 159 | + |
| 160 | +} // namespace vllm::v1 |
0 commit comments