/*! * Copyright (c) 2023-2025 by Contributors * \file serve/model.cc * \brief The implementation of runtime module of LLM functions (prefill/decode/etc.) */ #include "model.h" #include #include #include #include #include #include #include "../support/json_parser.h" #include "../support/vlm_utils.h" #include "config.h" #include "logit_processor.h" namespace mlc { namespace llm { namespace serve { using tvm::support::NVTXScopedRange; /*********************** Model Implementation ***********************/ TVM_FFI_STATIC_INIT_BLOCK() { ModelObj::RegisterReflection(); } class ModelImpl; Model Model::Create(String reload_lib_path, String model_path, const tvm::ffi::json::Object& model_config, DLDevice device, const Optional& session, int num_shards, int num_stages, bool trace_enabled) { return Model(tvm::ffi::make_object(reload_lib_path, model_path, model_config, device, session, num_shards, num_stages, trace_enabled)); } Result Model::LoadModelConfig(const String& model_path) { using TResult = Result; std::ifstream config_istream((model_path + "/mlc-chat-config.json").c_str()); std::ostringstream config_ostream; TVM_FFI_ICHECK(config_istream); config_ostream << config_istream.rdbuf(); std::string config_str = config_ostream.str(); tvm::ffi::String err; auto config_json = tvm::ffi::json::Parse(config_str, &err); if (!err.empty()) { return TResult::Error(std::string(err)); } auto opt = config_json.try_cast(); if (!opt.has_value()) { return TResult::Error("Expected JSON object in model config"); } return TResult::Ok(*opt); } class ModelImpl : public ModelObj { public: /*! * \brief Constructor of ModelImpl. * \sa Model::Create */ explicit ModelImpl(String reload_lib_path, String model_path, tvm::ffi::json::Object model_config, DLDevice device, const Optional& session, int num_shards, int num_stages, bool trace_enabled) : model_(model_path), device_(device), trace_enabled_(trace_enabled) { // Step 1. Process model config json string. LoadModelConfigJSON(model_config); // Step 2. Initialize vm, we use the packed function mechanism // so there is no explicit abi dependency on these extra // classes other than basic tvm runtime. this->ft_.Init(reload_lib_path, device_, model_config, session, num_shards, num_stages); this->num_shards_ = ft_.model_metadata_.tensor_parallel_shards; this->num_stages_ = ft_.model_metadata_.pipeline_parallel_stages; this->seqlen_padding_factor_ = ft_.model_metadata_.seqlen_padding_factor; // Step 3. Reset this->Reset(); // Step 4. Set model type this->kind = GetMetadata().kv_state_kind; } /*********************** Model Computation ***********************/ ObjectRef TokenEmbed(Shape token_ids, ObjectRef* dst, int offset) final { NVTXScopedRange nvtx_scope("TokenEmbed"); int num_tokens = token_ids.size(); if (seqlen_padding_factor_ > 1) { num_tokens = (offset + num_tokens + seqlen_padding_factor_ - 1) / seqlen_padding_factor_ * seqlen_padding_factor_; } // Copy input token ids to device. DLDataType dtype(DLDataType{kDLInt, 32, 1}); Tensor token_ids_nd; { NVTXScopedRange nvtx_scope("Allocate token_ids at offset"); token_ids_nd = token_ids_storage_->AllocTensor(offset * 4, {num_tokens}, dtype); int* p_token_ids = static_cast(token_ids_nd->data) + (token_ids_nd->byte_offset) / 4; for (int i = 0; i < static_cast(token_ids.size()); ++i) { p_token_ids[i] = token_ids[i]; } for (int i = static_cast(token_ids.size()); i < num_tokens; ++i) { p_token_ids[i] = 0; } } TVM_FFI_ICHECK_EQ(token_ids_nd->ndim, 1); TVM_FFI_ICHECK_EQ(token_ids_nd->shape[0], num_tokens); TVM_FFI_ICHECK_NE(prefill_chunk_size_, -1); ObjectRef token_ids_dref_or_nd; { NVTXScopedRange nvtx_scope("Copy to worker 0"); token_ids_dref_or_nd = ft_.CopyToWorker0(token_ids_nd, "token_ids", {prefill_chunk_size_}); } ObjectRef embeddings = ft_.embed_func_(token_ids_dref_or_nd, params_).cast(); if (dst != nullptr) { TVM_FFI_ICHECK(dst->defined()); ft_.nd_copy_embedding_to_offset_func_(embeddings, *dst, offset); return *dst; } else { TVM_FFI_ICHECK_EQ(offset, 0); return embeddings; } } ObjectRef ImageEmbed(const Tensor& image, ObjectRef* dst, int offset) final { NVTXScopedRange nvtx_scope("ImageEmbed"); TVM_FFI_ICHECK(ft_.image_embed_func_.defined()) << "`image_embed` function is not found in the model. "; int tmp_h = 0, tmp_w = 0; CalculateResizeShape(image, this->model_type_, &tmp_h, &tmp_w); Shape resize_h = {tmp_h}; Shape resize_w = {tmp_w}; CalculateCropShape(image, this->model_type_, &tmp_h, &tmp_w); Shape crop_h = {tmp_h}; Shape crop_w = {tmp_w}; auto image_dref_or_nd = ft_.CopyToWorker0(image, "image", image.Shape()); ObjectRef embeddings = ft_.image_embed_func_(image_dref_or_nd, resize_h, resize_w, crop_h, crop_w, params_) .cast(); if (dst != nullptr) { TVM_FFI_ICHECK(dst->defined()); ft_.nd_copy_embedding_to_offset_func_(embeddings, *dst, offset); return *dst; } else { TVM_FFI_ICHECK_EQ(offset, 0); return embeddings; } } bool CanGetLogits() final { return ft_.get_logits_func_.defined() && ft_.batch_get_logits_func_.defined(); } Tensor GetLogits(const ObjectRef& hidden_states) final { NVTXScopedRange nvtx_scope("GetLogits"); TVM_FFI_ICHECK(ft_.get_logits_func_.defined()) << "`get_logits` function is not found in the model."; ObjectRef hidden_states_dref_or_nd{nullptr}; if (!ft_.use_disco && hidden_states->IsInstance()) { hidden_states_dref_or_nd = hidden_states.as_or_throw()->DebugGetFromRemote(0).cast(); } else { hidden_states_dref_or_nd = hidden_states; } ObjectRef ret = ft_.get_logits_func_(hidden_states_dref_or_nd, params_).cast(); if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } Tensor logits{nullptr}; if (ft_.use_disco) { logits = ret.as_or_throw()->DebugGetFromRemote(0).cast(); } else { logits = ret.as_or_throw(); } // logits: (b * s, v) return logits; } Array GetMultiStepLogits(const ObjectRef& hidden_states) final { NVTXScopedRange nvtx_scope("GetMultiStepLogits"); TVM_FFI_ICHECK(ft_.get_logits_func_.defined()) << "`get_logits` function is not found in the model."; ObjectRef hidden_states_dref_or_nd{nullptr}; ObjectRef ret = ft_.get_logits_func_(hidden_states, params_).cast(); Array logits{nullptr}; if (ft_.use_disco) { logits = ret.as_or_throw()->DebugGetFromRemote(0).cast>(); } else { logits = ret.as_or_throw>(); } return logits; } ObjectRef FuseEmbedHidden(const ObjectRef& embeddings, const ObjectRef& previous_hidden_states, int batch_size, int seq_len) final { NVTXScopedRange nvtx_scope("FuseEmbedHidden"); ObjectRef embeddings_dref_or_nd{nullptr}; if (!embeddings->IsInstance()) { // embeddings: (n, h) Tensor embeddings_nd = embeddings.as_or_throw(); TVM_FFI_ICHECK_NE(hidden_size_, -1); TVM_FFI_ICHECK_EQ(embeddings_nd->ndim, 2); TVM_FFI_ICHECK_GE(embeddings_nd->shape[0], batch_size * seq_len); TVM_FFI_ICHECK_EQ(embeddings_nd->shape[1], hidden_size_); embeddings_dref_or_nd = embeddings_nd.CreateView({batch_size * seq_len, hidden_size_}, embeddings_nd->dtype); } else { Shape embedding_shape{batch_size * seq_len, hidden_size_}; embeddings_dref_or_nd = ft_.nd_view_func_(embeddings, embedding_shape).cast(); } ObjectRef previous_hidden_states_dref_or_nd{nullptr}; if (!ft_.use_disco && previous_hidden_states->IsInstance()) { previous_hidden_states_dref_or_nd = previous_hidden_states.as_or_throw()->DebugGetFromRemote(0).cast(); } else { previous_hidden_states_dref_or_nd = previous_hidden_states; } ObjectRef fused = ft_.fuse_embed_hidden_func_(embeddings_dref_or_nd, previous_hidden_states_dref_or_nd, params_) .cast(); if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } Shape out_shape{batch_size, seq_len, hidden_size_}; if (ft_.use_disco) { return ft_.nd_view_func_(fused, out_shape).cast(); } else { Tensor fused_nd = fused.as_or_throw(); TVM_FFI_ICHECK_EQ(fused_nd->ndim, 2); TVM_FFI_ICHECK_EQ(fused_nd->shape[0], batch_size * seq_len); return fused_nd.CreateView(out_shape, fused_nd->dtype); } } Tensor BatchPrefill(const ObjectRef& embeddings, const std::vector& seq_ids, const std::vector& lengths) final { TVM_FFI_ICHECK(!seq_ids.empty()); TVM_FFI_ICHECK_EQ(seq_ids.size(), lengths.size()); int num_sequences = seq_ids.size(); int total_length = 0; int* p_logit_pos = static_cast(logit_pos_arr_->data); for (int i = 0; i < num_sequences; ++i) { total_length += lengths[i]; p_logit_pos[i] = total_length - 1; } bool padded = total_length % seqlen_padding_factor_ != 0; if (padded) { total_length = (total_length + seqlen_padding_factor_ - 1) / seqlen_padding_factor_ * seqlen_padding_factor_; } NVTXScopedRange nvtx_scope("BatchPrefill num_seq=" + std::to_string(num_sequences) + " total_len=" + std::to_string(total_length)); Tensor logit_pos_nd = logit_pos_arr_.CreateView({num_sequences}, DLDataType{kDLInt, 32, 1}); TVM_FFI_ICHECK(ft_.prefill_func_.defined()) << "`prefill_with_embed` function is not found in the model. Please make sure the model is " "compiled with flag `--sep-embed` and `--enable-batching`"; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(lengths.begin(), lengths.end()); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple); if (kind == KVStateKind::kHybrid) { TVM_FFI_ICHECK(rnn_state_.defined()) << "RNN state has not been initialized."; ft_.kv_cache_begin_forward_func_(rnn_state_, seq_ids_tuple, lengths_tuple); } ObjectRef embeddings_dref_or_nd; if (!embeddings->IsInstance()) { // embeddings: (1, n, h) Tensor embeddings_nd = embeddings.as_or_throw(); TVM_FFI_ICHECK_NE(hidden_size_, -1); TVM_FFI_ICHECK_EQ(embeddings_nd->ndim, 2); TVM_FFI_ICHECK_GE(embeddings_nd->shape[0], total_length); TVM_FFI_ICHECK_EQ(embeddings_nd->shape[1], hidden_size_); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_type, device_.device_type); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_id, device_.device_id); embeddings_dref_or_nd = embeddings_nd.CreateView({1, total_length, hidden_size_}, embeddings_nd->dtype); } else { Shape embedding_shape{1, total_length, hidden_size_}; embeddings_dref_or_nd = ft_.nd_view_func_(embeddings, embedding_shape).cast(); } TVM_FFI_ICHECK_NE(max_num_sequence_, -1); ObjectRef logit_pos_dref_or_nd = ft_.CopyToWorker0(logit_pos_nd, "logit_pos", {max_num_sequence_}); Function single_batch_prefill_func = ft_.single_batch_prefill_func_; Function prefill_func = ft_.prefill_func_; if (ft_.single_batch_extend_func_.defined()) { TVM_FFI_ICHECK(ft_.extend_func_.defined()) << "`batch_extend` function is not found in the model."; bool has_existing_sequence = false; for (int64_t seq_id : seq_ids) { if (prefilled_seq_ids_.count(seq_id)) { has_existing_sequence = true; break; } } if (has_existing_sequence) { single_batch_prefill_func = ft_.single_batch_extend_func_; prefill_func = ft_.extend_func_; } for (int64_t seq_id : seq_ids) { prefilled_seq_ids_.insert(seq_id); } } // args: embeddings, logit_pos, kv_cache, [rnn_state,] params ObjectRef ret; if (kind == KVStateKind::kHybrid) { // Hybrid always uses batch_prefill (single_batch prefill has tensor-based GDN args). ret = prefill_func(embeddings_dref_or_nd, logit_pos_dref_or_nd, kv_cache_, rnn_state_, params_) .cast(); } else if (seq_ids.size() == 1 && !padded) { ret = single_batch_prefill_func(embeddings_dref_or_nd, kv_cache_, params_).cast(); } else { ret = prefill_func(embeddings_dref_or_nd, logit_pos_dref_or_nd, kv_cache_, params_) .cast(); } Tensor logits; if (ft_.use_disco) { ret = ft_.tuple_getitem_func_(ret, 0).cast(); if (num_stages_ > 1) { // Send the result from the last worker group to worker 0. Shape shape{1, num_sequences, vocab_size_}; DLDataType dtype = DLDataType{kDLFloat, 32, 1}; ret = ft_.last_group_send_to_worker_0_(ret, disco_logits_arr_, shape, dtype) .cast(); } logits = ret.as_or_throw()->DebugGetFromRemote(0).cast(); } else { // The function returns a (logits, kv_cache) tuple; extract element 0 as a // generic Any before casting to Tensor. Casting the whole tuple to // Array now fails strict element-type checking on the kv_cache. logits = ret.as_or_throw>()[0].cast(); } if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } ft_.kv_cache_end_forward_func_(kv_cache_); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_end_forward_func_(rnn_state_); } // logits: (1, num_sequences, v) TVM_FFI_ICHECK_EQ(logits->ndim, 3); TVM_FFI_ICHECK_EQ(logits->shape[0], 1); TVM_FFI_ICHECK_EQ(logits->shape[1], num_sequences); return logits; } ObjectRef BatchPrefillToLastHidden(const ObjectRef& embedding_or_hidden_states, const std::vector& seq_ids, const std::vector& lengths) final { NVTXScopedRange nvtx_scope("BatchPrefillToLastHidden"); TVM_FFI_ICHECK(!seq_ids.empty()); TVM_FFI_ICHECK_EQ(seq_ids.size(), lengths.size()); int num_sequences = seq_ids.size(); int total_length = 0; for (int i = 0; i < num_sequences; ++i) { total_length += lengths[i]; } ObjectRef embedding_or_hidden_states_dref_or_nd{nullptr}; Shape hidden_states_shape{1, total_length, hidden_size_}; if (!ft_.use_disco) { Tensor embedding_or_hidden_states_nd = embedding_or_hidden_states.as_or_throw(); embedding_or_hidden_states_dref_or_nd = embedding_or_hidden_states_nd.CreateView( hidden_states_shape, embedding_or_hidden_states_nd->dtype); } else { embedding_or_hidden_states_dref_or_nd = ft_.nd_view_func_(embedding_or_hidden_states, hidden_states_shape).cast(); } TVM_FFI_ICHECK(ft_.prefill_to_last_hidden_func_.defined()) << "`prefill_to_last_hidden_states` function is not found in the model."; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(lengths.begin(), lengths.end()); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_begin_forward_func_(rnn_state_, seq_ids_tuple, lengths_tuple); } // args: embeddings, logit_pos, kv_cache, params ObjectRef result{nullptr}; if (seq_ids.size() == 1) { TVM_FFI_ICHECK(ft_.single_batch_prefill_to_last_hidden_func_.defined()) << "`single_batch_prefill_to_last_hidden_states` function is not found in the model."; if (kind == KVStateKind::kHybrid) { result = ft_.single_batch_prefill_to_last_hidden_func_( embedding_or_hidden_states_dref_or_nd, kv_cache_, rnn_state_, params_) .cast(); } else { result = ft_.single_batch_prefill_to_last_hidden_func_( embedding_or_hidden_states_dref_or_nd, kv_cache_, params_) .cast(); } } else { if (kind == KVStateKind::kHybrid) { result = ft_.prefill_to_last_hidden_func_(embedding_or_hidden_states_dref_or_nd, kv_cache_, rnn_state_, params_) .cast(); } else { result = ft_.prefill_to_last_hidden_func_(embedding_or_hidden_states_dref_or_nd, kv_cache_, params_) .cast(); } } ObjectRef hidden_states = ft_.tuple_getitem_func_(result, 0).cast(); if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } ft_.kv_cache_end_forward_func_(kv_cache_); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_end_forward_func_(rnn_state_); } Shape out_shape{total_length, hidden_size_}; if (ft_.use_disco) { return ft_.nd_view_func_(hidden_states, out_shape).cast(); } else { Tensor hidden_states_nd = hidden_states.as_or_throw(); TVM_FFI_ICHECK_EQ(hidden_states_nd->ndim, 3); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[0], 1); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[1], total_length); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[2], hidden_size_); return hidden_states_nd.CreateView(out_shape, hidden_states_nd->dtype); } } Tensor BatchDecode(const ObjectRef& embeddings, const std::vector& seq_ids) final { NVTXScopedRange nvtx_scope("BatchDecode num_seqs=" + std::to_string(seq_ids.size())); int num_sequence = seq_ids.size(); TVM_FFI_ICHECK(ft_.decode_func_.defined()) << "`decode_with_embed` function is not found in the model. Please make sure the model is " "compiled with flag `--sep-embed` and `--enable-batching`"; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Reserve in KV cache for the lengths of the input. // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(std::vector(/*n=*/seq_ids.size(), /*v=*/1)); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_begin_forward_func_(rnn_state_, seq_ids_tuple, lengths_tuple); } ObjectRef embeddings_dref_or_nd; if (!embeddings->IsInstance()) { // embeddings: (1, b, h) Tensor embeddings_nd = embeddings.as_or_throw(); TVM_FFI_ICHECK_NE(hidden_size_, -1); TVM_FFI_ICHECK_EQ(embeddings_nd->ndim, 2); TVM_FFI_ICHECK_GE(embeddings_nd->shape[0], num_sequence); TVM_FFI_ICHECK_EQ(embeddings_nd->shape[1], hidden_size_); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_type, device_.device_type); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_id, device_.device_id); embeddings_dref_or_nd = embeddings_nd.CreateView({num_sequence, 1, hidden_size_}, embeddings_nd->dtype); } else { Shape embedding_shape{num_sequence, 1, hidden_size_}; embeddings_dref_or_nd = ft_.nd_view_func_(embeddings, embedding_shape).cast(); } // args: embeddings, kv_cache, [rnn_state,] params ObjectRef ret; if (kind == KVStateKind::kHybrid) { // Hybrid always uses batch_decode (single_batch decode has tensor-based GDN args). ret = ft_.decode_func_(embeddings_dref_or_nd, kv_cache_, rnn_state_, params_).cast(); } else if (seq_ids.size() == 1) { ret = ft_.single_batch_decode_func_(embeddings_dref_or_nd, kv_cache_, params_) .cast(); } else { ret = ft_.decode_func_(embeddings_dref_or_nd, kv_cache_, params_).cast(); } Tensor logits; if (ft_.use_disco) { ret = ft_.tuple_getitem_func_(ret, 0).cast(); if (num_stages_ > 1) { // Send the result from the last worker group to worker 0. Shape shape{num_sequence, 1, vocab_size_}; DLDataType dtype = DLDataType{kDLFloat, 32, 1}; ret = ft_.last_group_send_to_worker_0_(ret, disco_logits_arr_, shape, dtype) .cast(); } logits = ret.as_or_throw()->DebugGetFromRemote(0).cast(); } else { // The function returns a (logits, kv_cache) tuple; extract element 0 as a // generic Any before casting to Tensor. Casting the whole tuple to // Array now fails strict element-type checking on the kv_cache. logits = ret.as_or_throw>()[0].cast(); } if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } ft_.kv_cache_end_forward_func_(kv_cache_); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_end_forward_func_(rnn_state_); } // logits: (b, 1, v) TVM_FFI_ICHECK_EQ(logits->ndim, 3); TVM_FFI_ICHECK_EQ(logits->shape[0], num_sequence); TVM_FFI_ICHECK_EQ(logits->shape[1], 1); return logits; } Tensor BatchTreeDecode(const ObjectRef& embeddings, const std::vector& seq_ids, const std::vector& lengths, const std::vector& token_tree_parent_ptr) { // This is similar to BatchDecode, except that it takes 'length', so that each sequence can have // multiple leaf nodes for decoding. NVTXScopedRange nvtx_scope("BatchTreeDecode num_seqs=" + std::to_string(seq_ids.size())); int num_sequence = seq_ids.size(); int total_length = 0; for (int i = 0; i < num_sequence; ++i) { total_length += lengths[i]; } TVM_FFI_ICHECK_EQ(total_length, token_tree_parent_ptr.size()); TVM_FFI_ICHECK(ft_.decode_func_.defined()) << "`tree_decode_with_embed` function is not found in the model. Please make sure the " "model " "is compiled with flag `--sep-embed` and `--enable-batching`"; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Reserve in KV cache for the lengths of the input. // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(lengths.begin(), lengths.end()); Shape token_tree_parent_ptr_tuple(token_tree_parent_ptr); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple, token_tree_parent_ptr_tuple); ObjectRef embeddings_dref_or_nd; if (!embeddings->IsInstance()) { // embeddings: (1, n, h) Tensor embeddings_nd = embeddings.as_or_throw(); TVM_FFI_ICHECK_NE(hidden_size_, -1); TVM_FFI_ICHECK_EQ(embeddings_nd->ndim, 2); TVM_FFI_ICHECK_GE(embeddings_nd->shape[0], total_length); TVM_FFI_ICHECK_EQ(embeddings_nd->shape[1], hidden_size_); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_type, device_.device_type); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_id, device_.device_id); embeddings_dref_or_nd = embeddings_nd.CreateView({total_length, 1, hidden_size_}, embeddings_nd->dtype); } else { Shape embedding_shape{total_length, 1, hidden_size_}; embeddings_dref_or_nd = ft_.nd_view_func_(embeddings, embedding_shape).cast(); } // same as BatchDecode ObjectRef ret; if (0 && seq_ids.size() == 1) { ret = ft_.single_batch_decode_func_(embeddings_dref_or_nd, kv_cache_, params_) .cast(); } else { ret = ft_.decode_func_(embeddings_dref_or_nd, kv_cache_, params_).cast(); } Tensor logits; if (ft_.use_disco) { Array result = ret.as_or_throw()->DebugGetFromRemote(0).cast>(); logits = result[0].as_or_throw(); } else { // The function returns a (logits, kv_cache) tuple; extract element 0 as a // generic Any before casting to Tensor. Casting the whole tuple to // Array now fails strict element-type checking on the kv_cache. logits = ret.as_or_throw>()[0].cast(); } if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } ft_.kv_cache_end_forward_func_(kv_cache_); // logits: (b, 1, v) TVM_FFI_ICHECK_EQ(logits->ndim, 3); TVM_FFI_ICHECK_EQ(logits->shape[0], total_length); TVM_FFI_ICHECK_EQ(logits->shape[1], 1); return logits; } ObjectRef BatchDecodeToLastHidden(const ObjectRef& hidden_states_dref_or_nd, const std::vector& seq_ids) final { NVTXScopedRange nvtx_scope("BatchDecodeToLastHidden num_seqs=" + std::to_string(seq_ids.size())); int num_sequence = seq_ids.size(); TVM_FFI_ICHECK(ft_.decode_to_last_hidden_func_.defined()) << "`batch_decode_to_last_hidden_states` function is not found in the model."; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Reserve in KV cache for the lengths of the input. // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(std::vector(/*n=*/seq_ids.size(), /*v=*/1)); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_begin_forward_func_(rnn_state_, seq_ids_tuple, lengths_tuple); } // args: embeddings, kv_cache, params ObjectRef result{nullptr}; if (seq_ids.size() == 1) { TVM_FFI_ICHECK(ft_.single_batch_decode_to_last_hidden_func_.defined()) << "`decode_to_last_hidden_states` function is not found in the model."; if (kind == KVStateKind::kHybrid) { result = ft_.single_batch_decode_to_last_hidden_func_(hidden_states_dref_or_nd, kv_cache_, rnn_state_, params_) .cast(); } else { result = ft_.single_batch_decode_to_last_hidden_func_(hidden_states_dref_or_nd, kv_cache_, params_) .cast(); } } else { if (kind == KVStateKind::kHybrid) { result = ft_.decode_to_last_hidden_func_(hidden_states_dref_or_nd, kv_cache_, rnn_state_, params_) .cast(); } else { result = ft_.decode_to_last_hidden_func_(hidden_states_dref_or_nd, kv_cache_, params_) .cast(); } } ft_.kv_cache_end_forward_func_(kv_cache_); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_end_forward_func_(rnn_state_); } ObjectRef hidden_states = ft_.tuple_getitem_func_(result, 0).cast(); if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } // hidden_states: (b, 1, v) to (b, v) Shape out_shape{num_sequence, hidden_size_}; if (ft_.use_disco) { return ft_.nd_view_func_(hidden_states, out_shape).cast(); } else { Tensor hidden_states_nd = hidden_states.as_or_throw(); TVM_FFI_ICHECK_EQ(hidden_states_nd->ndim, 3); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[0], num_sequence); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[1], 1); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[2], hidden_size_); return hidden_states_nd.CreateView(out_shape, hidden_states_nd->dtype); } } Tensor BatchVerify(const ObjectRef& embeddings, const std::vector& seq_ids, const std::vector& lengths, const std::vector& token_tree_parent_ptr) final { TVM_FFI_ICHECK(!seq_ids.empty()); TVM_FFI_ICHECK_EQ(seq_ids.size(), lengths.size()); int num_sequences = seq_ids.size(); int total_length = 0; for (int i = 0; i < num_sequences; ++i) { total_length += lengths[i]; } TVM_FFI_ICHECK_EQ(total_length, token_tree_parent_ptr.size()); NVTXScopedRange nvtx_scope("BatchVerify num_tokens=" + std::to_string(total_length)); TVM_FFI_ICHECK(ft_.verify_func_.defined()) << "`verify_with_embed` function is not found in the model. Please make sure the model is " "compiled with flag `--sep-embed` and `--enable-batching`"; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(lengths.begin(), lengths.end()); Shape token_tree_parent_ptr_tuple(token_tree_parent_ptr); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple, token_tree_parent_ptr_tuple); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_begin_forward_func_(rnn_state_, seq_ids_tuple, lengths_tuple, token_tree_parent_ptr_tuple); } ObjectRef embeddings_dref_or_nd; if (!embeddings->IsInstance()) { // embeddings: (1, n, h) Tensor embeddings_nd = embeddings.as_or_throw(); TVM_FFI_ICHECK_NE(hidden_size_, -1); TVM_FFI_ICHECK_EQ(embeddings_nd->ndim, 2); TVM_FFI_ICHECK_GE(embeddings_nd->shape[0], total_length); TVM_FFI_ICHECK_EQ(embeddings_nd->shape[1], hidden_size_); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_type, device_.device_type); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_id, device_.device_id); embeddings_dref_or_nd = embeddings_nd.CreateView({1, total_length, hidden_size_}, embeddings_nd->dtype); } else { Shape embedding_shape{1, total_length, hidden_size_}; embeddings_dref_or_nd = ft_.nd_view_func_(embeddings, embedding_shape).cast(); } // args: embeddings, kv_cache, [rnn_state,] params ObjectRef ret; if (kind == KVStateKind::kHybrid) { ret = ft_.verify_func_(embeddings_dref_or_nd, kv_cache_, rnn_state_, params_).cast(); } else { ret = ft_.verify_func_(embeddings_dref_or_nd, kv_cache_, params_).cast(); } Tensor logits; if (ft_.use_disco) { ret = ft_.tuple_getitem_func_(ret, 0).cast(); if (num_stages_ > 1) { // Send the result from the last worker group to worker 0. Shape shape{1, total_length, vocab_size_}; DLDataType dtype = DLDataType{kDLFloat, 32, 1}; ret = ft_.last_group_send_to_worker_0_(ret, disco_logits_arr_, shape, dtype) .cast(); } logits = ret.as_or_throw()->DebugGetFromRemote(0).cast(); } else { // The function returns a (logits, kv_cache) tuple; extract element 0 as a // generic Any before casting to Tensor. Casting the whole tuple to // Array now fails strict element-type checking on the kv_cache. logits = ret.as_or_throw>()[0].cast(); } if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } ft_.kv_cache_end_forward_func_(kv_cache_); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_end_forward_func_(rnn_state_); } // logits: (1, total_length, v) TVM_FFI_ICHECK_EQ(logits->ndim, 3); TVM_FFI_ICHECK_EQ(logits->shape[0], 1); TVM_FFI_ICHECK_EQ(logits->shape[1], total_length); return logits; } ObjectRef BatchVerifyToLastHidden(const ObjectRef& embeddings, const std::vector& seq_ids, const std::vector& lengths, const std::vector& token_tree_parent_ptr) final { TVM_FFI_ICHECK(!seq_ids.empty()); TVM_FFI_ICHECK_EQ(seq_ids.size(), lengths.size()); int num_sequences = seq_ids.size(); int total_length = 0; for (int i = 0; i < num_sequences; ++i) { total_length += lengths[i]; } TVM_FFI_ICHECK_EQ(total_length, token_tree_parent_ptr.size()); NVTXScopedRange nvtx_scope("BatchVerifyToLastHidden num_tokens=" + std::to_string(total_length)); TVM_FFI_ICHECK(ft_.verify_to_last_hidden_func_.defined()) << "`batch_verify_to_last_hidden_states` function is not found in the model."; TVM_FFI_ICHECK(ft_.kv_cache_begin_forward_func_.defined()); TVM_FFI_ICHECK(ft_.kv_cache_end_forward_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; ObjectRef embeddings_dref_or_nd; if (!embeddings->IsInstance()) { // embeddings: (1, n, h) Tensor embeddings_nd = embeddings.as_or_throw(); TVM_FFI_ICHECK_NE(hidden_size_, -1); TVM_FFI_ICHECK_EQ(embeddings_nd->ndim, 2); TVM_FFI_ICHECK_GE(embeddings_nd->shape[0], total_length); TVM_FFI_ICHECK_EQ(embeddings_nd->shape[1], hidden_size_); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_type, device_.device_type); TVM_FFI_ICHECK_EQ(embeddings_nd->device.device_id, device_.device_id); embeddings_dref_or_nd = embeddings_nd.CreateView({1, total_length, hidden_size_}, embeddings_nd->dtype); } else { Shape embedding_shape{1, total_length, hidden_size_}; embeddings_dref_or_nd = ft_.nd_view_func_(embeddings, embedding_shape).cast(); } // Begin forward with the sequence ids and new lengths. Shape seq_ids_tuple(seq_ids); Shape lengths_tuple(lengths.begin(), lengths.end()); Shape token_tree_parent_ptr_tuple(token_tree_parent_ptr); ft_.kv_cache_begin_forward_func_(kv_cache_, seq_ids_tuple, lengths_tuple, token_tree_parent_ptr_tuple); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_begin_forward_func_(rnn_state_, seq_ids_tuple, lengths_tuple, token_tree_parent_ptr_tuple); } // args: embeddings, logit_pos, kv_cache, params ObjectRef result; if (kind == KVStateKind::kHybrid) { result = ft_.verify_to_last_hidden_func_(embeddings_dref_or_nd, kv_cache_, rnn_state_, params_) .cast(); } else { result = ft_.verify_to_last_hidden_func_(embeddings_dref_or_nd, kv_cache_, params_) .cast(); } ft_.kv_cache_end_forward_func_(kv_cache_); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_end_forward_func_(rnn_state_); } ObjectRef hidden_states = ft_.tuple_getitem_func_(result, 0).cast(); if (trace_enabled_) { DeviceAPI::Get(device_)->StreamSync(device_, nullptr); } Shape out_shape{total_length, hidden_size_}; if (!ft_.use_disco) { Tensor hidden_states_nd = hidden_states.as_or_throw(); TVM_FFI_ICHECK_EQ(hidden_states_nd->ndim, 3); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[0], 1); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[1], total_length); TVM_FFI_ICHECK_EQ(hidden_states_nd->shape[2], hidden_size_); return hidden_states_nd.CreateView(out_shape, hidden_states_nd->dtype); } else { return ft_.nd_view_func_(hidden_states, out_shape).cast(); } } /*********************** KV Cache Management ***********************/ void CreateKVCache(int page_size, int max_num_sequence, int64_t max_total_sequence_length, int64_t prefill_chunk_size, int max_history_size, int prefix_cache_max_num_recycling_seqs = 0) final { KVStateKind kv_state_kind = GetMetadata().kv_state_kind; if (kv_state_kind == KVStateKind::kKVCache) { Shape max_num_sequence_tuple{max_num_sequence}; Shape max_total_sequence_length_tuple{max_total_sequence_length}; Shape prefill_chunk_size_tuple{prefill_chunk_size}; Shape page_size_tuple{page_size}; Shape support_sliding_window{sliding_window_size_ != -1}; kv_cache_ = ft_.create_kv_cache_func_(max_num_sequence_tuple, max_total_sequence_length_tuple, prefill_chunk_size_tuple, page_size_tuple, support_sliding_window) .cast(); local_kv_cache_ = ft_.use_disco ? kv_cache_.as_or_throw()->DebugGetFromRemote(0).cast() : kv_cache_; } else if (kv_state_kind == KVStateKind::kRNNState) { // RNN state needs extra slots for prefix cache recycling sequences. Shape max_num_sequence_tuple{max_num_sequence + prefix_cache_max_num_recycling_seqs}; Shape max_history_size_tuple = {std::max(max_history_size, 1)}; kv_cache_ = ft_.create_kv_cache_func_(max_num_sequence_tuple, max_history_size_tuple) .cast(); local_kv_cache_ = ft_.use_disco ? kv_cache_.as_or_throw()->DebugGetFromRemote(0).cast() : kv_cache_; } else if (kv_state_kind == KVStateKind::kHybrid) { // Hybrid: create both PagedKVCache (for attention layers) and RNNState (for GDN layers). Shape max_num_sequence_tuple{max_num_sequence}; Shape max_total_sequence_length_tuple{max_total_sequence_length}; Shape prefill_chunk_size_tuple{prefill_chunk_size}; Shape page_size_tuple{page_size}; Shape support_sliding_window{sliding_window_size_ != -1}; kv_cache_ = ft_.create_kv_cache_func_(max_num_sequence_tuple, max_total_sequence_length_tuple, prefill_chunk_size_tuple, page_size_tuple, support_sliding_window) .cast(); local_kv_cache_ = ft_.use_disco ? kv_cache_.as_or_throw()->DebugGetFromRemote(0).cast() : kv_cache_; // Create RNN state for recurrent layers. // RNN state needs extra slots for prefix cache recycling sequences that // coexist with active sequences (e.g., during ForkSequence). Shape rnn_max_batch{max_num_sequence + prefix_cache_max_num_recycling_seqs}; Shape rnn_max_history{std::max(max_history_size, 1)}; rnn_state_ = ft_.create_rnn_state_func_(rnn_max_batch, rnn_max_history).cast(); local_rnn_state_ = ft_.use_disco ? rnn_state_.as_or_throw()->DebugGetFromRemote(0).cast() : rnn_state_; } else if (kv_state_kind == KVStateKind::kNone) { // Do nothing } else { LOG(FATAL) << "Unknown kv_state_kind: " << static_cast(kv_state_kind); } } void AddNewSequence(int64_t seq_id) final { if (this->kind == KVStateKind::kNone) { return; } ft_.kv_cache_add_sequence_func_(kv_cache_, seq_id); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_add_sequence_func_(rnn_state_, seq_id); } } void ForkSequence(int64_t parent_seq_id, int64_t child_seq_id, int64_t fork_pos) final { if (this->kind == KVStateKind::kNone) { return; } ft_.kv_cache_fork_sequence_func_(kv_cache_, parent_seq_id, child_seq_id, fork_pos); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_fork_sequence_func_(rnn_state_, parent_seq_id, child_seq_id, fork_pos); } prefilled_seq_ids_.insert(child_seq_id); } void RemoveSequence(int64_t seq_id) final { if (this->kind == KVStateKind::kNone) { return; } prefilled_seq_ids_.erase(seq_id); ft_.kv_cache_remove_sequence_func_(kv_cache_, seq_id); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_remove_sequence_func_(rnn_state_, seq_id); } } void PopNFromKVCache(int64_t seq_id, int num_tokens) final { if (this->kind == KVStateKind::kNone) { return; } ft_.kv_cache_popn_func_(kv_cache_, seq_id, num_tokens); if (kind == KVStateKind::kHybrid) { ft_.kv_cache_popn_func_(rnn_state_, seq_id, num_tokens); } } void CommitAcceptedTokenTreeNodesToKVCache( const std::vector& seq_ids, const std::vector& accepted_leaf_indices) final { Shape seq_ids_tuple(seq_ids); Shape accepted_leaf_indices_tuple(accepted_leaf_indices); ft_.kv_cache_commit_accepted_token_tree_nodes_func_(kv_cache_, seq_ids_tuple, accepted_leaf_indices_tuple); } void EnableSlidingWindowForSeq(int64_t seq_id) final { if (this->kind == KVStateKind::kNone) { return; } if (sliding_window_size_ != -1) { ft_.kv_cache_enable_sliding_window_for_seq_(kv_cache_, seq_id, sliding_window_size_, attention_sink_size_); } } Shape DisaggPrepareKVRecv(int64_t seq_id, int length) final { NVTXScopedRange nvtx_scope("DisaggPrepareKVRecv length=" + std::to_string(length)); TVM_FFI_ICHECK(ft_.kv_cache_disagg_prepare_recv_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Run KV receive preparation. ObjectRef ret; ret = ft_.kv_cache_disagg_prepare_recv_func_(kv_cache_, seq_id, length).cast(); Shape compressed_kv_append_metadata; if (ft_.use_disco) { compressed_kv_append_metadata = ret.as_or_throw()->DebugGetFromRemote(0).cast(); } else { compressed_kv_append_metadata = ret.as_or_throw(); } return compressed_kv_append_metadata; } void DisaggMarkKVSend(int64_t seq_id, int begin_pos, Shape compressed_kv_append_metadata, int dst_group_offset) final { NVTXScopedRange nvtx_scope("DisaggMarkKVSend seq_id=" + std::to_string(seq_id) + " begin_pos=" + std::to_string(begin_pos)); TVM_FFI_ICHECK(ft_.kv_cache_disagg_mark_send_func_.defined()); TVM_FFI_ICHECK(kv_cache_.defined()) << "KV cache has not been initialized."; // Run KV send preparation. ft_.kv_cache_disagg_mark_send_func_(kv_cache_, seq_id, begin_pos, compressed_kv_append_metadata, dst_group_offset); } /************** Raw Info Query **************/ ModelMetadata GetMetadata() const final { return ft_.model_metadata_; } int GetNumAvailablePages() const final { if (this->kind == KVStateKind::kRNNState || this->kind == KVStateKind::kNone) { // RNNState does not introduce new page at runtime return std::numeric_limits::max(); } else { // kKVCache and kHybrid both use PagedKVCache for capacity. return ft_.kv_cache_get_num_available_pages_func_(local_kv_cache_).cast(); } } int GetCurrentTotalSequenceLength() const final { if (this->kind == KVStateKind::kRNNState || this->kind == KVStateKind::kNone) { // RNNState does not have a total sequence length limit return 0; } else { return ft_.kv_cache_get_total_sequence_length_func_(local_kv_cache_).cast(); } } /*********************** Utilities ***********************/ void LoadParams() final { this->params_ = ft_.LoadParams(model_, device_); } void SetMaxNumSequence(int max_num_sequence) final { this->max_num_sequence_ = max_num_sequence; this->logit_pos_arr_ = Tensor::Empty({max_num_sequence}, DLDataType{kDLInt, 32, 1}, Device{DLDeviceType::kDLCPU, 0}); } void SetPrefillChunkSize(int prefill_chunk_size) final { this->prefill_chunk_size_ = prefill_chunk_size; Device preferred_host_device = GetPreferredHostDevice(device_); memory::Allocator* allocator = memory::MemoryManager::GetOrCreateAllocator( preferred_host_device, memory::AllocatorType::kNaive); TVM_FFI_ICHECK_NOTNULL(allocator); token_ids_storage_ = memory::Storage( allocator->Alloc(preferred_host_device, {prefill_chunk_size_}, DLDataType{kDLInt, 32, 1}), allocator); if (this->num_stages_ > 1) { // Create a remote Tensor for logits when pipeline parallelism is enabled. disco_logits_arr_ = ft_.Empty({prefill_chunk_size_, vocab_size_}, DLDataType{kDLFloat, 32, 1}, device_, /*worker0_only=*/true); } } LogitProcessor CreateLogitProcessor(int max_num_token, Optional trace_recorder) final { return LogitProcessor(max_num_token, vocab_size_, &this->ft_, device_, std::move(trace_recorder)); } Sampler CreateSampler(int max_num_sample, int num_models, Optional trace_recorder) final { if (Sampler::SupportGPUSampler(device_)) { return Sampler::CreateGPUSampler(max_num_sample, vocab_size_, &this->ft_, device_, std::move(trace_recorder)); } else { return Sampler::CreateCPUSampler(std::move(trace_recorder)); } } int EstimateHostCPURequirement() const final { TVM_FFI_ICHECK_NE(num_shards_, -1) << "The model has not been initialized"; return num_shards_ > 1 ? num_shards_ : 0; } int GetSlidingWindowSize() const final { return sliding_window_size_; } int GetAttentionSinkSize() const final { return attention_sink_size_; } ObjectRef AllocEmbeddingTensor() final { if (!ft_.alloc_embedding_tensor_func_.defined()) { return ObjectRef{nullptr}; } // Allocate the embedding tensor. ObjectRef embedding = ft_.alloc_embedding_tensor_func_().cast(); // Get the shape of the embedding tensor for hidden size. Shape embedding_shape; if (ft_.use_disco) { TVM_FFI_ICHECK(embedding->IsInstance()); ObjectRef shape_ref = ft_.nd_get_shape_func_(embedding).cast(); embedding_shape = shape_ref.as_or_throw()->DebugGetFromRemote(0).cast(); } else { Tensor embedding_nd = embedding.as_or_throw(); embedding_shape = embedding_nd.Shape(); } TVM_FFI_ICHECK_NE(prefill_chunk_size_, -1); TVM_FFI_ICHECK_EQ(embedding_shape.size(), 2); TVM_FFI_ICHECK_GE(embedding_shape[0], prefill_chunk_size_); this->hidden_size_ = embedding_shape[1]; return embedding; } ObjectRef AllocHiddenStatesTensor() final { if (!ft_.alloc_embedding_tensor_func_.defined()) { return ObjectRef{nullptr}; } // Allocate the hidden_states tensor. // Use the same function as embeddings. ObjectRef hidden_states = ft_.alloc_embedding_tensor_func_().cast(); Tensor hidden_states_nd{nullptr}; // Get the shape of the hidden_states tensor for hidden size. if (ft_.use_disco) { TVM_FFI_ICHECK(hidden_states->IsInstance()); hidden_states_nd = hidden_states.as_or_throw()->DebugGetFromRemote(0).cast(); } else { hidden_states_nd = hidden_states.as_or_throw(); } Shape hidden_states_shape = hidden_states_nd.Shape(); TVM_FFI_ICHECK_NE(prefill_chunk_size_, -1); TVM_FFI_ICHECK_EQ(hidden_states_shape.size(), 2); TVM_FFI_ICHECK_GE(hidden_states_shape[0], prefill_chunk_size_); this->hidden_size_ = hidden_states_shape[1]; this->hidden_states_dtype_ = hidden_states_nd->dtype; return hidden_states; } void Reset() final { // Reset the KV cache. if (kv_cache_.defined()) { ft_.reset_kv_cache_func_(kv_cache_); } if (rnn_state_.defined()) { ft_.reset_kv_cache_func_(rnn_state_); } } /********************** Utilities for speculative decoding **********************/ DraftTokenWorkspaceManager CreateDraftTokenWorkspaceManager(int max_num_tokens) { return DraftTokenWorkspaceManager(max_num_tokens, vocab_size_, hidden_size_, hidden_states_dtype_, device_, ft_); } ObjectRef GatherHiddenStates(const ObjectRef& input, const std::vector& indices, ObjectRef* dst) final { ObjectRef dst_view{nullptr}; Shape out_shape{static_cast(indices.size()), hidden_size_}; if ((*dst)->IsInstance()) { dst_view = ft_.nd_view_func_(*dst, out_shape).cast(); } else { Tensor dst_nd = (*dst).as_or_throw(); dst_view = dst_nd.CreateView(out_shape, hidden_states_dtype_); } Tensor indices_nd = logit_pos_arr_.CreateView({static_cast(indices.size())}, DLDataType{kDLInt, 32, 1}); indices_nd.CopyFromBytes(indices.data(), indices.size() * sizeof(int)); TVM_FFI_ICHECK_NE(max_num_sequence_, -1); ObjectRef indices_device = ft_.CopyToWorker0(indices_nd, "logit_pos", {max_num_sequence_}); ft_.gather_hidden_states_func_(input, indices_device, dst_view); return dst_view; } void ScatterHiddenStates(const ObjectRef& input, const std::vector& indices, ObjectRef* dst) final { Tensor indices_nd = logit_pos_arr_.CreateView({static_cast(indices.size())}, DLDataType{kDLInt, 32, 1}); indices_nd.CopyFromBytes(indices.data(), indices.size() * sizeof(int)); TVM_FFI_ICHECK_NE(max_num_sequence_, -1); ObjectRef indices_device = ft_.CopyToWorker0(indices_nd, "logit_pos", {max_num_sequence_}); ft_.scatter_hidden_states_func_(input, indices_device, *dst); } Tensor GatherDraftProbs(const Tensor& input, const std::vector& indices, Tensor* dst) final { Tensor dst_view = dst->CreateView({static_cast(indices.size()), vocab_size_}, DLDataType{kDLFloat, 32, 1}); Tensor indices_nd = logit_pos_arr_.CreateView({static_cast(indices.size())}, DLDataType{kDLInt, 32, 1}); indices_nd.CopyFromBytes(indices.data(), indices.size() * sizeof(int)); TVM_FFI_ICHECK_NE(max_num_sequence_, -1); ObjectRef indices_device = ft_.CopyToWorker0(indices_nd, "logit_pos_local", {max_num_sequence_}, /*local_only=*/true); ft_.gather_probs_func_(input, indices_device, dst_view); return dst_view; } void ScatterDraftProbs(const Tensor& input, const std::vector& indices, Tensor* dst) final { Tensor indices_nd = logit_pos_arr_.CreateView({static_cast(indices.size())}, DLDataType{kDLInt, 32, 1}); indices_nd.CopyFromBytes(indices.data(), indices.size() * sizeof(int)); TVM_FFI_ICHECK_NE(max_num_sequence_, -1); ObjectRef indices_device = ft_.CopyToWorker0(indices_nd, "logit_pos_local", {max_num_sequence_}, /*local_only=*/true); ft_.scatter_probs_func_(input, indices_device, *dst); } Array GetMedusaLogits(const ObjectRef& hidden_states) { ObjectRef result = ft_.get_logits_func_(hidden_states).cast(); Array logits{nullptr}; if (ft_.use_disco) { logits = result.as_or_throw()->DebugGetFromRemote(0).cast>(); } else { logits = result.as_or_throw>(); } return logits; } /************** Debug/Profile **************/ void DebugCallFuncOnAllAllWorker(const String& func_name, Optional func_args) final { ft_.DebugCallFuncOnAllAllWorker(func_name, func_args); } private: /*! \brief Load model configuration from JSON. */ void LoadModelConfigJSON(const tvm::ffi::json::Object& config) { this->sliding_window_size_ = json::LookupOrDefault(config, "sliding_window_size", this->sliding_window_size_); TVM_FFI_ICHECK(sliding_window_size_ == -1 || sliding_window_size_ > 0) << "Sliding window should be either -1 (which means disabled) of positive"; this->attention_sink_size_ = json::LookupOrDefault(config, "attention_sink_size", this->attention_sink_size_); this->attention_sink_size_ = std::max(this->attention_sink_size_, 0); this->vocab_size_ = json::Lookup(config, "vocab_size"); this->model_type_ = json::Lookup(config, "model_type"); } //---------------------------- // Model configurations //---------------------------- std::string model_; int sliding_window_size_ = -1; int attention_sink_size_ = 0; int num_shards_ = -1; int num_stages_ = -1; int max_num_sequence_ = -1; int prefill_chunk_size_ = -1; int hidden_size_ = -1; DLDataType hidden_states_dtype_; int vocab_size_ = -1; int image_embed_size_ = -1; int seqlen_padding_factor_ = 1; std::string model_type_; //---------------------------- // TVM related states //---------------------------- // Packed function table FunctionTable ft_; // Paged KV cache. // - We use `kv_cache_` for general KV cache operations. // When tensor parallelism is enabled, `kv_cache_` is a DRef object. // - For efficient KV cache raw info query, we use `local_kv_cache` // as a local **reference** of `kv_cache_`. It is a pure mirror of `kv_cache_` // except that it is always a local object. ObjectRef kv_cache_{nullptr}; ObjectRef local_kv_cache_{nullptr}; // RNN state for hybrid models (GatedDeltaNet recurrent layers). ObjectRef rnn_state_{nullptr}; ObjectRef local_rnn_state_{nullptr}; // Runtime device Device device_; // Model parameters ObjectRef params_; // Shared Tensor memory::Storage token_ids_storage_{nullptr}; Tensor logit_pos_arr_{nullptr}; ObjectRef disco_logits_arr_{nullptr}; // A boolean indicating if tracing is enabled. bool trace_enabled_; // An enum indicating whether it's RNN-based. KVStateKind kind; // A set of sequence IDs that have been prefilled. std::unordered_set prefilled_seq_ids_; }; TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; refl::GlobalDef().def( "mlc.copy_embedding_to_offset", [](Tensor embedding, Tensor dst, int offset) { // embedding: (m, hidden_size) // dst: (prefill_chunk_size, hidden_size) TVM_FFI_ICHECK_EQ(embedding->ndim, 2); TVM_FFI_ICHECK_EQ(dst->ndim, 2); TVM_FFI_ICHECK_LE(embedding->shape[0] + offset, dst->shape[0]); TVM_FFI_ICHECK_EQ(embedding->shape[1], dst->shape[1]); const DLTensor& copy_src = *(embedding.operator->()); const DLTensor* p_copy_dst = dst.operator->(); DLTensor copy_dst = *p_copy_dst; copy_dst.shape = embedding->shape; copy_dst.byte_offset = offset * embedding->shape[1] * ((embedding->dtype.bits * embedding->dtype.lanes + 7) / 8); Tensor::CopyFromTo(©_src, ©_dst); }); } } // namespace serve } // namespace llm } // namespace mlc