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

319 lines
12 KiB
C++

/*!
* Copyright (c) 2023-2025 by Contributors
* \file serve/request_state.h
* \brief The data structure maintaining the generation states of user requests.
*/
#ifndef MLC_LLM_SERVE_REQUEST_STATE_H_
#define MLC_LLM_SERVE_REQUEST_STATE_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/object.h>
#include <tvm/runtime/tensor.h>
#include <xgrammar/xgrammar.h>
#include <optional>
#include "../support/random.h"
#include "../tokenizers/streamer.h"
#include "config.h"
#include "metrics.h"
#include "request.h"
namespace mlc {
namespace llm {
namespace serve {
using namespace tvm::runtime;
using tvm::ffi::Object;
using tvm::ffi::ObjectRef;
/*!
* \brief The state of a request with regard to some single model.
* \details In MLC LLM, the serving engine may leverage multiple models
* to fulfill a user generation request (e.g., use speculation decoding).
* For each request, we isolate its states (e.g. the generated tokens)
* on each model. This is to say, we use RequestModelState to store
* the state of a user request on a single model (rather than all models).
*/
class RequestModelStateNode : public Object {
public:
/*! \brief The request that this state corresponds to. */
Request request;
/*!
* \brief The internal request id of this state.
* It is the **physical index** of the request in the running request queue.
* If the request is on hold (not in the running queue), the request id
* should be -1.
*/
int64_t internal_id = -1;
/*! \brief The corresponding model id of this state. */
int model_id = -1;
/*!
* \brief The committed generated token ids and related probability info.
* A token is "committed" means it will no longer be updated (or changed).
*/
std::vector<SampleResult> committed_tokens;
/*! \brief The list of input data yet for the model to prefill. */
Array<Data> inputs;
/*! \brief The list of prefilled input data, used to notify prefix cache. */
std::vector<Data> prefilled_inputs;
/*! \brief The number of tokens already cached in prefix cache. */
int64_t cached_committed_tokens = 0;
/*! \brief The number of tokens that is already prefilled from the inputs. */
int64_t num_prefilled_tokens = 0;
/*! \brief The number of tokens that need to be processed in the next decoding. */
int num_tokens_for_next_decode = 0;
/*! \brief Whether retokenization is needed in the next decoding. When the jump-forward decoding
* is enabled, retokenization is needed after every jump-forward and decoding action. */
bool require_retokenization_in_next_decode = false;
// NOTE: The following fields are reserved for future speculative inference
// settings, and are produced by the speculative small models.
/*!
* \brief The draft generated token ids and related probability info,
* which are usually generated by "small" speculative models.
* These tokens will be fed to a "large" model to determine the final
* result of speculation.
*/
std::vector<SampleResult> draft_output_tokens;
/*! \brief The storage slots for the associated states of draft tokens. */
std::vector<int> draft_token_slots;
/*! \brief The parent indices of the draft tokens. */
std::vector<int64_t> draft_token_parent_idx;
/*! \brief The first child indices of the draft tokens. */
std::vector<int64_t> draft_token_first_child_idx;
/*! \brief The appeared committed and draft tokens and their occurrence times. */
std::unordered_map<int32_t, int32_t> appeared_token_ids;
/*!
* \brief The current state of the generated token matching the grammar. Used in grammar-guided
* generation, otherwise it's std::nullopt.
*/
std::optional<xgrammar::GrammarMatcher> grammar_matcher;
/*! \brief Return the total length of the input data. */
int GetInputLength() const;
/*!
* \brief Return whether the next token bitmask is required, i.e. the grammar-guided generation is
* enabled.
*/
bool RequireNextTokenBitmask();
/*!
* \brief Find the next token bitmask and store it in the given DLTensor.
* \param bitmask The DLTensor to store the next token bitmask. The bitmask should be a tensor
* with dtype uint32_t and shape (ceildiv(vocab_size, 32),).
*/
void GetNextTokenBitmask(DLTensor* bitmask);
/*! \brief Commit a new token into committed_tokens. Does not effect the kv cache. Update
* appeared_token_ids and the grammar state. */
void CommitToken(SampleResult sampled_token);
/*! \brief Roll back the last tokens back from committed_tokens. Does not effect the kv cache.
* Also roll back appeared_token_ids and the grammar state. */
void RollbackTokens(int count);
/*! \brief Add a draft token into draft_output_tokens. Update appeared_token_ids. */
void AddDraftToken(SampleResult sampled_token, int draft_token_slot, int64_t parent_idx);
/*! \brief Remove all draft tokens from draft_output_tokens. Update appeared_token_ids. */
void RemoveAllDraftTokens(std::vector<int>* removed_draft_token_slots = nullptr);
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RequestModelStateNode>();
}
static constexpr const bool _type_has_method_sequal_reduce = false;
static constexpr const bool _type_has_method_shash_reduce = false;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("mlc.serve.RequestModelState", RequestModelStateNode, Object);
};
class RequestModelState : public ObjectRef {
public:
explicit RequestModelState(Request request, int model_id, int64_t internal_id, Array<Data> inputs,
const std::optional<xgrammar::CompiledGrammar>& compiled_grammar);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(RequestModelState, ObjectRef, RequestModelStateNode);
};
struct DeltaRequestReturn {
std::vector<int64_t> delta_token_ids;
std::vector<String> delta_logprob_json_strs;
Optional<String> finish_reason;
/*! \brief The extra string to prepend the delta output. The delta output should be
* extra_prefix_string + detokenize(delta_token_ids). */
String extra_prefix_string = "";
};
/****************** Request States ******************/
/*!
* \brief For each request, we maintain its "request state" in the
* engine. Generally, the state of a request contains the information
* of the request's generation at the current moment, including
* the generated token ids, the grammar handler, etc.
*
* When a request has multiple parallel generations (e.g., the field
* `n` of its generation config is more than 1), each generation will
* have different states all the time.
*
* Therefore, to better support parallel generations, we denote the
* state of a single generation as a "RequestStateEntry" instance,
* and denote the state of a request's all generations using a vector,
* named as a "RequestState" instance.
*
* A request's all state entries are organized as a tree structure
* when there are parallel generations.
* - the request input has the root status entry,
* - each parallel generation is a child of the root.
* This tree structure may be further extended to more complicated
* cases in the future. As of now, for the case of `n > 1`, there
* will be (n + 1) entries in total. In a "RequestState", the root
* entry always has index 0. And we guarantee that the entry order
* from the vector begin to the end is always a topological order
* of the tree.
*/
/*! \brief Request state status. */
enum class RequestStateStatus : int {
kPending = 0,
kAlive = 1,
kFinished = 2,
};
/*! \brief The data structures for each request used in the action post-process. */
struct RequestActionPostProcWorkspace {
std::vector<RequestStreamOutput> stream_outputs;
RequestStreamOutput GetStreamOutput();
};
// forward declare request state node.
class RequestStateNode;
/*!
* \brief A request's state entry. It contains the state of a single
* generation of a request, or the state of a prompt prefix of a request.
*/
class RequestStateEntryNode : public Object {
public:
/*! \brief The status of the request state entry. */
RequestStateStatus status;
/*! \brief The request that this state corresponds to. */
Request request;
/*!
* \brief The idx of the parent request state entry of this state.
* Being -1 means the state has no parent and is the foremost
* "prefix" entry or the only entry.
*/
int parent_idx = -1;
/*! \brief The children indices of the request state entry. */
std::vector<int> child_indices;
/*!
* \brief The state with regard to each model.
* \sa RequestModelState
*/
Array<RequestModelState> mstates;
/*! \brief The random number generator of this request state entry. */
RandomGenerator rng;
/*! \brief The stop string handler of this request state entry. */
StopStrHandler stop_str_handler;
/*!
* \brief The start position of the committed tokens in the
* next request stream callback invocation.
*/
int next_callback_token_pos;
/*! \brief The extra string to prepend the output. */
std::string extra_prefix_string;
std::vector<int32_t> token_ids_for_prefix_cache_update;
/*!
* \brief Back reference to the request state.
* Use ObjectRef to avoid circulate reference.
*/
RequestStateNode* rstate = nullptr;
/*!
* \brief Get the delta token ids and the logprob JSON strings for this request to return since
* the last time calling into this function, and return the finish reason if the request
* generation has finished.
* \note This function follows the destination passing style, which means it writes the
* output into the "idx"-th slot in "delta_stream_output".
* We adopt the destination passing style to reduce the CPU data structure allocation and
* construction overhead.
* \param tokenizer The tokenizer for logprob process.
* \param max_single_sequence_length The maximum allowed single sequence length.
* \param delta_stream_output The delta token ids to return, the logprob JSON strings
* of each delta token id, and the optional finish reason.
* \param idx The index denoting which slot to write results in "delta_request_return".
*/
void GetDeltaRequestReturn(const Tokenizer& tokenizer, int64_t max_single_sequence_length,
RequestStreamOutput* delta_stream_output, int idx);
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RequestStateEntryNode>();
}
static constexpr const bool _type_has_method_sequal_reduce = false;
static constexpr const bool _type_has_method_shash_reduce = false;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("mlc.serve.RequestStateEntry", RequestStateEntryNode, Object);
};
class RequestStateEntry : public ObjectRef {
public:
explicit 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 = -1);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(RequestStateEntry, ObjectRef, RequestStateEntryNode);
};
/*! \brief A request's state, which groups all the request state entries. */
class RequestStateNode : public Object {
public:
/*! \brief the request state entries */
std::vector<RequestStateEntry> entries;
/*! \brief tracks the request metrics. */
RequestMetrics metrics;
/*!
* \brief The post-process data structures.
* We make it a state to avoid repetitive memory allocation/free in the action post process.
*/
RequestActionPostProcWorkspace postproc_states;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RequestStateNode>();
}
static constexpr const bool _type_has_method_sequal_reduce = false;
static constexpr const bool _type_has_method_shash_reduce = false;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("mlc.serve.RequestState", RequestStateNode, Object);
};
class RequestState : public ObjectRef {
public:
/*!
* \brief Request state constructor. We take the number of response (namely "n" in OpenAI
* API) to pre-allocate all the data structure, in order to reduce the CPU data structure
* allocation overhead when updating the request state.
*/
explicit RequestState(std::vector<RequestStateEntry> entries, int num_response,
std::chrono::high_resolution_clock::time_point add_time_point);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(RequestState, ObjectRef, RequestStateNode);
};
} // namespace serve
} // namespace llm
} // namespace mlc
#endif // MLC_LLM_SERVE_REQUEST_STATE_H_