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

166 lines
6.7 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 <cstdint>
#include <cstring>
#include "tensorflow/lite/core/c/common.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 kKeyTensor = 0;
static const int kValueTensor = 1;
static const int kPositionTensor = 2;
static const int kKeySliceTensor = 3;
static const int kValueSliceTensor = 4;
static const int kRequiredNumDimensions = 4;
TfLiteStatus ExternalKVCachePrepare(TfLiteContext* context, TfLiteNode* node) {
// k_cache, v_cache, position, k_slice, v_slice
TF_LITE_ENSURE_EQ(context, NumInputs(node), 5);
// updated: k_cache, v_cache
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 2);
const TfLiteTensor* k_cache;
const TfLiteTensor* v_cache;
const TfLiteTensor* position;
const TfLiteTensor* k_slice;
const TfLiteTensor* v_slice;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kKeyTensor, &k_cache));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kValueTensor, &v_cache));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kPositionTensor, &position));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kKeySliceTensor, &k_slice));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kValueSliceTensor, &v_slice));
TfLiteTensor* updated_k_cache;
TfLiteTensor* updated_v_cache;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kKeyTensor, &updated_k_cache));
TF_LITE_ENSURE_OK(
context, GetOutputSafe(context, node, kValueTensor, &updated_v_cache));
TF_LITE_ENSURE_EQ(context, k_cache->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, v_cache->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, position->type, kTfLiteInt32);
TF_LITE_ENSURE_EQ(context, k_slice->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, v_slice->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, updated_k_cache->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, updated_v_cache->type, kTfLiteFloat32);
TF_LITE_ENSURE(context, HaveSameShapes(k_cache, v_cache));
TF_LITE_ENSURE(context, HaveSameShapes(k_slice, v_slice));
TF_LITE_ENSURE(context, HaveSameShapes(updated_k_cache, updated_v_cache));
TF_LITE_ENSURE(context, HaveSameShapes(k_cache, updated_k_cache));
// Support only (B, S, N, H) for now.
TF_LITE_ENSURE(context, NumDimensions(k_slice) == kRequiredNumDimensions);
TF_LITE_ENSURE(context, NumDimensions(k_cache) == kRequiredNumDimensions);
// Ensure Positions correspond to KV sequence length.
TF_LITE_ENSURE(context, NumDimensions(position) == 1);
TF_LITE_ENSURE(context, GetTensorShape(position).Dims(0) ==
GetTensorShape(k_slice).Dims(1));
// Enforce Batch == 1 for now.
TF_LITE_ENSURE(context, GetTensorShape(k_slice).Dims(0) == 1);
return kTfLiteOk;
}
TfLiteStatus ExternalKVCacheEval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* k_cache;
const TfLiteTensor* v_cache;
const TfLiteTensor* position;
const TfLiteTensor* k_slice;
const TfLiteTensor* v_slice;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kKeyTensor, &k_cache));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kValueTensor, &v_cache));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kPositionTensor, &position));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kKeySliceTensor, &k_slice));
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kValueSliceTensor, &v_slice));
TfLiteTensor* updated_k_cache;
TfLiteTensor* updated_v_cache;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kKeyTensor, &updated_k_cache));
TF_LITE_ENSURE_OK(
context, GetOutputSafe(context, node, kValueTensor, &updated_v_cache));
// Note: For the best performance, the following memcpys should be avoided.
// The way to avoid that is to take advantage of CustomAllocation and use
// the same buffer for both input and output.
if (k_cache->data.raw != updated_k_cache->data.raw) {
memcpy(updated_k_cache->data.data, k_cache->data.data, k_cache->bytes);
}
if (v_cache->data.raw != updated_v_cache->data.raw) {
memcpy(updated_v_cache->data.data, v_cache->data.data, v_cache->bytes);
}
// Copy the new slice to the updated cache.
const int32_t elements_in_one_entry =
GetTensorShape(k_cache).Dims(2) * GetTensorShape(k_cache).Dims(3);
const int32_t cache_size = GetTensorShape(k_cache).Dims(1);
int32_t last_update_position = -1;
for (int i = 0; i < position->bytes / sizeof(int32_t); ++i) {
const int32_t update_position = position->data.i32[i];
// We are making the assumption that the positions are in increasing order
// and a decrease or equal value shows exhaustion of update slices.
// This assumption can be relaxed once we switch to dynamic shapes.
if (update_position < last_update_position) {
break;
}
last_update_position = update_position;
TF_LITE_ENSURE(context, update_position < cache_size);
const int32_t cache_offset = update_position * elements_in_one_entry;
const int32_t update_offset = i * elements_in_one_entry;
TF_LITE_ENSURE(context,
(cache_offset + elements_in_one_entry) * sizeof(float) <=
k_cache->bytes);
memcpy(updated_k_cache->data.f + cache_offset,
k_slice->data.f + update_offset,
elements_in_one_entry * sizeof(float));
memcpy(updated_v_cache->data.f + cache_offset,
v_slice->data.f + update_offset,
elements_in_one_entry * sizeof(float));
}
return kTfLiteOk;
}
} // namespace llm
TfLiteRegistration* Register_EXTERNAL_KV_CACHE() {
static TfLiteRegistration r = {/*init=*/nullptr, /*free=*/nullptr,
llm::ExternalKVCachePrepare,
llm::ExternalKVCacheEval};
return &r;
}
} // namespace custom
} // namespace ops
} // namespace tflite