Files
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

357 lines
14 KiB
C++

/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "flatbuffers/flexbuffers.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/experimental/resource/cache_buffer.h"
#include "tensorflow/lite/experimental/resource/resource_base.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace ops {
namespace custom {
namespace llm {
static const int kPositionTensor = 0;
static const int kKeyTensor = 1;
static const int kValueTensor = 2;
static const int kFullKeyTensor = 0;
static const int kFullValueTensor = 1;
static const int kRequiredNumDimensions = 4;
static const int kDefaultMaxNumCacheEntries = 2048;
static const int kDefaultNumTransformerLayers = 32;
static const int kDefaultTransformerLayerId = 0;
static const int KVCACHE_KEY_RESOURCE = 42;
static const int KVCACHE_VALUE_RESOURCE = 43;
struct OpData {
int num_layers;
int layer_index;
int max_num_entries;
int first_slot_index;
// Pointers to the key and value cache buffers that this Op doesn't own
// (and therefore does not free on destruction of this Op).
resource::CacheBuffer* key_cache_buffer;
resource::CacheBuffer* value_cache_buffer;
bool is_initialized;
uint8_t* key_cache_ptr;
uint8_t* value_cache_ptr;
};
void* KVCacheInit(TfLiteContext* context, const char* buffer, size_t length) {
OpData* op_data = new OpData();
// TODO(b/333891673) Reset this value via ClearCaches in
// InternalBackendContext.
op_data->max_num_entries = -1;
op_data->num_layers = -1;
op_data->layer_index = -1;
op_data->first_slot_index = -1;
op_data->key_cache_buffer = nullptr;
op_data->value_cache_buffer = nullptr;
op_data->is_initialized = false;
op_data->key_cache_ptr = nullptr;
op_data->value_cache_ptr = nullptr;
return op_data;
}
TfLiteStatus KVCachePrepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 3);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 2);
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
if (!op_data->is_initialized) {
const uint8_t* buffer =
reinterpret_cast<const uint8_t*>(node->custom_initial_data);
const size_t length = node->custom_initial_data_size;
auto flexbuffer_map = flexbuffers::GetRoot(buffer, length).AsMap();
int32_t max_num_entries = flexbuffer_map["kv_cache_max"].AsInt32();
int32_t num_layers = flexbuffer_map["num_layers"].AsInt32();
int32_t layer_index = flexbuffer_map["layer_index"].AsInt32();
op_data->max_num_entries =
max_num_entries > 0 ? max_num_entries : kDefaultMaxNumCacheEntries;
op_data->num_layers =
num_layers > 0 ? num_layers : kDefaultNumTransformerLayers;
op_data->layer_index =
layer_index > 0 ? layer_index : kDefaultTransformerLayerId;
op_data->first_slot_index = 0;
op_data->is_initialized = true;
}
// Prepare the inputs.
const TfLiteTensor* position;
const TfLiteTensor* key;
const TfLiteTensor* value;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kPositionTensor, &position));
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kKeyTensor, &key));
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kValueTensor, &value));
TF_LITE_ENSURE_EQ(context, position->type, kTfLiteInt64);
TF_LITE_ENSURE_EQ(context, key->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, value->type, kTfLiteFloat32);
// Ensure Positions correspond to KV sequence length.
TF_LITE_ENSURE(context, NumDimensions(position) == 1);
TF_LITE_ENSURE(
context, GetTensorShape(position).Dims(0) == GetTensorShape(key).Dims(1));
// Support only (B, S, N, H) for now.
TF_LITE_ENSURE(context, NumDimensions(key) == kRequiredNumDimensions);
// Enforce Batch == 1 for now.
TF_LITE_ENSURE(context, GetTensorShape(key).Dims(0) == 1);
TF_LITE_ENSURE(context, HaveSameShapes(key, value));
// Create the key and value caches. Currently statically sized.
TfLiteTensor* kfull;
TfLiteTensor* vfull;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kFullKeyTensor, &kfull));
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kFullValueTensor, &vfull));
// Custom data pointer to the resource cache buffer.
kfull->allocation_type = kTfLiteCustom;
vfull->allocation_type = kTfLiteCustom;
kfull->type = kTfLiteFloat32;
vfull->type = kTfLiteFloat32;
TfLiteIntArray* input_dims = key->dims;
TfLiteIntArray* kcache_dims = TfLiteIntArrayCopy(input_dims);
TfLiteIntArray* vcache_dims = TfLiteIntArrayCopy(input_dims);
kcache_dims->data[1] = op_data->max_num_entries;
vcache_dims->data[1] = op_data->max_num_entries;
TfLiteIntArray* kcache_buffer_dims = TfLiteIntArrayCreate(5);
// Batch
kcache_buffer_dims->data[0] = input_dims->data[0];
// Number of layers
kcache_buffer_dims->data[1] = op_data->num_layers;
// Sequence Length
kcache_buffer_dims->data[2] = op_data->max_num_entries;
// Num heads
kcache_buffer_dims->data[3] = input_dims->data[2];
// Head dim
kcache_buffer_dims->data[4] = input_dims->data[3];
TfLiteIntArray* vcache_buffer_dims = TfLiteIntArrayCopy(kcache_buffer_dims);
// Get the pointer to the tensor for our buffer storage.
Subgraph* subgraph = reinterpret_cast<Subgraph*>(context->impl_);
auto& resources = subgraph->resources();
if (resources.count(KVCACHE_KEY_RESOURCE) == 0) {
auto* cbuffer = new resource::CacheBuffer();
cbuffer->Initialize(*kcache_buffer_dims);
resources.emplace(KVCACHE_KEY_RESOURCE, cbuffer);
op_data->key_cache_buffer = cbuffer;
} else {
resource::ResourceBase* resourcePtr =
resources.at(KVCACHE_KEY_RESOURCE).get();
resource::CacheBuffer* cbuffer = (resource::CacheBuffer*)(resourcePtr);
op_data->key_cache_buffer = cbuffer;
}
if (resources.count(KVCACHE_VALUE_RESOURCE) == 0) {
auto* cbuffer = new resource::CacheBuffer();
cbuffer->Initialize(*vcache_buffer_dims);
resources.emplace(KVCACHE_VALUE_RESOURCE, cbuffer);
op_data->value_cache_buffer = cbuffer;
} else {
resource::ResourceBase* resourcePtr =
resources.at(KVCACHE_VALUE_RESOURCE).get();
resource::CacheBuffer* cbuffer = (resource::CacheBuffer*)(resourcePtr);
op_data->value_cache_buffer = cbuffer;
}
// Get the pointers to the individual caches for a layer.
RuntimeShape shape(GetTensorShape(key));
const int elements_in_one_entry = shape.Dims(2) * shape.Dims(3);
const int elements_in_one_block =
op_data->max_num_entries * elements_in_one_entry;
uint8_t* k_ptr =
reinterpret_cast<uint8_t*>(op_data->key_cache_buffer->GetBuffer());
uint8_t* v_ptr =
reinterpret_cast<uint8_t*>(op_data->value_cache_buffer->GetBuffer());
k_ptr = k_ptr + sizeof(float) * op_data->layer_index * elements_in_one_block;
v_ptr = v_ptr + sizeof(float) * op_data->layer_index * elements_in_one_block;
size_t kcache_dims_flatsize = kcache_dims->data[0] * kcache_dims->data[1] *
kcache_dims->data[2] * kcache_dims->data[3];
size_t vcache_dims_flatsize = vcache_dims->data[0] * vcache_dims->data[1] *
vcache_dims->data[2] * vcache_dims->data[3];
RuntimeShape kfull_shape(GetTensorShape(kfull));
RuntimeShape vfull_shape(GetTensorShape(vfull));
// Some testing utils don't fully set the output tensor shape
if (kfull_shape.FlatSize() > 1 && vfull_shape.FlatSize() > 1) {
TF_LITE_ENSURE_EQ(context, kfull_shape.FlatSize(), kcache_dims_flatsize);
TF_LITE_ENSURE_EQ(context, vfull_shape.FlatSize(), vcache_dims_flatsize);
}
TF_LITE_ENSURE_EQ(context, elements_in_one_block, kcache_dims_flatsize);
TF_LITE_ENSURE_EQ(context, elements_in_one_block, vcache_dims_flatsize);
kfull->data.data = k_ptr;
vfull->data.data = v_ptr;
op_data->key_cache_ptr = k_ptr;
op_data->value_cache_ptr = v_ptr;
TF_LITE_ENSURE_OK(context,
context->ResizeTensor(context, kfull, kcache_dims));
TF_LITE_ENSURE_OK(context,
context->ResizeTensor(context, vfull, vcache_dims));
TfLiteIntArrayFree(kcache_buffer_dims);
TfLiteIntArrayFree(vcache_buffer_dims);
return kTfLiteOk;
}
void KVCacheFree(TfLiteContext* context, void* buffer) {
delete static_cast<OpData*>(buffer);
}
TfLiteStatus KVCacheEval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* position;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kPositionTensor, &position));
const TfLiteTensor* key;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kKeyTensor, &key));
const TfLiteTensor* value;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kValueTensor, &value));
// Prepare the outputs.
TfLiteTensor* kfull;
TfLiteTensor* vfull;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kFullKeyTensor, &kfull));
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kFullValueTensor, &vfull));
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
float* key_cache_ptr = op_data->key_cache_buffer->GetBuffer();
float* value_cache_ptr = op_data->value_cache_buffer->GetBuffer();
const int layer_index = op_data->layer_index;
const int64_t max_num_entries = op_data->max_num_entries;
int current_num_entries =
op_data->key_cache_buffer->GetNumEntries(layer_index);
// Compute some constants for various pieces of the cache.
RuntimeShape shape(GetTensorShape(key));
const int64_t num_slots_needed = shape.Dims(1);
const int elements_in_one_entry = shape.Dims(2) * shape.Dims(3);
const int elements_in_one_block =
op_data->max_num_entries * elements_in_one_entry;
const int64_t num_bytes_per_tensor = sizeof(float) * elements_in_one_entry;
// Get the pointers to the individual caches for a layer.
uint8_t* k_ptr = reinterpret_cast<uint8_t*>(key_cache_ptr);
uint8_t* v_ptr = reinterpret_cast<uint8_t*>(value_cache_ptr);
k_ptr = k_ptr + sizeof(float) * op_data->layer_index * elements_in_one_block;
v_ptr = v_ptr + sizeof(float) * op_data->layer_index * elements_in_one_block;
// 0. Ensure output ptr is pointing to the cache data
TF_LITE_ENSURE(context, k_ptr == op_data->key_cache_ptr);
TF_LITE_ENSURE(context, v_ptr == op_data->value_cache_ptr);
TF_LITE_ENSURE(context, k_ptr == kfull->data.data);
TF_LITE_ENSURE(context, v_ptr == vfull->data.data);
// 1. Determine which slots the inputs take up, and which slots are in the
// existing span of the cache.
// Compute the span of the inputs.
const int64_t input_first_idx = position->data.i64[0];
const int64_t input_last_idx = input_first_idx + num_slots_needed - 1;
// Compute the span of the cache.
const int64_t cache_first_slot_idx = op_data->first_slot_index;
const int64_t cache_last_slot_idx =
cache_first_slot_idx + op_data->max_num_entries - 1;
// Compute if a shift is needed.
const int slots_to_shift = std::min(
std::max(static_cast<int64_t>(0), input_last_idx - cache_last_slot_idx),
max_num_entries);
// These values determine how we will write to the output tensor:
// first_slot := the first cache entry that we will write to in the output
int64_t first_slot = input_first_idx - op_data->first_slot_index;
if (first_slot < 0) {
TF_LITE_KERNEL_LOG(
context,
"Can not specify a position before this cache's first slot index of %d",
op_data->first_slot_index);
return kTfLiteError;
}
// byte_offset_for_output := the byte offset for the first slot.
int64_t byte_offset_for_output = first_slot * num_bytes_per_tensor;
// num_slots_for_output := the number of slots we write in the output
int64_t num_slots_for_output = num_slots_needed;
// 3. If we need more slots, make room in the cache by writing over oldest
// entries.
if (slots_to_shift > 0 && slots_to_shift < max_num_entries) {
// If we are shifting the cache, we need to start writing from the
// beginning.
byte_offset_for_output = 0;
// And we need to write the entire cache.
num_slots_for_output = max_num_entries;
const int bytes_offset =
sizeof(float) * elements_in_one_entry * slots_to_shift;
const int size_bytes_to_shift = sizeof(float) * elements_in_one_entry *
(max_num_entries - slots_to_shift);
// TODO(b/333893996): This is O(cache_size) data motion. Consider optimizing
// with a circular buffer or similar.
memmove(k_ptr, k_ptr + bytes_offset, size_bytes_to_shift);
memmove(v_ptr, v_ptr + bytes_offset, size_bytes_to_shift);
}
// Update the first slot this cache now covers.
op_data->first_slot_index = op_data->first_slot_index + slots_to_shift;
// Recompute the first slot in case any shifting occurred.
first_slot = input_first_idx - op_data->first_slot_index;
const int64_t bytes_offset_for_cache = first_slot * num_bytes_per_tensor;
// 4. Put the key and value in their respective caches.
memcpy(k_ptr + bytes_offset_for_cache, key->data.data, key->bytes);
memcpy(v_ptr + bytes_offset_for_cache, value->data.data, value->bytes);
// Update counts.
current_num_entries =
std::min(first_slot + num_slots_needed, max_num_entries);
op_data->key_cache_buffer->SetNumEntries(layer_index, current_num_entries);
op_data->value_cache_buffer->SetNumEntries(layer_index, current_num_entries);
return kTfLiteOk;
}
} // namespace llm
TfLiteRegistration* Register_KV_CACHE() {
static TfLiteRegistration r = {llm::KVCacheInit, llm::KVCacheFree,
llm::KVCachePrepare, llm::KVCacheEval};
return &r;
}
} // namespace custom
} // namespace ops
} // namespace tflite