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

675 lines
28 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 <math.h>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include "flatbuffers/flexbuffers.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/internal/common.h"
#include "tensorflow/lite/kernels/internal/reference/add.h"
#include "tensorflow/lite/kernels/internal/reference/batch_matmul.h"
#include "tensorflow/lite/kernels/internal/reference/fully_connected.h"
#include "tensorflow/lite/kernels/internal/reference/softmax.h"
#include "tensorflow/lite/kernels/internal/reference/transpose.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace ops {
namespace custom {
namespace llm {
static const int kQueryTensor = 0;
static const int kKeyTensor = 1;
static const int kValueTensor = 2;
static const int kAttentionMaskTensor = 3;
static const int kOutputTensor = 0;
static const int kNumTempTensors = 10;
static const int kTransposeQueryTempTensorIndex = 0;
static const int kTransposeKeyTempTensorIndex = 1;
static const int kMatMul1TempTensorIndex = 2;
static const int kAddTempTensorIndex = 3;
static const int kTransposeValueTempTensorIndex = 4;
static const int kMatMul2TempTensorIndex = 5;
static const int kReshape1TempTensorIndex = 6;
static const int kReshape2TempTensorIndex = 7;
static const int kBroadcastKTempTensorIndex = 8;
static const int kBroadcastVTempTensorIndex = 9;
struct OpData {
float scale;
int scratch_tensor_index;
};
void* SDPAInit(TfLiteContext* context, const char* buffer, size_t length) {
OpData* op_data = new OpData();
op_data->scale = 0.0f;
context->AddTensors(context, kNumTempTensors, &op_data->scratch_tensor_index);
return op_data;
}
TfLiteStatus SDPAPrepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 4);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
const TfLiteTensor* q_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kQueryTensor, &q_tensor));
const TfLiteTensor* k_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kKeyTensor, &k_tensor));
const TfLiteTensor* v_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kValueTensor, &v_tensor));
const TfLiteTensor* mask_tensor;
TF_LITE_ENSURE_OK(
context, GetInputSafe(context, node, kAttentionMaskTensor, &mask_tensor));
TF_LITE_ENSURE_EQ(context, NumDimensions(q_tensor), NumDimensions(k_tensor));
TF_LITE_ENSURE_EQ(context, NumDimensions(k_tensor), NumDimensions(v_tensor));
TF_LITE_ENSURE_EQ(context, NumDimensions(v_tensor),
NumDimensions(mask_tensor));
TF_LITE_ENSURE_EQ(context, NumDimensions(mask_tensor), 4);
// Get custom op params
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();
float scale = flexbuffer_map["scale"].AsFloat();
op_data->scale = scale > 0.0f ? scale : 0.0f;
// If scale is not set, use sqrt(q_tensor->dims->data[3])
if (op_data->scale == 0.0f)
op_data->scale = 1 / sqrt(q_tensor->dims->data[3]);
TfLiteIntArrayFree(node->temporaries);
node->temporaries = TfLiteIntArrayCreate(kNumTempTensors);
bool mqa = k_tensor->dims->data[2] == 1;
// Temp tensor for Transposed Q;
{
node->temporaries->data[kTransposeQueryTempTensorIndex] =
op_data->scratch_tensor_index + kTransposeQueryTempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(context,
GetTemporarySafe(context, node,
/*index=*/kTransposeQueryTempTensorIndex,
&scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
for (int i = 0; i < 4; ++i) {
scratch_buffer_size->data[i] = q_tensor->dims->data[i];
}
// Swap middle two dimensions.
scratch_buffer_size->data[1] = q_tensor->dims->data[2];
scratch_buffer_size->data[2] = q_tensor->dims->data[1];
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Transposed K;
{
node->temporaries->data[kTransposeKeyTempTensorIndex] =
op_data->scratch_tensor_index + kTransposeKeyTempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(context,
GetTemporarySafe(context, node,
/*index=*/kTransposeKeyTempTensorIndex,
&scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
for (int i = 0; i < 4; ++i) {
scratch_buffer_size->data[i] = k_tensor->dims->data[i];
}
// Swap to middle two dimensions.
scratch_buffer_size->data[1] = k_tensor->dims->data[2];
scratch_buffer_size->data[2] = k_tensor->dims->data[1];
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
TfLiteIntArray* add_broadcast_shape = nullptr;
// Temp tensor for Matmul1 output;
{
node->temporaries->data[kMatMul1TempTensorIndex] =
op_data->scratch_tensor_index + kMatMul1TempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node,
/*index=*/kMatMul1TempTensorIndex, &scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
// mha/gqa: [permute_q[0], permute_q[1], permute_q[2], permute_k[2]]
int matmul_out_shape[4] = {q_tensor->dims->data[0], q_tensor->dims->data[2],
q_tensor->dims->data[1],
k_tensor->dims->data[1]};
for (int i = 0; i < 4; ++i) {
scratch_buffer_size->data[i] = matmul_out_shape[i];
}
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
// get dims from attention_mask, matmul1_out for add broadcast
CalculateShapeForBroadcast(context, mask_tensor, scratch_buffer,
&add_broadcast_shape);
}
// Temp tensor for add output;
int add_out_shape[4];
{
node->temporaries->data[kAddTempTensorIndex] =
op_data->scratch_tensor_index + kAddTempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node,
/*index=*/kAddTempTensorIndex,
&scratch_buffer));
TfLiteIntArray* scratch_buffer_size = add_broadcast_shape;
for (int i = 0; i < 4; ++i) {
add_out_shape[i] = scratch_buffer_size->data[i];
}
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Transposed V;
{
node->temporaries->data[kTransposeValueTempTensorIndex] =
op_data->scratch_tensor_index + kTransposeValueTempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(context,
GetTemporarySafe(context, node,
/*index=*/kTransposeValueTempTensorIndex,
&scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
// Swap to {0, 2, 3, 1} dimensions.
scratch_buffer_size->data[0] = v_tensor->dims->data[0];
scratch_buffer_size->data[1] = v_tensor->dims->data[2];
scratch_buffer_size->data[2] = v_tensor->dims->data[3];
scratch_buffer_size->data[3] = v_tensor->dims->data[1];
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Matmul2 output;
{
node->temporaries->data[kMatMul2TempTensorIndex] =
op_data->scratch_tensor_index + kMatMul2TempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node,
/*index=*/kMatMul2TempTensorIndex, &scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
// logits_out_shape = add_out_shape
// mha/gqa: [logits_out[0], logits_out[1], logits_out[2], permute_v[2]]
scratch_buffer_size->data[0] = add_out_shape[0];
scratch_buffer_size->data[1] = add_out_shape[1];
scratch_buffer_size->data[2] = add_out_shape[2];
scratch_buffer_size->data[3] = v_tensor->dims->data[3];
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Reshape K / Transpose Q;
{
node->temporaries->data[kReshape1TempTensorIndex] =
op_data->scratch_tensor_index + kReshape1TempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node,
/*index=*/kReshape1TempTensorIndex, &scratch_buffer));
TfLiteIntArray* scratch_buffer_size;
if (mqa)
scratch_buffer_size = TfLiteIntArrayCreate(2);
else
scratch_buffer_size = TfLiteIntArrayCreate(4);
if (mqa) {
scratch_buffer_size->data[0] = k_tensor->dims->data[1];
scratch_buffer_size->data[1] = k_tensor->dims->data[3];
} else {
scratch_buffer_size->data[0] = q_tensor->dims->data[0];
scratch_buffer_size->data[1] = q_tensor->dims->data[2];
scratch_buffer_size->data[2] = q_tensor->dims->data[3];
scratch_buffer_size->data[3] = q_tensor->dims->data[1];
}
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Reshape V / Add_out (softmax_out);
{
node->temporaries->data[kReshape2TempTensorIndex] =
op_data->scratch_tensor_index + kReshape2TempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node,
/*index=*/kReshape2TempTensorIndex, &scratch_buffer));
TfLiteIntArray* scratch_buffer_size;
if (mqa)
scratch_buffer_size = TfLiteIntArrayCreate(2);
else
scratch_buffer_size = TfLiteIntArrayCreate(4);
if (mqa) {
scratch_buffer_size->data[0] = v_tensor->dims->data[3];
scratch_buffer_size->data[1] = v_tensor->dims->data[1];
} else {
scratch_buffer_size->data[0] = add_out_shape[0];
scratch_buffer_size->data[1] = add_out_shape[1];
scratch_buffer_size->data[2] = add_out_shape[3];
scratch_buffer_size->data[3] = add_out_shape[2];
}
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Broadcast K
{
node->temporaries->data[kBroadcastKTempTensorIndex] =
op_data->scratch_tensor_index + kBroadcastKTempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(context,
GetTemporarySafe(context, node,
/*index=*/kBroadcastKTempTensorIndex,
&scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
scratch_buffer_size->data[0] = k_tensor->dims->data[0];
scratch_buffer_size->data[1] = q_tensor->dims->data[2]; // num_heads
scratch_buffer_size->data[2] = k_tensor->dims->data[1];
scratch_buffer_size->data[3] = k_tensor->dims->data[3];
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
// Temp tensor for Broadcast V
{
node->temporaries->data[kBroadcastVTempTensorIndex] =
op_data->scratch_tensor_index + kBroadcastVTempTensorIndex;
TfLiteTensor* scratch_buffer;
TF_LITE_ENSURE_OK(context,
GetTemporarySafe(context, node,
/*index=*/kBroadcastVTempTensorIndex,
&scratch_buffer));
TfLiteIntArray* scratch_buffer_size = TfLiteIntArrayCreate(4);
scratch_buffer_size->data[0] = v_tensor->dims->data[0];
scratch_buffer_size->data[1] = q_tensor->dims->data[2]; // num_heads
scratch_buffer_size->data[2] = v_tensor->dims->data[3];
scratch_buffer_size->data[3] = v_tensor->dims->data[1];
scratch_buffer->type = kTfLiteFloat32;
scratch_buffer->allocation_type = kTfLiteArenaRw;
TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scratch_buffer,
scratch_buffer_size));
}
return kTfLiteOk;
}
void SDPAFree(TfLiteContext* context, void* buffer) {
delete static_cast<OpData*>(buffer);
}
TfLiteStatus SDPAEval(TfLiteContext* context, TfLiteNode* node) {
/*
Simple implementation of Scaled Dot Product Attention.
Takes query_proj, key_proj, value_proj, mask tensors as inputs, and
outputs the attention result.
Notes:
Scale is computed using 1/sqrt(head_dim),
head_dim = q[-1] = embedding_dim // num_q_heads
Only support for FLOAT32 inputs for now.
Only support static tensors for now (k/v[1] = max sequence length)
*/
const TfLiteTensor* query_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kQueryTensor, &query_tensor));
auto query_shape = GetTensorShape(query_tensor);
auto query_data = GetTensorData<float>(query_tensor);
const TfLiteTensor* key_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kKeyTensor, &key_tensor));
auto key_shape = GetTensorShape(key_tensor);
auto key_data = GetTensorData<float>(key_tensor);
const TfLiteTensor* value_tensor;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kValueTensor, &value_tensor));
auto value_shape = GetTensorShape(value_tensor);
auto value_data = GetTensorData<float>(value_tensor);
const TfLiteTensor* attention_mask_tensor;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kAttentionMaskTensor,
&attention_mask_tensor));
auto attention_mask_shape = GetTensorShape(attention_mask_tensor);
auto attention_mask_data = GetTensorData<float>(attention_mask_tensor);
TfLiteTensor* output_tensor;
TF_LITE_ENSURE_OK(
context, GetOutputSafe(context, node, kOutputTensor, &output_tensor));
auto output_shape = GetTensorShape(output_tensor);
auto output_data = GetTensorData<float>(output_tensor);
// temporaries
TfLiteTensor* transpose_q_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kTransposeQueryTempTensorIndex,
&transpose_q_out_tensor));
auto transpose_q_out_shape = GetTensorShape(transpose_q_out_tensor);
auto transpose_q_out_data = GetTensorData<float>(transpose_q_out_tensor);
TfLiteTensor* transpose_k_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kTransposeKeyTempTensorIndex,
&transpose_k_out_tensor));
auto transpose_k_out_shape = GetTensorShape(transpose_k_out_tensor);
auto transpose_k_out_data = GetTensorData<float>(transpose_k_out_tensor);
TfLiteTensor* matmul1_out_tensor;
TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node,
/*index=*/kMatMul1TempTensorIndex,
&matmul1_out_tensor));
auto matmul1_out_shape = GetTensorShape(matmul1_out_tensor);
auto matmul1_out_data = GetTensorData<float>(matmul1_out_tensor);
TfLiteTensor* add_out_tensor;
TF_LITE_ENSURE_OK(
context, GetTemporarySafe(context, node, /*index=*/kAddTempTensorIndex,
&add_out_tensor));
auto add_out_shape = GetTensorShape(add_out_tensor);
auto add_out_data = GetTensorData<float>(add_out_tensor);
TfLiteTensor* transpose_v_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kTransposeValueTempTensorIndex,
&transpose_v_out_tensor));
auto transpose_v_out_shape = GetTensorShape(transpose_v_out_tensor);
auto transpose_v_out_data = GetTensorData<float>(transpose_v_out_tensor);
TfLiteTensor* matmul2_out_tensor;
TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node,
/*index=*/kMatMul2TempTensorIndex,
&matmul2_out_tensor));
auto matmul2_out_shape = GetTensorShape(matmul2_out_tensor);
auto matmul2_out_data = GetTensorData<float>(matmul2_out_tensor);
TfLiteTensor* reshape_k_or_q_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kReshape1TempTensorIndex,
&reshape_k_or_q_out_tensor));
auto reshape_k_or_q_out_shape = GetTensorShape(reshape_k_or_q_out_tensor);
auto reshape_k_or_q_out_data =
GetTensorData<float>(reshape_k_or_q_out_tensor);
TfLiteTensor* reshape_v_or_add_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kReshape2TempTensorIndex,
&reshape_v_or_add_out_tensor));
auto reshape_v_or_add_out_shape = GetTensorShape(reshape_v_or_add_out_tensor);
auto reshape_v_or_add_out_data =
GetTensorData<float>(reshape_v_or_add_out_tensor);
TfLiteTensor* broadcast_k_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kBroadcastKTempTensorIndex,
&broadcast_k_out_tensor));
auto broadcast_k_out_shape = GetTensorShape(broadcast_k_out_tensor);
auto broadcast_k_out_data = GetTensorData<float>(broadcast_k_out_tensor);
TfLiteTensor* broadcast_v_out_tensor;
TF_LITE_ENSURE_OK(
context,
GetTemporarySafe(context, node, /*index=*/kBroadcastVTempTensorIndex,
&broadcast_v_out_tensor));
auto broadcast_v_out_shape = GetTensorShape(broadcast_v_out_tensor);
auto broadcast_v_out_data = GetTensorData<float>(broadcast_v_out_tensor);
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
bool mqa = key_tensor->dims->data[2] == 1;
bool gqa = !mqa && (key_tensor->dims->data[2] != query_tensor->dims->data[2]);
// scale * q
float scale = op_data->scale;
int flat_size = query_shape.FlatSize();
float output_min = -std::numeric_limits<float>::infinity();
float output_max = std::numeric_limits<float>::infinity();
for (int i = 0; i < flat_size; ++i) {
query_tensor->data.f[i] = ActivationFunctionWithMinMax(
query_tensor->data.f[i] * scale, output_min, output_max);
}
// permute q {0, 2, 1, 3}
tflite::TransposeParams transpose_q_params;
transpose_q_params.perm_count = 4;
transpose_q_params.perm[0] = 0;
transpose_q_params.perm[1] = 2;
transpose_q_params.perm[2] = 1;
transpose_q_params.perm[3] = 3;
reference_ops::Transpose(transpose_q_params, query_shape, query_data,
transpose_q_out_shape, transpose_q_out_data);
// permute k {0, 2, 1, 3}
tflite::TransposeParams transpose_k_params;
transpose_k_params.perm_count = 4;
transpose_k_params.perm[0] = 0;
transpose_k_params.perm[1] = 2;
transpose_k_params.perm[2] = 1;
transpose_k_params.perm[3] = 3;
reference_ops::Transpose(transpose_k_params, key_shape, key_data,
transpose_k_out_shape, transpose_k_out_data);
// broadcast k to match num_heads
// broadcasting similar to torch.repeat_interleave
if (gqa) {
float* transpose_k_ptr = transpose_k_out_data;
float* broadcast_k_ptr = broadcast_k_out_data;
int num_elements =
transpose_k_out_shape.Dims(2) * transpose_k_out_shape.Dims(3);
int num_repeat =
broadcast_k_out_shape.Dims(1) / transpose_k_out_shape.Dims(1);
for (int i = 0; i < transpose_k_out_shape.Dims(0); ++i) {
for (int j = 0; j < transpose_k_out_shape.Dims(1); ++j) {
for (int k = 0; k < num_repeat; ++k) {
memcpy(broadcast_k_ptr, transpose_k_ptr,
num_elements * sizeof(float));
broadcast_k_ptr += num_elements;
}
transpose_k_ptr += num_elements;
}
}
}
// reshape k for MQA, or transpose q for MHA
if (mqa) {
TF_LITE_ENSURE_EQ(context, transpose_k_out_tensor->bytes,
reshape_k_or_q_out_tensor->bytes);
memcpy(reshape_k_or_q_out_tensor->data.data,
transpose_k_out_tensor->data.data, transpose_k_out_tensor->bytes);
} else {
// permute q2 {0, 1, 3, 2}
tflite::TransposeParams transpose_q2_params;
transpose_q2_params.perm_count = 4;
transpose_q2_params.perm[0] = 0;
transpose_q2_params.perm[1] = 1;
transpose_q2_params.perm[2] = 3;
transpose_q2_params.perm[3] = 2;
reference_ops::Transpose(transpose_q2_params, transpose_q_out_shape,
transpose_q_out_data, reshape_k_or_q_out_shape,
reshape_k_or_q_out_data);
}
// mqa FC (q, squeezed_k)
// mha BMM(q, k) transpose_b = true
if (mqa) {
tflite::FullyConnectedParams fc_params;
fc_params.float_activation_min = output_min;
fc_params.float_activation_max = output_max;
reference_ops::FullyConnected(
fc_params, transpose_q_out_shape, transpose_q_out_data,
reshape_k_or_q_out_shape, reshape_k_or_q_out_data, RuntimeShape(),
nullptr, matmul1_out_shape, matmul1_out_data);
} else if (gqa) {
// pass rhs first (this is why we transpose q above)
reference_ops::BatchMatMul(
broadcast_k_out_shape, broadcast_k_out_data, reshape_k_or_q_out_shape,
reshape_k_or_q_out_data, matmul1_out_shape, matmul1_out_data);
} else {
reference_ops::BatchMatMul(
transpose_k_out_shape, transpose_k_out_data, reshape_k_or_q_out_shape,
reshape_k_or_q_out_data, matmul1_out_shape, matmul1_out_data);
}
// add matmul_out + mask
tflite::ArithmeticParams add_params;
SetActivationParams(output_min, output_max, &add_params);
reference_ops::BroadcastAdd6DSlow(
add_params, attention_mask_shape, attention_mask_data, matmul1_out_shape,
matmul1_out_data, add_out_shape, add_out_data);
// softmax, can do in-place
tflite::SoftmaxParams softmax_params;
softmax_params.beta = 1.0f;
reference_ops::Softmax(softmax_params, add_out_shape, add_out_data,
add_out_shape, add_out_data);
// permute v {0, 2, 3, 1}
tflite::TransposeParams transpose_v_params;
transpose_v_params.perm_count = 4;
transpose_v_params.perm[0] = 0;
transpose_v_params.perm[1] = 2;
transpose_v_params.perm[2] = 3;
transpose_v_params.perm[3] = 1;
reference_ops::Transpose(transpose_v_params, value_shape, value_data,
transpose_v_out_shape, transpose_v_out_data);
// broadcast v to match num_heads
// broadcasting similar to torch.repeat_interleave
if (gqa) {
float* transpose_v_ptr = transpose_v_out_data;
float* broadcast_v_ptr = broadcast_v_out_data;
int num_elements =
transpose_v_out_shape.Dims(2) * transpose_v_out_shape.Dims(3);
int num_repeat =
broadcast_v_out_shape.Dims(1) / transpose_v_out_shape.Dims(1);
for (int i = 0; i < transpose_v_out_shape.Dims(0); ++i) {
for (int j = 0; j < transpose_v_out_shape.Dims(1); ++j) {
for (int k = 0; k < num_repeat; ++k) {
memcpy(broadcast_v_ptr, transpose_v_ptr,
num_elements * sizeof(float));
broadcast_v_ptr += num_elements;
}
transpose_v_ptr += num_elements;
}
}
}
// reshape v for MQA, or add_out (softmax_out)
if (mqa) {
TF_LITE_ENSURE_EQ(context, transpose_v_out_tensor->bytes,
reshape_v_or_add_out_tensor->bytes);
memcpy(reshape_v_or_add_out_tensor->data.data,
transpose_v_out_tensor->data.data, transpose_v_out_tensor->bytes);
} else {
// permute softmax_out {0, 1, 3, 2}
tflite::TransposeParams transpose_softmax_out_params;
transpose_softmax_out_params.perm_count = 4;
transpose_softmax_out_params.perm[0] = 0;
transpose_softmax_out_params.perm[1] = 1;
transpose_softmax_out_params.perm[2] = 3;
transpose_softmax_out_params.perm[3] = 2;
reference_ops::Transpose(transpose_softmax_out_params, add_out_shape,
add_out_data, reshape_v_or_add_out_shape,
reshape_v_or_add_out_data);
}
// mqa FC (softmax_out, squeezed_v)
// mha BMM(softmax_out, v) transpose_b = true
if (mqa) {
tflite::FullyConnectedParams fc_params;
fc_params.float_activation_min = output_min;
fc_params.float_activation_max = output_max;
reference_ops::FullyConnected(fc_params, add_out_shape, add_out_data,
reshape_v_or_add_out_shape,
reshape_v_or_add_out_data, RuntimeShape(),
nullptr, matmul2_out_shape, matmul2_out_data);
} else if (gqa) {
// pass rhs first (this is why we transpose add_out above)
reference_ops::BatchMatMul(
broadcast_v_out_shape, broadcast_v_out_data, reshape_v_or_add_out_shape,
reshape_v_or_add_out_data, matmul2_out_shape, matmul2_out_data);
} else {
reference_ops::BatchMatMul(
transpose_v_out_shape, transpose_v_out_data, reshape_v_or_add_out_shape,
reshape_v_or_add_out_data, matmul2_out_shape, matmul2_out_data);
}
// permute out {0, 2, 1, 3}
tflite::TransposeParams transpose_out_params;
transpose_out_params.perm_count = 4;
transpose_out_params.perm[0] = 0;
transpose_out_params.perm[1] = 2;
transpose_out_params.perm[2] = 1;
transpose_out_params.perm[3] = 3;
reference_ops::Transpose(transpose_out_params, matmul2_out_shape,
matmul2_out_data, output_shape, output_data);
return kTfLiteOk;
}
} // namespace llm
TfLiteRegistration* Register_SDPA() {
static TfLiteRegistration r = {llm::SDPAInit, llm::SDPAFree, llm::SDPAPrepare,
llm::SDPAEval};
return &r;
}
} // namespace custom
} // namespace ops
} // namespace tflite