/*! * Copyright (c) 2023-2025 by Contributors * \file serve/logit_processor.cc * \brief The implementation of logit processor. */ #include "logit_processor.h" #include #include #include 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 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& generation_cfg, // const Array& mstates, // const Array& request_ids, // const std::vector* cum_num_token, // const Array* draft_mstates, // const std::vector>* 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& generation_cfg, const Array& request_ids, const std::vector* 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(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(); 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& generation_cfg, const std::vector* 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(pos2seq_id_host_->data); int* p_token_ids = static_cast(token_ids_host_->data); float* p_token_logit_bias = static_cast(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(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& generation_cfg, const Array& mstates, const std::vector* cum_num_token, const Array* draft_mstates, const std::vector>* 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(seq_ids_host_->data); int* p_pos2seq_id = static_cast(pos2seq_id_host_->data); int* p_token_ids = static_cast(token_ids_host_->data); int* p_token_cnt = static_cast(token_cnt_host_->data); float* p_penalties = static_cast(penalties_host_->data); // - Set arrays. int num_token_for_penalty = 0; int num_penalty_appeared_token = 0; for (int i = 0; i < static_cast(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 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& mstates, const std::vector* cum_num_token, const Array* draft_mstates, const std::vector>* 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(seq_ids_host_->data); uint32_t* p_bitmask = static_cast(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(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 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 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 trace_recorder) { data_ = tvm::ffi::make_object(max_num_token, vocab_size, ft, device, std::move(trace_recorder)); } } // namespace serve } // namespace llm } // namespace mlc