Files
wehub-resource-sync 770d92cb1f
Lint / lint (push) Waiting to run
Windows CI / Windows (push) Waiting to run
Build Docs / Deploy Docs (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:23:58 +08:00

299 lines
12 KiB
C++

/*!
* Copyright (c) 2023-2025 by Contributors
* \file serve/request_state.cc
*/
#include "request_state.h"
#include <unordered_set>
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<Data> inputs,
const std::optional<xgrammar::CompiledGrammar>& compiled_grammar) {
ObjectPtr<RequestModelStateNode> n = tvm::ffi::make_object<RequestModelStateNode>();
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<int>(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<int>(draft_output_tokens.size()) - 1;
}
}
}
void RequestModelStateNode::RemoveAllDraftTokens(std::vector<int>* removed_draft_token_slots) {
if (removed_draft_token_slots != nullptr) {
std::unordered_set<int> 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<std::vector<int64_t>> group_delta_token_ids;
std::vector<std::vector<String>> group_delta_logprob_json_strs;
std::vector<Optional<String>> group_finish_reason;
std::vector<String> 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<std::string>& token_table,
const std::optional<xgrammar::CompiledGrammar>& compiled_grammar, int parent_idx) {
ObjectPtr<RequestStateEntryNode> n = tvm::ffi::make_object<RequestStateEntryNode>();
Array<RequestModelState> mstates;
Array<Data> 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<String>(),
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<SampleResult>& 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<int>((*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<int>((*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<RequestStateEntry> entries, int num_response,
std::chrono::high_resolution_clock::time_point add_time_point) {
TVM_FFI_ICHECK(!entries.empty());
ObjectPtr<RequestStateNode> n = tvm::ffi::make_object<RequestStateNode>();
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<std::vector<int64_t>> group_delta_token_ids;
std::vector<std::vector<String>> group_delta_logprob_json_strs;
std::vector<Optional<String>> group_finish_reason;
std::vector<String> 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