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

510 lines
23 KiB
C++

/*!
* Copyright (c) 2023-2025 by Contributors
* \file serve/logit_processor.cc
* \brief The implementation of logit processor.
*/
#include "logit_processor.h"
#include <tvm/ffi/function.h>
#include <tvm/runtime/device_api.h>
#include <tvm/support/cuda/nvtx.h>
namespace mlc {
namespace llm {
namespace serve {
using tvm::support::NVTXScopedRange;
inline void CopyArray(Tensor src, Tensor dst, TVMStreamHandle copy_stream) {
DLTensor dl_dst = *(dst.operator->());
Tensor::CopyFromTo(src.operator->(), &dl_dst, copy_stream);
}
inline void SyncCopyStream(Device device, TVMStreamHandle compute_stream,
TVMStreamHandle copy_stream) {
// - If there is no particular copy stream, no action is needed.
if (copy_stream == nullptr) {
return;
}
// - Sync two streams.
DeviceAPI::Get(device)->SyncStreamFromTo(device, copy_stream, compute_stream);
}
/***************** LogitProcessor Implementation *****************/
TVM_FFI_STATIC_INIT_BLOCK() { LogitProcessorObj::RegisterReflection(); }
class LogitProcessorImpl : public LogitProcessorObj {
public:
/*! * \brief Constructor of LogitProcessorImpl. */
explicit LogitProcessorImpl(int max_num_token, int vocab_size, FunctionTable* ft, DLDevice device,
Optional<EventTraceRecorder> trace_recorder)
: max_num_token_(max_num_token),
vocab_size_(vocab_size),
bitmask_size_((vocab_size + 31) / 32),
softmax_func_(ft->softmax_func_),
device_(device),
apply_logit_bias_func_(ft->apply_logit_bias_func_),
apply_penalty_func_(ft->apply_penalty_func_),
apply_bitmask_func_(ft->apply_bitmask_func_),
trace_recorder_(std::move(trace_recorder)) {
Device preferred_host_device = GetPreferredHostDevice(device);
// Initialize auxiliary arrays on CPU.
seq_ids_host_ = Tensor::Empty({max_num_token}, dtype_i32_, preferred_host_device);
pos2seq_id_host_ =
Tensor::Empty({max_num_token * vocab_size}, dtype_i32_, preferred_host_device);
token_ids_host_ =
Tensor::Empty({max_num_token * vocab_size}, dtype_i32_, preferred_host_device);
token_cnt_host_ =
Tensor::Empty({max_num_token * vocab_size}, dtype_i32_, preferred_host_device);
token_logit_bias_host_ =
Tensor::Empty({max_num_token * vocab_size}, dtype_f32_, preferred_host_device);
penalties_host_ = Tensor::Empty({max_num_token, 3}, dtype_f32_, preferred_host_device);
bitmask_host_ =
Tensor::Empty({max_num_token, bitmask_size_}, dtype_i32_, preferred_host_device);
temperature_host_ = Tensor::Empty({max_num_token}, dtype_f32_, preferred_host_device);
// Initialize auxiliary arrays on GPU.
seq_ids_device_ = Tensor::Empty({max_num_token}, dtype_i32_, device);
pos2seq_id_device_ = Tensor::Empty({max_num_token * vocab_size}, dtype_i32_, device);
token_ids_device_ = Tensor::Empty({max_num_token * vocab_size}, dtype_i32_, device);
token_cnt_device_ = Tensor::Empty({max_num_token * vocab_size}, dtype_i32_, device);
token_logit_bias_device_ = Tensor::Empty({max_num_token * vocab_size}, dtype_f32_, device);
penalties_device_ = Tensor::Empty({max_num_token, 3}, dtype_f32_, device);
bitmask_device_ = Tensor::Empty({max_num_token, bitmask_size_}, dtype_i32_, device);
temperature_device_ = Tensor::Empty({max_num_token}, dtype_f32_, device);
TVM_FFI_ICHECK(apply_logit_bias_func_.defined())
<< "Function \"apply_logit_bias_inplace\" not found in model";
TVM_FFI_ICHECK(apply_penalty_func_.defined())
<< "Function \"apply_penalty_inplace\" not found in model";
TVM_FFI_ICHECK(apply_bitmask_func_.defined())
<< "Function \"apply_bitmask_inplace\" not found in model";
// If the device is CUDA/ROCm, we create a standalone copy stream, in
// purpose to hide the latency of auxiliary stream copy.
if (device.device_type == DLDeviceType::kDLCUDA ||
device.device_type == DLDeviceType::kDLROCM) {
// The compute stream is the default stream.
compute_stream_ = DeviceAPI::Get(device)->GetCurrentStream(device);
copy_stream_ = DeviceAPI::Get(device)->CreateStream(device);
}
}
~LogitProcessorImpl() {
// Free the copy stream if defined.
if (copy_stream_ != nullptr) {
DeviceAPI::Get(device_)->FreeStream(device_, copy_stream_);
}
}
void InplaceUpdateLogits(Tensor logits, //
const Array<GenerationConfig>& generation_cfg, //
const Array<RequestModelState>& mstates, //
const Array<String>& request_ids, //
const std::vector<int>* cum_num_token, //
const Array<RequestModelState>* draft_mstates, //
const std::vector<std::vector<int>>* draft_token_indices) final {
NVTXScopedRange nvtx_scope("Logit inplace update");
TVM_FFI_ICHECK_EQ(logits->ndim, 2);
TVM_FFI_ICHECK_EQ(logits->shape[1], vocab_size_);
TVM_FFI_ICHECK(logits.DataType() == (DLDataType{kDLFloat, 32, 1}));
TVM_FFI_ICHECK_EQ(generation_cfg.size(), mstates.size());
TVM_FFI_ICHECK_LE(logits->shape[0], max_num_token_);
int num_total_token = logits->shape[0];
int num_sequence = generation_cfg.size();
TVM_FFI_ICHECK((draft_mstates == nullptr) == (draft_token_indices == nullptr));
if (cum_num_token != nullptr) {
TVM_FFI_ICHECK(draft_mstates != nullptr);
TVM_FFI_ICHECK_EQ(cum_num_token->size(), num_sequence + 1);
TVM_FFI_ICHECK_EQ(cum_num_token->back(), num_total_token);
} else {
TVM_FFI_ICHECK_EQ(num_sequence, num_total_token);
}
if (draft_mstates != nullptr) {
TVM_FFI_ICHECK_EQ(draft_mstates->size(), num_sequence);
TVM_FFI_ICHECK_EQ(draft_token_indices->size(), num_sequence);
}
RECORD_EVENT(trace_recorder_, request_ids, "start update logits");
// Update 1. logit bias
RECORD_EVENT(trace_recorder_, request_ids, "start apply logit bias");
UpdateWithLogitBias(logits, generation_cfg, cum_num_token);
RECORD_EVENT(trace_recorder_, request_ids, "finish apply logit bias");
// Update 2. penalties
RECORD_EVENT(trace_recorder_, request_ids, "start apply penalty");
UpdateWithPenalty(logits, generation_cfg, mstates, cum_num_token, draft_mstates,
draft_token_indices);
RECORD_EVENT(trace_recorder_, request_ids, "finish apply penalty");
// Update 3. Vocabulary mask.
// Note: The mask application must be placed as the last step in logit processor.
// This is because the masked logits are set to the minimal value.
// Further logit subtraction may cause issue such as underflow.
RECORD_EVENT(trace_recorder_, request_ids, "start apply logit mask");
UpdateWithMask(logits, mstates, cum_num_token, draft_mstates, draft_token_indices);
RECORD_EVENT(trace_recorder_, request_ids, "finish apply logit mask");
RECORD_EVENT(trace_recorder_, request_ids, "finish update logits");
}
Tensor ComputeProbsFromLogits(Tensor logits, const Array<GenerationConfig>& generation_cfg,
const Array<String>& request_ids,
const std::vector<int>* cum_num_token) final {
NVTXScopedRange nvtx_scope("Compute probs from logits");
// logits: (n, v)
TVM_FFI_ICHECK_EQ(logits->ndim, 2);
TVM_FFI_ICHECK_LE(logits->shape[0], max_num_token_);
TVM_FFI_ICHECK_EQ(logits->shape[1], vocab_size_);
TVM_FFI_ICHECK(logits.DataType() == (DLDataType{kDLFloat, 32, 1}));
int num_total_token = logits->shape[0];
int num_sequence = generation_cfg.size();
if (cum_num_token != nullptr) {
TVM_FFI_ICHECK_EQ(cum_num_token->size(), num_sequence + 1);
TVM_FFI_ICHECK_EQ(cum_num_token->back(), num_total_token);
} else {
TVM_FFI_ICHECK_EQ(num_sequence, num_total_token);
}
RECORD_EVENT(trace_recorder_, request_ids, "start softmax");
// Construct:
// - temperature (max_num_token,) float32
float* p_temperature = static_cast<float*>(temperature_host_->data);
// - Set arrays.
for (int i = 0; i < num_sequence; ++i) {
int num_token_to_process =
cum_num_token == nullptr ? 1 : (cum_num_token->at(i + 1) - cum_num_token->at(i));
int token_offset = cum_num_token == nullptr ? i : cum_num_token->at(i);
for (int j = 0; j < num_token_to_process; ++j) {
p_temperature[token_offset + j] = std::max(generation_cfg[i]->temperature, 0.0);
}
}
// - View arrays.
Tensor temperature_host = temperature_host_.CreateView({num_total_token}, dtype_f32_);
Tensor temperature_device = temperature_device_.CreateView({num_total_token}, dtype_f32_);
// - Copy arrays to GPU.
CopyArray(/*src=*/temperature_host, /*dst=*/temperature_device, copy_stream_);
SyncCopyStream(device_, compute_stream_, copy_stream_);
// - Call kernel.
Tensor probs = softmax_func_(logits.CreateView({num_total_token, 1, vocab_size_}, dtype_f32_),
temperature_device)
.cast<Tensor>();
TVM_FFI_ICHECK_EQ(probs->ndim, 3);
TVM_FFI_ICHECK_EQ(probs->shape[0], num_total_token);
TVM_FFI_ICHECK_EQ(probs->shape[1], 1);
TVM_FFI_ICHECK_EQ(probs->shape[2], vocab_size_);
if (trace_recorder_.has_value()) {
DeviceAPI::Get(device_)->StreamSync(device_, /*stream=*/nullptr);
}
RECORD_EVENT(trace_recorder_, request_ids, "finish softmax");
return probs.CreateView({num_total_token, vocab_size_}, probs->dtype);
}
private:
void UpdateWithLogitBias(Tensor logits, const Array<GenerationConfig>& generation_cfg,
const std::vector<int>* cum_num_token) {
NVTXScopedRange nvtx_scope("UpdateWithLogitBias");
// Construct:
// - pos2seq_id (max_num_token * vocab_size,) int32
// - token_ids (max_num_token * vocab_size,) int32
// - token_logit_bias (max_num_token * vocab_size,) float32
int* p_pos2seq_id = static_cast<int*>(pos2seq_id_host_->data);
int* p_token_ids = static_cast<int*>(token_ids_host_->data);
float* p_token_logit_bias = static_cast<float*>(token_logit_bias_host_->data);
// - Set arrays.
int num_token_for_bias = 0;
int num_bias_token = 0;
for (int i = 0; i < static_cast<int>(generation_cfg.size()); ++i) {
int num_token_to_process =
cum_num_token == nullptr ? 1 : (cum_num_token->at(i + 1) - cum_num_token->at(i));
int token_offset = cum_num_token == nullptr ? i : cum_num_token->at(i);
for (int j = 0; j < num_token_to_process; ++j) {
if (!generation_cfg[i]->logit_bias.empty()) {
for (auto [token_id, bias] : generation_cfg[i]->logit_bias) {
p_pos2seq_id[num_bias_token] = token_offset + j;
p_token_ids[num_bias_token] = token_id;
p_token_logit_bias[num_bias_token] = bias;
++num_bias_token;
}
++num_token_for_bias;
}
}
}
if (num_token_for_bias == 0) {
return;
}
// - View arrays.
int num_token = num_bias_token;
Tensor pos2seq_id_host = pos2seq_id_host_.CreateView({num_token}, dtype_i32_);
Tensor pos2seq_id_device = pos2seq_id_device_.CreateView({num_token}, dtype_i32_);
Tensor token_ids_host = token_ids_host_.CreateView({num_token}, dtype_i32_);
Tensor token_ids_device = token_ids_device_.CreateView({num_token}, dtype_i32_);
Tensor token_logit_bias_host = token_logit_bias_host_.CreateView({num_token}, dtype_f32_);
Tensor token_logit_bias_device = token_logit_bias_device_.CreateView({num_token}, dtype_f32_);
// - Copy arrays to GPU.
CopyArray(/*src=*/pos2seq_id_host, /*dst=*/pos2seq_id_device, copy_stream_);
CopyArray(/*src=*/token_ids_host, /*dst=*/token_ids_device, copy_stream_);
CopyArray(/*src=*/token_logit_bias_host, /*dst=*/token_logit_bias_device, copy_stream_);
SyncCopyStream(device_, compute_stream_, copy_stream_);
// - Call kernel.
apply_logit_bias_func_(logits, pos2seq_id_device, token_ids_device, token_logit_bias_device);
if (trace_recorder_.has_value()) {
DeviceAPI::Get(device_)->StreamSync(device_, nullptr);
}
}
void UpdateWithPenalty(Tensor logits, const Array<GenerationConfig>& generation_cfg,
const Array<RequestModelState>& mstates,
const std::vector<int>* cum_num_token,
const Array<RequestModelState>* draft_mstates,
const std::vector<std::vector<int>>* draft_token_indices) {
NVTXScopedRange nvtx_scope("UpdateWithPenalty");
// Construct:
// - seq_ids (max_num_token,) int32
// - pos2seq_id (max_num_token * vocab_size,) int32
// - token_ids (max_num_token * vocab_size,) int32
// - token_cnt (max_num_token * vocab_size,) int32
// - penalties (max_num_token, 3) float32
int* p_seq_ids = static_cast<int*>(seq_ids_host_->data);
int* p_pos2seq_id = static_cast<int*>(pos2seq_id_host_->data);
int* p_token_ids = static_cast<int*>(token_ids_host_->data);
int* p_token_cnt = static_cast<int*>(token_cnt_host_->data);
float* p_penalties = static_cast<float*>(penalties_host_->data);
// - Set arrays.
int num_token_for_penalty = 0;
int num_penalty_appeared_token = 0;
for (int i = 0; i < static_cast<int>(generation_cfg.size()); ++i) {
if (generation_cfg[i]->frequency_penalty != 0.0 ||
generation_cfg[i]->presence_penalty != 0.0 ||
generation_cfg[i]->repetition_penalty != 1.0) {
int num_token_to_process =
cum_num_token == nullptr ? 1 : (cum_num_token->at(i + 1) - cum_num_token->at(i));
int token_offset = cum_num_token == nullptr ? i : cum_num_token->at(i);
TVM_FFI_ICHECK(num_token_to_process == 1 || mstates[i]->draft_output_tokens.empty());
TVM_FFI_ICHECK(draft_token_indices == nullptr ||
draft_token_indices->at(i).size() == num_token_to_process);
for (int j = 0; j < num_token_to_process; ++j) {
p_seq_ids[num_token_for_penalty] = token_offset + j;
std::vector<SampleResult> draft_token_seq;
// Update appeared_token_ids with draft tokens
if (draft_token_indices != nullptr) {
int cur_draft_token_index = draft_token_indices->at(i)[j];
while (cur_draft_token_index != -1) {
draft_token_seq.push_back(
(*draft_mstates)[i]->draft_output_tokens[cur_draft_token_index]);
cur_draft_token_index =
(*draft_mstates)[i]->draft_token_parent_idx[cur_draft_token_index];
}
}
auto& appeared_token_ids = mstates[i]->appeared_token_ids;
for (const auto& token : draft_token_seq) {
appeared_token_ids[token.GetTokenId()] += 1;
}
for (auto [token_id, cnt] : appeared_token_ids) {
p_pos2seq_id[num_penalty_appeared_token] = num_token_for_penalty;
p_token_ids[num_penalty_appeared_token] = token_id;
p_token_cnt[num_penalty_appeared_token] = cnt;
++num_penalty_appeared_token;
}
for (const auto& token : draft_token_seq) {
if ((--appeared_token_ids[token.GetTokenId()]) == 0) {
appeared_token_ids.erase(token.GetTokenId());
}
}
p_penalties[num_token_for_penalty * 3] = generation_cfg[i]->presence_penalty;
p_penalties[num_token_for_penalty * 3 + 1] = generation_cfg[i]->frequency_penalty;
p_penalties[num_token_for_penalty * 3 + 2] = generation_cfg[i]->repetition_penalty;
++num_token_for_penalty;
}
}
}
if (num_token_for_penalty == 0) {
return;
}
// - View arrays.
int num_seq = num_token_for_penalty;
int num_token = num_penalty_appeared_token;
Tensor seq_ids_host = seq_ids_host_.CreateView({num_seq}, dtype_i32_);
Tensor seq_ids_device = seq_ids_device_.CreateView({num_seq}, dtype_i32_);
Tensor pos2seq_id_host = pos2seq_id_host_.CreateView({num_token}, dtype_i32_);
Tensor pos2seq_id_device = pos2seq_id_device_.CreateView({num_token}, dtype_i32_);
Tensor token_ids_host = token_ids_host_.CreateView({num_token}, dtype_i32_);
Tensor token_ids_device = token_ids_device_.CreateView({num_token}, dtype_i32_);
Tensor token_cnt_host = token_cnt_host_.CreateView({num_token}, dtype_i32_);
Tensor token_cnt_device = token_cnt_device_.CreateView({num_token}, dtype_i32_);
Tensor penalties_host = penalties_host_.CreateView({num_seq, 3}, dtype_f32_);
Tensor penalties_device = penalties_device_.CreateView({num_seq, 3}, dtype_f32_);
// - Copy arrays to GPU.
CopyArray(/*src=*/seq_ids_host, /*dst=*/seq_ids_device, copy_stream_);
CopyArray(/*src=*/pos2seq_id_host, /*dst=*/pos2seq_id_device, copy_stream_);
CopyArray(/*src=*/token_ids_host, /*dst=*/token_ids_device, copy_stream_);
CopyArray(/*src=*/token_cnt_host, /*dst=*/token_cnt_device, copy_stream_);
CopyArray(/*src=*/penalties_host, /*dst=*/penalties_device, copy_stream_);
SyncCopyStream(device_, compute_stream_, copy_stream_);
// - Call kernel.
apply_penalty_func_(logits, seq_ids_device, pos2seq_id_device, token_ids_device,
token_cnt_device, penalties_device);
if (trace_recorder_.has_value()) {
DeviceAPI::Get(device_)->StreamSync(device_, nullptr);
}
}
void UpdateWithMask(Tensor logits, const Array<RequestModelState>& mstates,
const std::vector<int>* cum_num_token,
const Array<RequestModelState>* draft_mstates,
const std::vector<std::vector<int>>* draft_token_indices) {
NVTXScopedRange nvtx_scope("UpdateWithMask");
// Construct:
// - seq_ids (max_num_token,) int32
// - bitmask (max_num_token, ceildiv(vocab_size, 32)), int32
int32_t* p_seq_ids = static_cast<int32_t*>(seq_ids_host_->data);
uint32_t* p_bitmask = static_cast<uint32_t*>(bitmask_host_->data);
// - Set arrays.
int batch_size = logits->shape[0];
TVM_FFI_ICHECK((cum_num_token == nullptr && batch_size == mstates.size()) ||
(cum_num_token != nullptr && batch_size == cum_num_token->back()));
std::memset(p_seq_ids, 0, batch_size * sizeof(int32_t));
for (int i = 0; i < static_cast<int>(mstates.size()); ++i) {
int token_start_offset = cum_num_token == nullptr ? i : cum_num_token->at(i);
int token_number =
cum_num_token == nullptr ? 1 : (cum_num_token->at(i + 1) - cum_num_token->at(i));
bool require_mask = mstates[i]->RequireNextTokenBitmask();
TVM_FFI_ICHECK(draft_token_indices == nullptr ||
draft_token_indices->at(i).size() == token_number);
for (int j = 0; j < token_number; ++j) {
if (require_mask) {
std::vector<SampleResult> draft_token_seq;
if (draft_token_indices != nullptr) {
int cur_draft_token_index = draft_token_indices->at(i)[j];
while (cur_draft_token_index != -1) {
draft_token_seq.push_back(
(*draft_mstates)[i]->draft_output_tokens[cur_draft_token_index]);
cur_draft_token_index =
(*draft_mstates)[i]->draft_token_parent_idx[cur_draft_token_index];
}
for (auto it = draft_token_seq.rbegin(); it != draft_token_seq.rend(); ++it) {
mstates[i]->grammar_matcher.value().AcceptToken(it->GetTokenId());
}
}
// Find a slice of bitmask_host_: bitmask_host_[num_token_for_mask, :]
DLTensor bitmask_dltensor = *bitmask_host_.operator->();
int64_t bitmask_shape[] = {bitmask_size_};
bitmask_dltensor.data = p_bitmask + (token_start_offset + j) * bitmask_size_;
bitmask_dltensor.shape = bitmask_shape;
bitmask_dltensor.ndim = 1;
mstates[i]->GetNextTokenBitmask(&bitmask_dltensor);
p_seq_ids[token_start_offset + j] = 1;
if (draft_token_seq.size() > 0) {
mstates[i]->grammar_matcher.value().Rollback(draft_token_seq.size());
}
}
}
}
int num_token_for_mask = 0;
for (int i = 0; i < batch_size; ++i) {
if (p_seq_ids[i] == 1) {
p_seq_ids[num_token_for_mask] = i;
++num_token_for_mask;
}
}
if (num_token_for_mask == 0) {
return;
}
// - View arrays.
int num_seq = num_token_for_mask;
Tensor seq_ids_host = seq_ids_host_.CreateView({num_seq}, dtype_i32_);
Tensor seq_ids_device = seq_ids_device_.CreateView({num_seq}, dtype_i32_);
Tensor bitmask_host = bitmask_host_.CreateView({batch_size, bitmask_size_}, dtype_i32_);
Tensor bitmask_device = bitmask_device_.CreateView({batch_size, bitmask_size_}, dtype_i32_);
// - Copy arrays to GPU.
CopyArray(/*src=*/seq_ids_host, /*dst=*/seq_ids_device, copy_stream_);
CopyArray(/*src=*/bitmask_host, /*dst=*/bitmask_device, copy_stream_);
SyncCopyStream(device_, compute_stream_, copy_stream_);
// - Call kernel.
apply_bitmask_func_(logits, seq_ids_device, bitmask_device);
if (trace_recorder_.has_value()) {
DeviceAPI::Get(device_)->StreamSync(device_, nullptr);
}
}
// Model configurations
const int max_num_token_;
const int vocab_size_;
const int bitmask_size_;
const DLDataType dtype_i32_ = DLDataType{kDLInt, 32, 1};
const DLDataType dtype_u32_ = DLDataType{kDLUInt, 32, 1};
const DLDataType dtype_f32_ = DLDataType{kDLFloat, 32, 1};
// Packed functions.
Device device_;
Function softmax_func_;
Function apply_logit_bias_func_;
Function apply_penalty_func_;
Function apply_bitmask_func_;
// Auxiliary Tensors on CPU
Tensor seq_ids_host_;
Tensor pos2seq_id_host_;
Tensor token_ids_host_;
Tensor token_cnt_host_;
Tensor token_logit_bias_host_;
Tensor penalties_host_;
Tensor bitmask_host_;
Tensor temperature_host_;
// Auxiliary Tensors on GPU
Tensor seq_ids_device_;
Tensor pos2seq_id_device_;
Tensor token_ids_device_;
Tensor token_cnt_device_;
Tensor token_logit_bias_device_;
Tensor penalties_device_;
Tensor bitmask_device_;
Tensor temperature_device_;
// Event trace recorder.
Optional<EventTraceRecorder> trace_recorder_;
// The device stream for the default computation operations.
TVMStreamHandle compute_stream_ = nullptr;
// The device stream for copying auxiliary data structure to GPU.
TVMStreamHandle copy_stream_ = nullptr;
// A small epsilon.
const double eps_ = 1e-5;
};
LogitProcessor::LogitProcessor(int max_num_token, int vocab_size, FunctionTable* ft,
DLDevice device, Optional<EventTraceRecorder> trace_recorder) {
data_ = tvm::ffi::make_object<LogitProcessorImpl>(max_num_token, vocab_size, ft, device,
std::move(trace_recorder));
}
} // namespace serve
} // namespace llm
} // namespace mlc