/*! * Copyright (c) 2023-2025 by Contributors * \file serve/request_state.cc */ #include "request_state.h" #include namespace mlc { namespace llm { namespace serve { TVM_FFI_STATIC_INIT_BLOCK() { RequestModelStateNode::RegisterReflection(); RequestStateEntryNode::RegisterReflection(); RequestStateNode::RegisterReflection(); } /****************** RequestModelState ******************/ RequestModelState::RequestModelState( Request request, int model_id, int64_t internal_id, Array inputs, const std::optional& compiled_grammar) { ObjectPtr n = tvm::ffi::make_object(); n->model_id = model_id; n->internal_id = internal_id; n->inputs = std::move(inputs); if (compiled_grammar.has_value()) { // TODO(yixin): set rollback limit to a configurable value. n->grammar_matcher = xgrammar::GrammarMatcher(compiled_grammar.value(), std::nullopt, false, std::nullopt, 10); } n->request = std::move(request); data_ = std::move(n); } int RequestModelStateNode::GetInputLength() const { int total_length = 0; for (Data input : inputs) { total_length += input->GetLength(); } return total_length; } bool RequestModelStateNode::RequireNextTokenBitmask() { return grammar_matcher.has_value(); } void RequestModelStateNode::GetNextTokenBitmask(DLTensor* bitmask) { TVM_FFI_ICHECK(grammar_matcher.has_value()); grammar_matcher->GetNextTokenBitmask(bitmask); } void RequestModelStateNode::CommitToken(SampleResult sampled_token) { committed_tokens.push_back(std::move(sampled_token)); appeared_token_ids[sampled_token.GetTokenId()] += 1; // There will be one more token that will be processed in the next decoding. ++num_tokens_for_next_decode; // Update the grammar matcher state if it exists. if (grammar_matcher) { bool accepted = grammar_matcher->AcceptToken(sampled_token.GetTokenId()); TVM_FFI_ICHECK(accepted) << "Token id " << sampled_token.GetTokenId() << " is not accepted by the grammar state matcher."; } } void RequestModelStateNode::RollbackTokens(int count) { TVM_FFI_ICHECK(count <= static_cast(committed_tokens.size())); for (int i = 0; i < count; ++i) { auto it = appeared_token_ids.find(committed_tokens.back().GetTokenId()); TVM_FFI_ICHECK(it != appeared_token_ids.end()); if (--it->second == 0) { appeared_token_ids.erase(it); } committed_tokens.pop_back(); if (grammar_matcher) { grammar_matcher->Rollback(1); } } } void RequestModelStateNode::AddDraftToken(SampleResult sampled_token, int draft_token_slot, int64_t parent_idx) { draft_output_tokens.push_back(std::move(sampled_token)); draft_token_slots.push_back(draft_token_slot); draft_token_parent_idx.push_back(parent_idx); draft_token_first_child_idx.push_back(-1); if (parent_idx != -1) { if (draft_token_first_child_idx[parent_idx] == -1) { draft_token_first_child_idx[parent_idx] = static_cast(draft_output_tokens.size()) - 1; } } } void RequestModelStateNode::RemoveAllDraftTokens(std::vector* removed_draft_token_slots) { if (removed_draft_token_slots != nullptr) { std::unordered_set dedup; removed_draft_token_slots->clear(); for (auto slot : draft_token_slots) { bool inserted = dedup.insert(slot).second; if (inserted) { removed_draft_token_slots->push_back(slot); } } } draft_token_slots.clear(); draft_token_parent_idx.clear(); draft_token_first_child_idx.clear(); draft_output_tokens.clear(); } /****************** RequestActionPostProcWorkspace ******************/ RequestStreamOutput RequestActionPostProcWorkspace::GetStreamOutput() { for (const RequestStreamOutput& stream_output : stream_outputs) { if (stream_output->unpacked) { return stream_output; } } TVM_FFI_ICHECK(!stream_outputs.empty()); int num_response = stream_outputs[0]->group_delta_token_ids.size(); std::vector> group_delta_token_ids; std::vector> group_delta_logprob_json_strs; std::vector> group_finish_reason; std::vector group_extra_prefix_string; group_delta_token_ids.resize(num_response); group_finish_reason.resize(num_response); group_extra_prefix_string.resize(num_response); if (stream_outputs[0]->group_delta_logprob_json_strs.has_value()) { group_delta_logprob_json_strs.resize(num_response); } RequestStreamOutput stream_output(stream_outputs[0]->request_id, std::move(group_delta_token_ids), stream_outputs[0]->group_delta_logprob_json_strs.has_value() ? std::make_optional(group_delta_logprob_json_strs) : std::nullopt, std::move(group_finish_reason), std::move(group_extra_prefix_string)); stream_outputs.push_back(stream_output); return stream_output; } /****************** RequestStateEntry ******************/ RequestStateEntry::RequestStateEntry( Request request, int num_models, int64_t internal_id, int rng_seed, const std::vector& token_table, const std::optional& compiled_grammar, int parent_idx) { ObjectPtr n = tvm::ffi::make_object(); Array mstates; Array inputs; if (parent_idx == -1) { inputs = request->inputs; } mstates.reserve(num_models); for (int i = 0; i < num_models; ++i) { mstates.push_back(RequestModelState(request, i, internal_id, inputs, compiled_grammar)); } n->status = RequestStateStatus::kPending; n->rng = RandomGenerator(rng_seed); n->stop_str_handler = StopStrHandler(!request->generation_cfg->debug_config.ignore_eos ? request->generation_cfg->stop_strs : Array(), token_table); n->request = std::move(request); n->parent_idx = parent_idx; n->mstates = std::move(mstates); n->next_callback_token_pos = 0; data_ = std::move(n); } void RequestStateEntryNode::GetDeltaRequestReturn(const Tokenizer& tokenizer, int64_t max_single_sequence_length, RequestStreamOutput* delta_stream_output, int idx) { TVM_FFI_ICHECK_NOTNULL(delta_stream_output); bool needs_logprobs = (*delta_stream_output)->group_delta_logprob_json_strs.has_value(); (*delta_stream_output)->group_delta_token_ids[idx].clear(); if (needs_logprobs) { (*delta_stream_output)->group_delta_logprob_json_strs.value()[idx].clear(); } (*delta_stream_output)->group_finish_reason[idx] = std::nullopt; (*delta_stream_output)->group_extra_prefix_string[idx] = this->extra_prefix_string; this->extra_prefix_string.clear(); const std::vector& committed_tokens = this->mstates[0]->committed_tokens; int num_committed_tokens = committed_tokens.size(); TVM_FFI_ICHECK_LE(this->next_callback_token_pos, num_committed_tokens); // Case 1. There is no new token ids. if (this->next_callback_token_pos == num_committed_tokens && extra_prefix_string.empty()) { return; } // Case 2. Any of the stop strings is matched. TVM_FFI_ICHECK(!stop_str_handler->StopTriggered()); while (next_callback_token_pos < num_committed_tokens) { stop_str_handler->Put(committed_tokens[next_callback_token_pos].GetTokenId(), &(*delta_stream_output)->group_delta_token_ids[idx]); if (needs_logprobs) { (*delta_stream_output) ->group_delta_logprob_json_strs.value()[idx] .push_back(committed_tokens[next_callback_token_pos].GetLogProbJSON( tokenizer, request->generation_cfg->logprobs)); } ++next_callback_token_pos; if (stop_str_handler->StopTriggered()) { (*delta_stream_output)->group_finish_reason[idx] = "stop"; break; } } // Case 3. Any of the stop tokens appears in the committed tokens ===> Finished // `stop_token_ids` includes the stop tokens from conversation template and user-provided tokens. // This check will be ignored when `ignore_eos` is set for the benchmarking purpose. if (!request->generation_cfg->debug_config.ignore_eos) { for (int i = 0; i < static_cast((*delta_stream_output)->group_delta_token_ids[idx].size()); ++i) { if (std::any_of(request->generation_cfg->stop_token_ids.begin(), request->generation_cfg->stop_token_ids.end(), [delta_stream_output, idx, i](int32_t token) { return token == (*delta_stream_output)->group_delta_token_ids[idx][i]; })) { // Stop token matched. Erase the stop token and all tokens after it. (*delta_stream_output)->group_finish_reason[idx] = "stop"; while (static_cast((*delta_stream_output)->group_delta_token_ids[idx].size()) > i) { (*delta_stream_output)->group_delta_token_ids[idx].pop_back(); } break; } } } // Case 4. When stop token is not detected (e.g. ignore_eos is set), but the grammar state is // terminated, stop the generation and pop the last token (used to trigger the termination). if ((*delta_stream_output)->group_finish_reason[idx] != "stop" && this->mstates[0]->grammar_matcher.has_value() && this->mstates[0]->grammar_matcher->IsTerminated()) { (*delta_stream_output)->group_delta_token_ids[idx].pop_back(); (*delta_stream_output)->group_finish_reason[idx] = "stop"; } if ((*delta_stream_output)->group_finish_reason[idx].has_value()) { return; } // Case 5. Generation reaches the specified max generation length ==> Finished // `max_tokens` means the generation length is limited by model capacity. if (request->generation_cfg->max_tokens >= 0 && num_committed_tokens >= request->generation_cfg->max_tokens) { stop_str_handler->Finish(&(*delta_stream_output)->group_delta_token_ids[idx]); (*delta_stream_output)->group_finish_reason[idx] = "length"; return; } // Case 6. Total length of the request reaches the maximum single sequence length ==> Finished if (request->prompt_tokens + num_committed_tokens >= max_single_sequence_length) { stop_str_handler->Finish(&(*delta_stream_output)->group_delta_token_ids[idx]); (*delta_stream_output)->group_finish_reason[idx] = "length"; } } /****************** RequestState ******************/ RequestState::RequestState(std::vector entries, int num_response, std::chrono::high_resolution_clock::time_point add_time_point) { TVM_FFI_ICHECK(!entries.empty()); ObjectPtr n = tvm::ffi::make_object(); n->entries = std::move(entries); n->metrics.prompt_tokens = n->entries[0]->request->prompt_tokens; n->metrics.add_time_point = add_time_point; std::vector> group_delta_token_ids; std::vector> group_delta_logprob_json_strs; std::vector> group_finish_reason; std::vector group_extra_prefix_string; group_delta_token_ids.resize(num_response); group_finish_reason.resize(num_response); group_extra_prefix_string.resize(num_response); if (n->entries[0]->request->generation_cfg->logprobs) { group_delta_logprob_json_strs.resize(num_response); } RequestStreamOutput stream_output(n->entries[0]->request->id, std::move(group_delta_token_ids), n->entries[0]->request->generation_cfg->logprobs ? std::make_optional(group_delta_logprob_json_strs) : std::nullopt, std::move(group_finish_reason), std::move(group_extra_prefix_string)); stream_output->unpacked = true; n->postproc_states.stream_outputs = {std::move(stream_output)}; data_ = std::move(n); } } // namespace serve } // namespace llm } // namespace mlc