979 lines
47 KiB
C++
979 lines
47 KiB
C++
//
|
|
// CPUKVCacheManager.cpp
|
|
// MNN
|
|
//
|
|
// Created by MNN on 2024/08/05.
|
|
// Copyright © 2018, Alibaba Group Holding Limited
|
|
//
|
|
|
|
#ifdef MNN_SUPPORT_TRANSFORMER_FUSE
|
|
|
|
#include "CPUKVCacheManager.hpp"
|
|
#include "core/Concurrency.h"
|
|
#include "core/TensorUtils.hpp"
|
|
|
|
namespace MNN {
|
|
|
|
static inline int c4Offset(int token, int channel, int seqLen, int pack) {
|
|
return (channel / pack) * seqLen * pack + token * pack + (channel % pack);
|
|
}
|
|
|
|
/*
|
|
** @brief Expand the size of kvcache and copy it from the old tensor in memory to the new tensor in memory
|
|
** Finally reset the pointer to the new tensor
|
|
*/
|
|
void CPUKVCacheManager::expandKVCacheInMem(int oldMaxLength) {
|
|
/*=================================== Key ===================================*/
|
|
auto new_key = Tensor::createDevice<int8_t>({mKvNumHead, (int)mCurrentKeySizePerHead});
|
|
mBackend->onAcquireBuffer(new_key, Backend::STATIC);
|
|
if (mKeyQuantMode != KVQuantMode::None) {
|
|
memset(new_key->host<int8_t>(), 0, mKvNumHead * mCurrentKeySizePerHead);
|
|
}
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memcpy(new_key->host<int8_t>() + h * mCurrentKeySizePerHead, mPastKey->host<int8_t>() + h * mPastKey->stride(0),
|
|
mPastKey->stride(0));
|
|
if (mKeyQuantMode != KVQuantMode::Int8 && (new_key->stride(0) - mPastKey->stride(0)) > 0) {
|
|
memset(new_key->host<int8_t>() + h * new_key->stride(0) + mPastKey->stride(0), 0,
|
|
(new_key->stride(0) - mPastKey->stride(0)));
|
|
}
|
|
}
|
|
mPastKey.reset(new_key);
|
|
/*=================================== Value ===================================*/
|
|
auto newValue = Tensor::createDevice<int8_t>({mKvNumHead, (int)mCurrentValueSizePerHead});
|
|
mBackend->onAcquireBuffer(newValue, Backend::STATIC);
|
|
|
|
if (mUseFlashAttention) { // [mKvNumHead, UP_DIV(mMaxLength, mFlashAttentionUpperKv), UP_DIV(mHeadDim, hP),
|
|
// UP_DIV(mFlashAttentionUpperKv, lP), hP, lP]
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memset(newValue->host<int8_t>() + h * newValue->stride(0), 0, newValue->stride(0));
|
|
memcpy(newValue->host<int8_t>() + h * newValue->stride(0),
|
|
mPastValue->host<int8_t>() + h * mPastValue->stride(0), mPastValue->stride(0));
|
|
}
|
|
} else {
|
|
if (mValueQuantMode == KVQuantMode::Int8) { // [mKvNumHead, UP_DIV(mHeadDim, hP8), (UP_DIV(mMaxLength,
|
|
// lP8)*hP8*lP8+2*hP8*sizeof(float)) ]
|
|
auto currentWeightInside = ROUND_UP(mMaxLength, lP8) * hP8;
|
|
auto currentStride1 = currentWeightInside + 2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES;
|
|
auto currentStride0 = currentStride1 * UP_DIV(mHeadDim, hP8);
|
|
|
|
auto prevWeightInside = ROUND_UP(oldMaxLength, lP8) * hP8;
|
|
auto prevStride1 = prevWeightInside + 2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES;
|
|
auto prevStride0 = prevStride1 * UP_DIV(mHeadDim, hP8);
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
for (int d = 0; d < UP_DIV(mHeadDim, hP8); ++d) {
|
|
auto dstPtr = newValue->host<int8_t>() + h * currentStride0 + d * currentStride1;
|
|
auto srcPtr = mPastValue->host<int8_t>() + h * prevStride0 + d * prevStride1;
|
|
|
|
// initialize 0 for weightInt8
|
|
memset(dstPtr, 0, currentWeightInside);
|
|
// copy inner side weightInt8
|
|
memcpy(dstPtr, srcPtr, prevWeightInside);
|
|
// copy hP8 scale&bias
|
|
memcpy(dstPtr + currentWeightInside, srcPtr + prevWeightInside,
|
|
2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES);
|
|
}
|
|
}
|
|
} else { // [mKvNumHead, UP_DIV(mHeadDim, hP), UP_DIV(mMaxLength, lP), hP, lP]
|
|
auto currentStride1 = ROUND_UP(mMaxLength, lP) * hP * mBytes;
|
|
auto currentStride0 = ROUND_UP(mMaxLength, lP) * hP * UP_DIV(mHeadDim, hP) * mBytes;
|
|
|
|
auto prevStride1 = ROUND_UP(oldMaxLength, lP) * hP * mBytes;
|
|
auto prevStride0 = ROUND_UP(oldMaxLength, lP) * hP * UP_DIV(mHeadDim, hP) * mBytes;
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
for (int d = 0; d < UP_DIV(mHeadDim, hP); ++d) {
|
|
auto dstPtr = newValue->host<int8_t>() + h * currentStride0 + d * currentStride1;
|
|
auto srcPtr = mPastValue->host<int8_t>() + h * prevStride0 + d * prevStride1;
|
|
|
|
// initialize 0 for weight
|
|
if (lP > 1) {
|
|
memset(dstPtr, 0, currentStride1);
|
|
}
|
|
// copy inner side weight
|
|
memcpy(dstPtr, srcPtr, prevStride1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mPastValue.reset(newValue);
|
|
}
|
|
|
|
/*
|
|
** @brief Move the kvcache from memory to the memory-mapped kvcache files in disk
|
|
** Then release the memory buffer of old kvcache
|
|
*/
|
|
void CPUKVCacheManager::moveKVCacheFromMemToDisk(int oldMaxLength) {
|
|
/*=================================== Key ===================================*/
|
|
size_t prevKeySizePerHead = 0;
|
|
if (mKeyQuantMode == KVQuantMode::Int8) {
|
|
prevKeySizePerHead = ROUND_UP(oldMaxLength, hP8) * ROUND_UP(mHeadDim, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(oldMaxLength, hP8);
|
|
} else {
|
|
prevKeySizePerHead = UP_DIV(oldMaxLength, hP) * ROUND_UP(mHeadDim, lP) * hP * mBytes;
|
|
}
|
|
if (mHeadDim % lP || (mKeyQuantMode == KVQuantMode::Int8)) {
|
|
memset(mMapKeyAddr, 0, mKvNumHead * mCurrentKeySizePerHead);
|
|
}
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memcpy(mMapKeyAddr + h * mCurrentKeySizePerHead, mPastKey->host<int8_t>() + h * prevKeySizePerHead,
|
|
prevKeySizePerHead);
|
|
}
|
|
mBackend->onReleaseBuffer(mPastKey.get(), Backend::STATIC);
|
|
mPastKey.reset();
|
|
/*=================================== Value ===================================*/
|
|
{
|
|
size_t prevValueSizePerHead = 0;
|
|
if (mValueQuantMode == KVQuantMode::Int8) {
|
|
prevValueSizePerHead = UP_DIV(oldMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP8) * ROUND_UP(mFlashAttentionUpperKv, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mHeadDim, hP8));
|
|
} else {
|
|
prevValueSizePerHead = UP_DIV(oldMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP) * ROUND_UP(mFlashAttentionUpperKv, lP) * mBytes);
|
|
}
|
|
if (lP > 1 || (mValueQuantMode == KVQuantMode::Int8)) {
|
|
memset(mMapValueAddr, 0, mKvNumHead * mCurrentValueSizePerHead);
|
|
}
|
|
|
|
if (mUseFlashAttention) {
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memcpy(mMapValueAddr + h * mCurrentValueSizePerHead,
|
|
mPastValue->host<int8_t>() + h * prevValueSizePerHead, prevValueSizePerHead);
|
|
}
|
|
} else {
|
|
if (mValueQuantMode == KVQuantMode::Int8) { // [mKvNumHead, UP_DIV(mHeadDim, hP8), (UP_DIV(mMaxLength,
|
|
// lP8)*hP8*lP8+2*hP8*sizeof(float)) ]
|
|
auto currentWeightInside = ROUND_UP(mMaxLength, lP8) * hP8;
|
|
auto currentStride1 = currentWeightInside + 2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES;
|
|
auto currentStride0 = currentStride1 * UP_DIV(mHeadDim, hP8);
|
|
|
|
auto prevWeightInside = ROUND_UP(oldMaxLength, lP8) * hP8;
|
|
auto prevStride1 = prevWeightInside + 2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES;
|
|
auto prevStride0 = prevStride1 * UP_DIV(mHeadDim, hP8);
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
for (int d = 0; d < UP_DIV(mHeadDim, hP8); ++d) {
|
|
auto dstPtr = mMapValueAddr + h * currentStride0 + d * currentStride1;
|
|
auto srcPtr = mPastValue->host<int8_t>() + h * prevStride0 + d * prevStride1;
|
|
|
|
// initialize 0 for weightInt8
|
|
memset(dstPtr, 0, currentWeightInside);
|
|
// copy inner side weightInt8
|
|
memcpy(dstPtr, srcPtr, prevWeightInside);
|
|
// copy hP8 scale&bias
|
|
memcpy(dstPtr + currentWeightInside, srcPtr + prevWeightInside,
|
|
2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES);
|
|
}
|
|
}
|
|
} else { // [mKvNumHead, UP_DIV(mHeadDim, hP), UP_DIV(mMaxLength, lP), hP, lP]
|
|
auto currentStride1 = ROUND_UP(mMaxLength, lP) * hP * mBytes;
|
|
auto currentStride0 = ROUND_UP(mMaxLength, lP) * hP * UP_DIV(mHeadDim, hP) * mBytes;
|
|
|
|
auto prevStride1 = ROUND_UP(oldMaxLength, lP) * hP * mBytes;
|
|
auto prevStride0 = ROUND_UP(oldMaxLength, lP) * hP * UP_DIV(mHeadDim, hP) * mBytes;
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
for (int d = 0; d < UP_DIV(mHeadDim, hP); ++d) {
|
|
auto dstPtr = mMapValueAddr + h * currentStride0 + d * currentStride1;
|
|
auto srcPtr = mPastValue->host<int8_t>() + h * prevStride0 + d * prevStride1;
|
|
|
|
// initialize 0 for weight
|
|
if (lP > 1) {
|
|
memset(dstPtr, 0, currentStride1);
|
|
}
|
|
// copy inner side weight
|
|
memcpy(dstPtr, srcPtr, prevStride1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mBackend->onReleaseBuffer(mPastValue.get(), Backend::STATIC);
|
|
mPastValue.reset();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** @brief Expand the size of kvcache files in disk
|
|
*/
|
|
void CPUKVCacheManager::expandKVCacheInDisk(size_t oldMaxLength, size_t oldKeySize, size_t oldValueSize, size_t keySize,
|
|
size_t valueSize, file_t specKeyFile, file_t specValueFile) {
|
|
// Step 1: Copy the old kvcache from files to temporary buffers in memory
|
|
auto prevKeySizePerHead = oldKeySize / mKvNumHead;
|
|
auto prevValueSizePerHead = oldValueSize / mKvNumHead;
|
|
std::shared_ptr<Tensor> prevKey, prevValue;
|
|
// NOTE: Tensor shape is std::vector<int>. prevKeySizePerHead is size_t after
|
|
// the int -> size_t fix; cast back to int here. In realistic models a single
|
|
// head's cache is far below 2 GB, so the narrowing is safe in practice.
|
|
prevKey.reset(Tensor::createDevice<int8_t>({mKvNumHead, (int)prevKeySizePerHead}));
|
|
prevValue.reset(Tensor::createDevice<int8_t>({mKvNumHead, (int)prevValueSizePerHead}));
|
|
|
|
mBackend->onAcquireBuffer(prevKey.get(), Backend::STATIC);
|
|
mBackend->onAcquireBuffer(prevValue.get(), Backend::STATIC);
|
|
if (mHeadDim % lP) {
|
|
memset(prevKey->host<uint8_t>(), 0, prevKey->length(0) * prevKey->stride(0));
|
|
}
|
|
if (lP > 1) {
|
|
// can't be mMaxLenth % lP, since mMaxLength may be larger than seq_len for prefilling, we should ensure the
|
|
// (mMaxLength - seq_len)'s buffer is 0. computing L is seq_len
|
|
memset(prevValue->host<uint8_t>(), 0, prevValue->length(0) * prevValue->stride(0));
|
|
}
|
|
mmapKVCache(oldKeySize, oldValueSize, specKeyFile, specValueFile);
|
|
memcpy(prevKey->host<int8_t>(), mMapKeyAddr, oldKeySize);
|
|
memcpy(prevValue->host<int8_t>(), mMapValueAddr, oldValueSize);
|
|
// Step 2: Resize the kvcache files and remap them
|
|
unmapKVCache(oldKeySize, oldValueSize);
|
|
resetKVCacheFileSize(keySize, valueSize);
|
|
mmapKVCache(keySize, valueSize);
|
|
// Step 3: Move the kvcache from temporary buffers in memory to disk
|
|
memset(mMapKeyAddr, 0, keySize);
|
|
memset(mMapValueAddr, 0, valueSize);
|
|
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memcpy(mMapKeyAddr + h * mCurrentKeySizePerHead, prevKey->host<int8_t>() + h * prevKeySizePerHead,
|
|
prevKeySizePerHead);
|
|
}
|
|
|
|
if (mUseFlashAttention) {
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memcpy(mMapValueAddr + h * mCurrentValueSizePerHead, prevValue->host<int8_t>() + h * prevValueSizePerHead,
|
|
prevValueSizePerHead);
|
|
}
|
|
} else {
|
|
if (mValueQuantMode == KVQuantMode::Int8) {
|
|
auto currentWeightInside = ROUND_UP(mMaxLength, lP8) * hP8;
|
|
auto currentStride1 = currentWeightInside + 2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES;
|
|
auto currentStride0 = currentStride1 * UP_DIV(mHeadDim, hP8);
|
|
|
|
auto prevWeightInside = ROUND_UP(oldMaxLength, lP8) * hP8;
|
|
auto prevStride1 = prevWeightInside + 2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES;
|
|
auto prevStride0 = prevStride1 * UP_DIV(mHeadDim, hP8);
|
|
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
for (int d = 0; d < UP_DIV(mHeadDim, hP8); ++d) {
|
|
auto dstPtr = mMapValueAddr + h * currentStride0 + d * currentStride1;
|
|
auto srcPtr = prevValue->host<int8_t>() + h * prevStride0 + d * prevStride1;
|
|
|
|
// initialize 0 for weightInt8
|
|
memset(dstPtr, 0, currentWeightInside);
|
|
// copy inner side weightInt8
|
|
memcpy(dstPtr, srcPtr, prevWeightInside);
|
|
// copy hP8 scale&bias
|
|
memcpy(dstPtr + currentWeightInside, srcPtr + prevWeightInside,
|
|
2 * mConfig.mBlockNum * hP8 * QUANT_INFO_BYTES);
|
|
}
|
|
}
|
|
} else {
|
|
auto currentStride1 = ROUND_UP(mMaxLength, lP) * hP * mBytes;
|
|
auto currentStride0 = ROUND_UP(mMaxLength, lP) * hP * UP_DIV(mHeadDim, hP) * mBytes;
|
|
|
|
auto prevStride1 = ROUND_UP(oldMaxLength, lP) * hP * mBytes;
|
|
auto prevStride0 = ROUND_UP(oldMaxLength, lP) * hP * UP_DIV(mHeadDim, hP) * mBytes;
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
for (int d = 0; d < UP_DIV(mHeadDim, hP); ++d) {
|
|
auto dstPtr = mMapValueAddr + h * currentStride0 + d * currentStride1;
|
|
auto srcPtr = prevValue->host<int8_t>() + h * prevStride0 + d * prevStride1;
|
|
|
|
// initialize 0 for weight
|
|
if (lP > 1) {
|
|
memset(dstPtr, 0, currentStride1);
|
|
}
|
|
// copy inner side weight
|
|
memcpy(dstPtr, srcPtr, prevStride1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step 4: Release the temporary buffers
|
|
mBackend->onReleaseBuffer(prevKey.get(), Backend::STATIC);
|
|
mBackend->onReleaseBuffer(prevValue.get(), Backend::STATIC);
|
|
}
|
|
|
|
void CPUKVCacheManager::onResize(int kv_num_head, int head_dim) {
|
|
mKvNumHead = kv_num_head;
|
|
mHeadDim = head_dim;
|
|
auto core = static_cast<CPUBackend*>(mBackend)->functions();
|
|
core->MNNGetMatMulPackMode(&eP, &lP, &hP);
|
|
mBytes = core->bytes;
|
|
mThreadNum = static_cast<CPUBackend*>(mBackend)->threadNumber();
|
|
if (mThreadNum > mKvNumHead) {
|
|
mThreadNum = mKvNumHead;
|
|
}
|
|
|
|
static_cast<CPUBackend*>(mBackend)->int8Functions()->MNNGetGemmUnit(&hP8, &lP8, &eP8);
|
|
mQuantKeyFunc = core->MNNQuantAttentionKey;
|
|
mQuantValueFunc = core->MNNQuantAttentionValue;
|
|
}
|
|
|
|
void CPUKVCacheManager::onAlloc(KVMeta* meta, int seq_len) {
|
|
mMeta = meta;
|
|
|
|
// load disk prefix kvcache
|
|
if (mMeta != nullptr && mMeta->file_name.size() > 0 && mMeta->file_flag == KVMeta::PendingRead) {
|
|
// create new files
|
|
std::string pathk = MNNFilePathConcat(mConfig.mPrefixCacheDir, mMeta->file_name) + "_" +
|
|
std::to_string(mMeta->layer_index) + ".k";
|
|
std::string pathv = MNNFilePathConcat(mConfig.mPrefixCacheDir, mMeta->file_name) + "_" +
|
|
std::to_string(mMeta->layer_index++) + ".v";
|
|
mMeta->layer_index = mMeta->layer_index % mMeta->layer_nums;
|
|
auto old_key_fd = MNNOpenFile(pathk.c_str(), MNN_FILE_WRITE);
|
|
auto old_value_fd = MNNOpenFile(pathv.c_str(), MNN_FILE_WRITE);
|
|
if (old_key_fd == INVALID_FILE) {
|
|
MNN_PRINT("Failed to open the file: %s\n", pathk.c_str());
|
|
}
|
|
if (old_value_fd == INVALID_FILE) {
|
|
MNN_PRINT("Failed to open the file: %s\n", pathv.c_str());
|
|
}
|
|
|
|
// get kv cache file info
|
|
auto oldKeySize = MNNGetFileSize(old_key_fd);
|
|
auto oldValueSize = MNNGetFileSize(old_value_fd);
|
|
|
|
size_t oldMaxLength = 0;
|
|
if (mKeyQuantMode != KVQuantMode::None || mValueQuantMode != KVQuantMode::None) {
|
|
MNN_ERROR("[Error]: Currently, kvcache save in disk not support quantized key/value\n");
|
|
} else {
|
|
size_t oldKeyMaxLength = oldKeySize / (mKvNumHead * ROUND_UP(mHeadDim, lP) * mBytes);
|
|
size_t oldValueMaxLength = oldValueSize / (mKvNumHead * ROUND_UP(mHeadDim, hP) * mBytes);
|
|
oldMaxLength = ALIMIN(oldKeyMaxLength, oldValueMaxLength);
|
|
}
|
|
if (oldMaxLength < meta->seqlen_in_disk) {
|
|
MNN_ERROR("[Error]: Kvcache in disk size smaller than saved lengthInDiskToload:%d\n",
|
|
(int)meta->seqlen_in_disk);
|
|
}
|
|
|
|
// Update mMaxLength first, then setFlashAttentionUpperKv to avoid division by zero
|
|
int kv_seq_len = meta->add + meta->seqlen_in_disk;
|
|
mMaxLength = kv_seq_len > oldMaxLength ? kv_seq_len + mConfig.mExpandChunk : oldMaxLength;
|
|
if (mUseFlashAttention) {
|
|
setFlashAttentionUpperKv(MNN_FLASH_ATTENTION_BLOCK_SIZE);
|
|
} else {
|
|
setFlashAttentionUpperKv(mMaxLength);
|
|
}
|
|
size_t keySize = (size_t)mKvNumHead * ROUND_UP(mMaxLength, hP) * ROUND_UP(mHeadDim, lP) * mBytes;
|
|
size_t valueSize = (size_t)mKvNumHead * UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP) * ROUND_UP(mFlashAttentionUpperKv, lP) * mBytes);
|
|
|
|
keySize = ALIMAX(keySize, oldKeySize);
|
|
valueSize = ALIMAX(valueSize, oldValueSize);
|
|
|
|
if (mKeyQuantMode == KVQuantMode::TQ3) {
|
|
int tq3BytesPerSeq = (mHeadDim / TQ3_BLOCK_SIZE) * TQ3_BYTES_PER_BLOCK;
|
|
mCurrentKeySizePerHead = (size_t)mMaxLength * tq3BytesPerSeq;
|
|
} else if (mKeyQuantMode == KVQuantMode::TQ4) {
|
|
int tq4BytesPerSeq = (mHeadDim / TQ4_BLOCK_SIZE) * TQ4_BYTES_PER_BLOCK;
|
|
mCurrentKeySizePerHead = (size_t)mMaxLength * tq4BytesPerSeq;
|
|
} else if (mKeyQuantMode == KVQuantMode::Int8) {
|
|
mCurrentKeySizePerHead = ROUND_UP(mMaxLength, hP8) * ROUND_UP(mHeadDim, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mMaxLength, hP8);
|
|
} else {
|
|
mCurrentKeySizePerHead = ROUND_UP(mMaxLength, hP) * ROUND_UP(mHeadDim, lP) * mBytes;
|
|
}
|
|
if (mValueQuantMode == KVQuantMode::TQ3) {
|
|
int tq3BytesPerSeq = (mHeadDim / TQ3_BLOCK_SIZE) * TQ3_BYTES_PER_BLOCK;
|
|
mCurrentValueSizePerHead = (size_t)mMaxLength * tq3BytesPerSeq;
|
|
} else if (mValueQuantMode == KVQuantMode::TQ4) {
|
|
int tq4BytesPerSeq = (mHeadDim / TQ4_BLOCK_SIZE) * TQ4_BYTES_PER_BLOCK;
|
|
mCurrentValueSizePerHead = (size_t)mMaxLength * tq4BytesPerSeq;
|
|
} else if (mValueQuantMode == KVQuantMode::Int8) {
|
|
mCurrentValueSizePerHead = UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP8) * ROUND_UP(mFlashAttentionUpperKv, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mHeadDim, hP8));
|
|
} else {
|
|
mCurrentValueSizePerHead = UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP) * ROUND_UP(mFlashAttentionUpperKv, lP) * mBytes);
|
|
}
|
|
|
|
createKVCacheFile();
|
|
resetKVCacheFileSize(keySize, valueSize);
|
|
expandKVCacheInDisk(oldMaxLength, oldKeySize, oldValueSize, keySize, valueSize, old_key_fd, old_value_fd);
|
|
mPastLength = meta->seqlen_in_disk;
|
|
mKVCacheInDisk = true;
|
|
|
|
return;
|
|
}
|
|
|
|
// Do not use mMeta->add, because in VL models or Qnn case, mMeta->add is 0 or mMeta is nullptr.
|
|
int kv_seq_len = seq_len;
|
|
mMaxLength = kv_seq_len + mConfig.mExpandChunk;
|
|
if (mUseFlashAttention) {
|
|
setFlashAttentionUpperKv(MNN_FLASH_ATTENTION_BLOCK_SIZE);
|
|
} else {
|
|
setFlashAttentionUpperKv(mMaxLength);
|
|
}
|
|
|
|
// 1. compute size
|
|
if (mKeyQuantMode == KVQuantMode::TQ3) {
|
|
mCurrentKeySizePerHead = (size_t)mMaxLength * (mHeadDim / TQ3_BLOCK_SIZE) * TQ3_BYTES_PER_BLOCK;
|
|
} else if (mKeyQuantMode == KVQuantMode::TQ4) {
|
|
mCurrentKeySizePerHead = (size_t)mMaxLength * (mHeadDim / TQ4_BLOCK_SIZE) * TQ4_BYTES_PER_BLOCK;
|
|
} else if (mKeyQuantMode == KVQuantMode::Int8) {
|
|
mCurrentKeySizePerHead = ROUND_UP(mMaxLength, hP8) * ROUND_UP(mHeadDim, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mMaxLength, hP8);
|
|
} else {
|
|
mCurrentKeySizePerHead = ROUND_UP(mMaxLength, hP) * ROUND_UP(mHeadDim, lP) * mBytes;
|
|
}
|
|
if (mValueQuantMode == KVQuantMode::TQ3) {
|
|
mCurrentValueSizePerHead = (size_t)mMaxLength * (mHeadDim / TQ3_BLOCK_SIZE) * TQ3_BYTES_PER_BLOCK;
|
|
} else if (mValueQuantMode == KVQuantMode::TQ4) {
|
|
mCurrentValueSizePerHead = (size_t)mMaxLength * (mHeadDim / TQ4_BLOCK_SIZE) * TQ4_BYTES_PER_BLOCK;
|
|
} else if (mValueQuantMode == KVQuantMode::Int8) {
|
|
mCurrentValueSizePerHead = UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP8) * ROUND_UP(mFlashAttentionUpperKv, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mHeadDim, hP8));
|
|
} else {
|
|
mCurrentValueSizePerHead = UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP) * ROUND_UP(mFlashAttentionUpperKv, lP) * mBytes);
|
|
}
|
|
size_t keySize = (size_t)mKvNumHead * mCurrentKeySizePerHead;
|
|
size_t valueSize = (size_t)mKvNumHead * mCurrentValueSizePerHead;
|
|
|
|
// 2. allocate buffer
|
|
|
|
// case1: key&value size exceeds the limited size
|
|
// case2: multi prompts share a common prefix kv cache info
|
|
bool storeKvInDisk = !mConfig.mKVCacheDir.empty();
|
|
bool sharePrefixKv = mMeta != nullptr && mMeta->file_name.size() > 0 && mMeta->file_flag == KVMeta::PendingWrite;
|
|
|
|
if (sharePrefixKv) {
|
|
mSaveShareKvPrefix = true;
|
|
if (!MNNCreateDir(mConfig.mPrefixCacheDir.c_str())) {
|
|
MNN_PRINT("Failed to create prefix cache file dir: %s\n", mConfig.mPrefixCacheDir.c_str());
|
|
}
|
|
}
|
|
if (storeKvInDisk || sharePrefixKv) { // store kv in disk
|
|
std::string keyStoredDst = "";
|
|
std::string valueStoredDst = "";
|
|
if (mMeta != nullptr) {
|
|
mBasePrefixFileName =
|
|
MNNFilePathConcat(mConfig.mPrefixCacheDir, mMeta->file_name) + "_" + std::to_string(mMeta->layer_index);
|
|
keyStoredDst = sharePrefixKv ? mBasePrefixFileName + ".k" : "";
|
|
valueStoredDst = sharePrefixKv ? mBasePrefixFileName + ".v" : "";
|
|
mMeta->layer_index++;
|
|
mMeta->layer_index = mMeta->layer_index % mMeta->layer_nums;
|
|
}
|
|
createKVCacheFile(keyStoredDst, valueStoredDst);
|
|
resetKVCacheFileSize(keySize, valueSize);
|
|
mmapKVCache(keySize, valueSize);
|
|
mKVCacheInDisk = true;
|
|
} else { // store kv in memory
|
|
mPastKey.reset(Tensor::createDevice<int8_t>({mKvNumHead, (int)mCurrentKeySizePerHead}));
|
|
mPastValue.reset(Tensor::createDevice<int8_t>({mKvNumHead, (int)mCurrentValueSizePerHead}));
|
|
|
|
mBackend->onAcquireBuffer(mPastKey.get(), Backend::STATIC);
|
|
mBackend->onAcquireBuffer(mPastValue.get(), Backend::STATIC);
|
|
|
|
// initilize 0
|
|
if (mHeadDim % lP || mKeyQuantMode != KVQuantMode::None) {
|
|
memset(mPastKey->host<int8_t>(), 0, mPastKey->length(0) * mPastKey->stride(0));
|
|
}
|
|
if (lP > 1 || mValueQuantMode != KVQuantMode::None) {
|
|
memset(mPastValue->host<int8_t>(), 0, mPastValue->length(0) * mPastValue->stride(0));
|
|
}
|
|
}
|
|
// scale, zero point and sum of key for quantization
|
|
if (mKeyQuantMode == KVQuantMode::Int8) { // quant K
|
|
mKeySum.reset(Tensor::createDevice<int8_t>({mKvNumHead, ROUND_UP(mMaxLength, hP8) * QUANT_INFO_BYTES}));
|
|
mKeyMax.reset(Tensor::createDevice<int8_t>({mKvNumHead, mHeadDim * QUANT_INFO_BYTES}));
|
|
mBackend->onAcquireBuffer(mKeySum.get(), Backend::STATIC);
|
|
mBackend->onAcquireBuffer(mKeyMax.get(), Backend::STATIC);
|
|
|
|
for (int ks = 0; ks < mKvNumHead * mHeadDim; ++ks) {
|
|
mKeyMax->host<float>()[ks] = std::numeric_limits<float>::lowest();
|
|
}
|
|
if (mBytes == 2) {
|
|
auto core = static_cast<CPUBackend*>(mBackend)->functions();
|
|
core->MNNFp32ToLowp(mKeyMax->host<float>(), (int16_t*)(mKeyMax->host<float>()), mKvNumHead * mHeadDim);
|
|
}
|
|
}
|
|
if (mValueQuantMode == KVQuantMode::Int8) {
|
|
mValueSum.reset(Tensor::createDevice<int8_t>(
|
|
{mKvNumHead, (int)UP_DIV(mMaxLength, mFlashAttentionUpperKv), ROUND_UP(mHeadDim, hP8) * QUANT_INFO_BYTES}));
|
|
mBackend->onAcquireBuffer(mValueSum.get(), Backend::STATIC);
|
|
memset(mValueSum->host<int8_t>(), 0, mValueSum->stride(0) * mValueSum->length(0));
|
|
}
|
|
}
|
|
|
|
void CPUKVCacheManager::onRealloc(KVMeta* meta) {
|
|
auto kv_seq_len = meta->previous + meta->add - meta->remove + meta->computeReverseSize();
|
|
if (kv_seq_len > mMaxLength) {
|
|
// Realloc
|
|
int oldMaxLength = mMaxLength;
|
|
mMaxLength = (int)kv_seq_len + mConfig.mExpandChunk;
|
|
if (mUseFlashAttention) {
|
|
setFlashAttentionUpperKv(MNN_FLASH_ATTENTION_BLOCK_SIZE);
|
|
} else {
|
|
setFlashAttentionUpperKv(mMaxLength);
|
|
}
|
|
size_t oldKeySize = (size_t)mKvNumHead * mCurrentKeySizePerHead;
|
|
size_t oldValueSize = (size_t)mKvNumHead * mCurrentValueSizePerHead;
|
|
|
|
// update current key size per head
|
|
if (mKeyQuantMode == KVQuantMode::TQ3) {
|
|
mCurrentKeySizePerHead = (size_t)mMaxLength * (mHeadDim / TQ3_BLOCK_SIZE) * TQ3_BYTES_PER_BLOCK;
|
|
} else if (mKeyQuantMode == KVQuantMode::TQ4) {
|
|
mCurrentKeySizePerHead = (size_t)mMaxLength * (mHeadDim / TQ4_BLOCK_SIZE) * TQ4_BYTES_PER_BLOCK;
|
|
} else if (mKeyQuantMode == KVQuantMode::Int8) {
|
|
mCurrentKeySizePerHead = ROUND_UP(mMaxLength, hP8) * ROUND_UP(mHeadDim, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mMaxLength, hP8);
|
|
} else {
|
|
mCurrentKeySizePerHead = UP_DIV(mMaxLength, hP) * ROUND_UP(mHeadDim, lP) * hP * mBytes;
|
|
}
|
|
// update current value size per head
|
|
if (mValueQuantMode == KVQuantMode::TQ3) {
|
|
mCurrentValueSizePerHead = (size_t)mMaxLength * (mHeadDim / TQ3_BLOCK_SIZE) * TQ3_BYTES_PER_BLOCK;
|
|
} else if (mValueQuantMode == KVQuantMode::TQ4) {
|
|
mCurrentValueSizePerHead = (size_t)mMaxLength * (mHeadDim / TQ4_BLOCK_SIZE) * TQ4_BYTES_PER_BLOCK;
|
|
} else if (mValueQuantMode == KVQuantMode::Int8) {
|
|
mCurrentValueSizePerHead = UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP8) * ROUND_UP(mFlashAttentionUpperKv, lP8) +
|
|
2 * QUANT_INFO_BYTES * mConfig.mBlockNum * ROUND_UP(mHeadDim, hP8));
|
|
} else {
|
|
mCurrentValueSizePerHead = UP_DIV(mMaxLength, mFlashAttentionUpperKv) *
|
|
(ROUND_UP(mHeadDim, hP) * ROUND_UP(mFlashAttentionUpperKv, lP) * mBytes);
|
|
}
|
|
size_t keySize = (size_t)mKvNumHead * mCurrentKeySizePerHead;
|
|
size_t valueSize = (size_t)mKvNumHead * mCurrentValueSizePerHead;
|
|
|
|
/*==== No limit for kvcache ====*/
|
|
if (mKVCacheInDisk == false) {
|
|
expandKVCacheInMem(oldMaxLength);
|
|
} else {
|
|
expandKVCacheInDisk(oldMaxLength, oldKeySize, oldValueSize, keySize, valueSize);
|
|
}
|
|
/* No matter where is the kvcache, the scales and zero points are always in memory, since their size is very
|
|
* small */
|
|
if (mKeyQuantMode == KVQuantMode::Int8) {
|
|
auto newKeySumTensor = Tensor::createDevice<int32_t>({mKvNumHead, UP_DIV(mMaxLength, hP8), hP8});
|
|
mBackend->onAcquireBuffer(newKeySumTensor, Backend::STATIC);
|
|
for (int h = 0; h < mKvNumHead; h++) {
|
|
memcpy(newKeySumTensor->host<int8_t>() + h * UP_DIV(mMaxLength, hP8) * hP8 * 4,
|
|
mKeySum->host<int8_t>() + h * UP_DIV(oldMaxLength, hP8) * hP8 * 4,
|
|
UP_DIV(oldMaxLength, hP8) * hP8 * 4);
|
|
}
|
|
mKeySum.reset(newKeySumTensor);
|
|
}
|
|
if (mValueQuantMode == KVQuantMode::Int8) {
|
|
auto newValueSumTensor =
|
|
Tensor::createDevice<int8_t>({mKvNumHead, (int)UP_DIV(mMaxLength, mFlashAttentionUpperKv),
|
|
ROUND_UP(mHeadDim, hP8) * QUANT_INFO_BYTES});
|
|
mBackend->onAcquireBuffer(newValueSumTensor, Backend::STATIC);
|
|
auto remainSizePerHead = mValueSum->stride(0);
|
|
auto increSizePerHead = newValueSumTensor->stride(0) - mValueSum->stride(0);
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
memcpy(newValueSumTensor->host<int8_t>() + h * newValueSumTensor->stride(0),
|
|
mValueSum->host<int8_t>() + h * mValueSum->stride(0), remainSizePerHead);
|
|
// memset 0
|
|
if (increSizePerHead > 0) {
|
|
memset(newValueSumTensor->host<int8_t>() + h * newValueSumTensor->stride(0) + remainSizePerHead, 0,
|
|
increSizePerHead);
|
|
}
|
|
}
|
|
mValueSum.reset(newValueSumTensor);
|
|
}
|
|
}
|
|
// Remove
|
|
auto start = mPastLength - meta->remove;
|
|
if (0 == meta->n_reserve || mKeyQuantMode != KVQuantMode::None ||
|
|
mValueQuantMode != KVQuantMode::None) { // n_reserve > 0 is not currently supported when K or V is quantized.
|
|
mPastLength = start;
|
|
return;
|
|
}
|
|
#if 1
|
|
auto dstIndex = start;
|
|
for (int n = 0; n < meta->n_reserve; ++n) {
|
|
auto begin = meta->reserve[2 * n];
|
|
auto size = meta->reserve[2 * n + 1];
|
|
auto srcIndex = start + begin;
|
|
if (mBytes == 2) {
|
|
moveKV<FLOAT16_T>(srcIndex, dstIndex, size);
|
|
} else {
|
|
moveKV<float>(srcIndex, dstIndex, size);
|
|
}
|
|
dstIndex += size;
|
|
}
|
|
mPastLength = dstIndex;
|
|
#else
|
|
// Don't support not align reserve
|
|
auto align = hP;
|
|
auto dstStart = start;
|
|
auto lastValidSrcEnd = start;
|
|
for (int n = 0; n < meta->n_reserve; ++n) {
|
|
auto lastEndAlign = UP_DIV(lastValidSrcEnd, align) * align;
|
|
auto begin = meta->reserve[2 * n];
|
|
auto size = meta->reserve[2 * n + 1];
|
|
auto startAlign = ((begin + start) / align) * align;
|
|
if (startAlign <= lastEndAlign) {
|
|
// Fullly reserve
|
|
dstStart = dstStart + size;
|
|
lastValidSrcEnd = begin + start + size;
|
|
continue;
|
|
}
|
|
auto end = begin + start + size;
|
|
auto endAlign = UP_DIV(end, align) * align;
|
|
|
|
auto sizeUnit = (endAlign - startAlign) / align;
|
|
auto dstStartAlign = UP_DIV(dstStart, align) * align;
|
|
|
|
// TODO: Support Quant
|
|
// mPastKey.reset(Tensor::createDevice<float>({mKvNumHead, UP_DIV(mMaxLength, hP), mHeadDim, hP}));
|
|
|
|
// Move K
|
|
auto keyStride = UP_DIV(mMaxLength, align) * align * ROUND_UP(mHeadDim, lP);
|
|
auto dstKAddr = keyAddr() + dstStartAlign * ROUND_UP(mHeadDim, lP) * mBytes;
|
|
auto srcKAddr = keyAddr() + startAlign * ROUND_UP(mHeadDim, lP) * mBytes;
|
|
for (int i = 0; i < mKvNumHead; ++i) {
|
|
auto dst = dstKAddr + i * keyStride * mBytes;
|
|
auto src = srcKAddr + i * keyStride * mBytes;
|
|
for (int j = 0; j < sizeUnit; ++j) {
|
|
::memcpy(dst + j * align * ROUND_UP(mHeadDim, lP) * mBytes,
|
|
src + j * align * ROUND_UP(mHeadDim, lP) * mBytes, align * ROUND_UP(mHeadDim, lP) * mBytes);
|
|
}
|
|
}
|
|
|
|
// Move V
|
|
auto dstVAddr = valudAddr() + dstStartAlign * align * mBytes;
|
|
auto srcVAddr = valudAddr() + startAlign * align * mBytes;
|
|
auto number = mKvNumHead * UP_DIV(mHeadDim, align);
|
|
for (int i = 0; i < number; ++i) {
|
|
auto dst = dstVAddr + i * ROUND_UP(mMaxLength, lP) * align * mBytes;
|
|
auto src = srcVAddr + i * ROUND_UP(mMaxLength, lP) * align * mBytes;
|
|
for (int j = 0; j < sizeUnit; ++j) {
|
|
::memcpy(dst + j * align * align * mBytes, src + j * align * align * mBytes, align * align * mBytes);
|
|
}
|
|
}
|
|
dstStart = dstStart + size;
|
|
lastValidSrcEnd = begin + start + size;
|
|
}
|
|
mPastLength = dstStart;
|
|
#endif
|
|
}
|
|
|
|
void CPUKVCacheManager::saveKVCacheInDisk() {
|
|
// get original kv cache info
|
|
auto keySize = MNNGetFileSize(mKeyCacheFD);
|
|
auto valueSize = MNNGetFileSize(mValueCacheFD);
|
|
mmapKVCache(keySize, valueSize);
|
|
if (!MNNCreateDir(mConfig.mPrefixCacheDir.c_str())) {
|
|
MNN_PRINT("Failed to create prefix cache file dir: %s\n", mConfig.mPrefixCacheDir.c_str());
|
|
}
|
|
|
|
// create new files
|
|
std::string pathk =
|
|
MNNFilePathConcat(mConfig.mPrefixCacheDir, mMeta->file_name) + "_" + std::to_string(mMeta->layer_index) + ".k";
|
|
std::string pathv = MNNFilePathConcat(mConfig.mPrefixCacheDir, mMeta->file_name) + "_" +
|
|
std::to_string(mMeta->layer_index++) + ".v";
|
|
mMeta->layer_index = mMeta->layer_index % mMeta->layer_nums;
|
|
|
|
auto new_key_fd = MNNCreateFile(pathk.c_str());
|
|
auto new_value_fd = MNNCreateFile(pathv.c_str());
|
|
if (new_key_fd == INVALID_FILE) {
|
|
MNN_PRINT("Failed to create the file: %s\n", pathk.c_str());
|
|
}
|
|
if (new_value_fd == INVALID_FILE) {
|
|
MNN_PRINT("Failed to create the file: %s\n", pathv.c_str());
|
|
}
|
|
// set new file size
|
|
if (MNNSetFileSize(new_key_fd, keySize) != MNN::NO_ERROR ||
|
|
MNNSetFileSize(new_value_fd, valueSize) != MNN::NO_ERROR) {
|
|
MNN_PRINT("Failed to resize the kvcache files!\n");
|
|
}
|
|
// mmap files
|
|
int8_t* mMapNewKeyAddr = (int8_t*)MNNMmapFile(new_key_fd, keySize);
|
|
if (mMapNewKeyAddr == nullptr) {
|
|
MNN_PRINT("Failed to memory-map the new kvcache!\n");
|
|
}
|
|
int8_t* mMapNewValueAddr = (int8_t*)MNNMmapFile(new_value_fd, valueSize);
|
|
if (mMapNewValueAddr == nullptr) {
|
|
MNN_PRINT("Failed to memory-map the kvcache!\n");
|
|
}
|
|
|
|
// copy
|
|
memcpy(mMapNewKeyAddr, mMapKeyAddr, keySize);
|
|
memcpy(mMapNewValueAddr, mMapValueAddr, valueSize);
|
|
|
|
// unmap new files
|
|
if (mMapNewKeyAddr != nullptr) {
|
|
MNNUnmapFile(mMapNewKeyAddr, keySize);
|
|
mMapNewKeyAddr = nullptr;
|
|
}
|
|
if (mMapNewValueAddr != nullptr) {
|
|
MNNUnmapFile(mMapNewValueAddr, valueSize);
|
|
mMapNewValueAddr = nullptr;
|
|
}
|
|
// close file
|
|
if (new_key_fd != INVALID_FILE) {
|
|
MNNCloseFile(new_key_fd);
|
|
new_key_fd = INVALID_FILE;
|
|
}
|
|
if (new_value_fd != INVALID_FILE) {
|
|
MNNCloseFile(new_value_fd);
|
|
new_value_fd = INVALID_FILE;
|
|
}
|
|
}
|
|
|
|
void CPUKVCacheManager::onClear() {
|
|
if (mKVCacheInDisk) {
|
|
// mSaveShareKvPrefix also need unmap file
|
|
unmapKVCache(mCurrentKeySizePerHead * (size_t)mKvNumHead, mCurrentValueSizePerHead * (size_t)mKvNumHead);
|
|
if (!mSaveShareKvPrefix) {
|
|
// delete temp kvcache file
|
|
removeKVCacheFile();
|
|
}
|
|
mKVCacheInDisk = false;
|
|
}
|
|
mPastKey.reset();
|
|
mPastValue.reset();
|
|
mKeySum.reset();
|
|
mKeyMax.reset();
|
|
mValueSum.reset();
|
|
mMaxLength = mPastLength = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
void CPUKVCacheManager::ProcessKey(const Tensor* key, int seqLen, int kvHead) {
|
|
if ((mKeyQuantMode == KVQuantMode::TQ3) || (mKeyQuantMode == KVQuantMode::TQ4)) {
|
|
int bytesPerBlock = (mKeyQuantMode == KVQuantMode::TQ3) ? TQ3_BYTES_PER_BLOCK : TQ4_BYTES_PER_BLOCK;
|
|
int blockSize = (mKeyQuantMode == KVQuantMode::TQ3) ? TQ3_BLOCK_SIZE : TQ4_BLOCK_SIZE;
|
|
int bytesPerSeq = (mHeadDim / blockSize) * bytesPerBlock;
|
|
uint8_t* keyDst = (uint8_t*)addrOfKey(kvHead);
|
|
for (int i = 0; i < seqLen; i++) {
|
|
T* src = key->host<T>() + i * mKvNumHead * mHeadDim + kvHead * mHeadDim;
|
|
uint8_t* dst = keyDst + (mPastLength + i) * bytesPerSeq;
|
|
float block[TQ4_BLOCK_SIZE]; // TQ4_BLOCK_SIZE >= TQ3_BLOCK_SIZE
|
|
for (int b = 0; b < mHeadDim / blockSize; b++) {
|
|
for (int k = 0; k < blockSize; k++) {
|
|
block[k] = (float)src[b * blockSize + k];
|
|
}
|
|
if (mKeyQuantMode == KVQuantMode::TQ3) {
|
|
tq3_quantize_block(dst + b * bytesPerBlock, block);
|
|
} else {
|
|
tq4_quantize_block(dst + b * bytesPerBlock, block);
|
|
}
|
|
}
|
|
}
|
|
} else if (mKeyQuantMode ==
|
|
KVQuantMode::Int8) { // [seqLen, headDim] -> [maxlen/hP8, blockNum, (headDim/blockNum)/lP8, hP8, lP8]
|
|
int8_t* keyDst = reinterpret_cast<int8_t*>(addrOfKey(kvHead));
|
|
float* sumDst = reinterpret_cast<float*>(addrOfKeySum(kvHead));
|
|
|
|
auto blockL = UP_DIV(mHeadDim, mConfig.mBlockNum);
|
|
auto weightStride1 = ROUND_UP(blockL, lP8) * hP8;
|
|
auto weightStride2 = lP8 * hP8;
|
|
auto packedWeightStride1 = weightStride1 + 2 * QUANT_INFO_BYTES * hP8;
|
|
|
|
T* keyMax = reinterpret_cast<T*>(addrOfKeyMax(kvHead));
|
|
int32_t params[] = {mKvNumHead, seqLen, mHeadDim, mConfig.mBlockNum, eP8, lP8, hP8, mPastLength, kvHead};
|
|
mQuantKeyFunc(keyDst, key->host<float>(), sumDst, (float*)keyMax, params);
|
|
} else { // target: [maxlen/hP, headdim/lP, hP, lP]
|
|
T* key_dst = reinterpret_cast<T*>(addrOfKey(kvHead));
|
|
auto stride0 = ROUND_UP(mHeadDim, lP) * hP;
|
|
auto stride1 = hP * lP;
|
|
for (int i = 0; i < seqLen; i++) {
|
|
T* key_src = key->host<T>() + i * mKvNumHead * mHeadDim + kvHead * mHeadDim;
|
|
int out_index = (mPastLength + i) / hP;
|
|
int in_index = (mPastLength + i) % hP;
|
|
for (int j = 0; j < mHeadDim; j++) {
|
|
key_dst[out_index * stride0 + (j / lP) * stride1 + in_index * lP + (j % lP)] = key_src[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void CPUKVCacheManager::ProcessValue(const Tensor* value, int seqLen, int kvHead) { // [headdim/hP, maxlen, hP]
|
|
const bool valueC4 = TensorUtils::getDescribe(value)->dimensionFormat == MNN_DATA_FORMAT_NC4HW4;
|
|
const int pack = static_cast<CPUBackend*>(mBackend)->functions()->pack;
|
|
const auto valueSrc = value->host<T>();
|
|
auto loadValue = [&](int token, int channel) {
|
|
if (valueC4) {
|
|
return valueSrc[c4Offset(token, channel, seqLen, pack)];
|
|
}
|
|
return valueSrc[token * mKvNumHead * mHeadDim + channel];
|
|
};
|
|
const int channelBase = kvHead * mHeadDim;
|
|
if ((mValueQuantMode == KVQuantMode::TQ3) || (mValueQuantMode == KVQuantMode::TQ4)) {
|
|
int bytesPerBlock = (mValueQuantMode == KVQuantMode::TQ3) ? TQ3_BYTES_PER_BLOCK : TQ4_BYTES_PER_BLOCK;
|
|
int blockSize = (mValueQuantMode == KVQuantMode::TQ3) ? TQ3_BLOCK_SIZE : TQ4_BLOCK_SIZE;
|
|
int bytesPerSeq = (mHeadDim / blockSize) * bytesPerBlock;
|
|
uint8_t* valueDst = (uint8_t*)addrOfValue(kvHead);
|
|
for (int i = 0; i < seqLen; i++) {
|
|
uint8_t* dst = valueDst + (mPastLength + i) * bytesPerSeq;
|
|
float block[TQ4_BLOCK_SIZE];
|
|
for (int b = 0; b < mHeadDim / blockSize; b++) {
|
|
for (int k = 0; k < blockSize; k++) {
|
|
block[k] = (float)loadValue(i, channelBase + b * blockSize + k);
|
|
}
|
|
if (mValueQuantMode == KVQuantMode::TQ3) {
|
|
tq3_quantize_block(dst + b * bytesPerBlock, block);
|
|
} else {
|
|
tq4_quantize_block(dst + b * bytesPerBlock, block);
|
|
}
|
|
}
|
|
}
|
|
} else if (mValueQuantMode == KVQuantMode::Int8) {
|
|
int8_t* valueDst = reinterpret_cast<int8_t*>(addrOfValue(kvHead));
|
|
float* valueSum = reinterpret_cast<float*>(addrOfValueSum(kvHead));
|
|
|
|
int32_t params[] = {mKvNumHead, seqLen,
|
|
mHeadDim, mConfig.mBlockNum,
|
|
mMaxLength, lP8,
|
|
hP8, mPastLength,
|
|
kvHead, (int32_t)mFlashAttentionUpperKv};
|
|
if (!valueC4) {
|
|
mQuantValueFunc(valueDst, value->host<float>(), valueSum, params);
|
|
} else {
|
|
auto weightStride2 = lP8 * hP8;
|
|
auto weightStride1 = UP_DIV((int32_t)mFlashAttentionUpperKv, lP8) * weightStride2;
|
|
auto packedStride1 = (int)(weightStride1 + 2 * hP8 * sizeof(float));
|
|
auto packedStride0 = UP_DIV(mHeadDim, hP8) * packedStride1;
|
|
|
|
if (mPastLength == 0) {
|
|
for (int d = 0; d < mHeadDim; ++d) {
|
|
float* scalePtr = (float*)(valueDst + (d / hP8) * packedStride1 + weightStride1) + (d % hP8);
|
|
float* biasPtr = scalePtr + hP8;
|
|
|
|
float dMax = (float)loadValue(0, channelBase + d);
|
|
float dMin = dMax;
|
|
for (int s = 0; s < seqLen; ++s) {
|
|
float data = (float)loadValue(s, channelBase + d);
|
|
dMax = ALIMAX(dMax, data);
|
|
dMin = ALIMIN(dMin, data);
|
|
}
|
|
|
|
float range = dMax - dMin;
|
|
if (range < 1e-6f) {
|
|
scalePtr[0] = 0.0f;
|
|
biasPtr[0] = dMax;
|
|
} else {
|
|
float scale = range / 255.0f;
|
|
float bias = range / 255.0f * 128.0f + dMin;
|
|
scalePtr[0] = scale;
|
|
biasPtr[0] = bias;
|
|
}
|
|
}
|
|
}
|
|
|
|
// FA value int8 keeps one scale/bias per channel and copies it to each block. Decode within an existing
|
|
// block intentionally reuses that scale; updating it requires re-quantizing cached values and valueSum.
|
|
if (mPastLength == 0 || (mPastLength % mFlashAttentionUpperKv) == 0) {
|
|
int32_t d0 = UP_DIV(mMaxLength, (int32_t)mFlashAttentionUpperKv);
|
|
int32_t d1 = UP_DIV(mHeadDim, hP8);
|
|
for (int k = 0; k < d0; ++k) {
|
|
for (int r = 0; r < d1; ++r) {
|
|
float* scalePtr = (float*)(valueDst + k * packedStride0 + r * packedStride1 + weightStride1);
|
|
float* biasPtr = scalePtr + hP8;
|
|
memcpy(scalePtr, valueDst + r * packedStride1 + weightStride1, hP8 * sizeof(float));
|
|
memcpy(biasPtr, valueDst + r * packedStride1 + weightStride1 + hP8 * sizeof(float),
|
|
hP8 * sizeof(float));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int d = 0; d < mHeadDim; ++d) {
|
|
int idxBase = (d / hP8) * packedStride1 + (d % hP8) * lP8;
|
|
int8_t* dstBase = valueDst + idxBase;
|
|
float* scaleBase = (float*)(valueDst + (d / hP8) * packedStride1 + weightStride1) + (d % hP8);
|
|
float* biasBase = scaleBase + hP8;
|
|
float* sumBase = valueSum + (d / hP8) * hP8 + (d % hP8);
|
|
|
|
float qscale = scaleBase[0] < 1e-6f ? 0.0f : 1.0f / scaleBase[0];
|
|
float qbias = scaleBase[0] < 1e-6f ? 0.0f : (-biasBase[0] / scaleBase[0]);
|
|
for (int s = 0; s < seqLen; ++s) {
|
|
int kvSeqIndx = s + mPastLength;
|
|
int idxInner = (kvSeqIndx / (int32_t)mFlashAttentionUpperKv) * packedStride0 +
|
|
(kvSeqIndx % (int32_t)mFlashAttentionUpperKv) / lP8 * weightStride2 +
|
|
(kvSeqIndx % (int32_t)mFlashAttentionUpperKv) % lP8;
|
|
float xf = (float)loadValue(s, channelBase + d);
|
|
int8_t xq = ALIMAX(ALIMIN(127, static_cast<int32_t>(roundf(xf * qscale + qbias))), -128);
|
|
dstBase[idxInner] = xq;
|
|
|
|
int idxSum = (kvSeqIndx / (int32_t)mFlashAttentionUpperKv) * ROUND_UP(mHeadDim, hP8);
|
|
sumBase[idxSum] += ((float)xq * scaleBase[0] + biasBase[0]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// [mHeadDim/hP, mMaxLength/lP, hP, lP]
|
|
auto stride0 = ROUND_UP(mMaxLength, lP) * hP;
|
|
auto stride1 = hP * lP;
|
|
|
|
auto weightStride2 = lP * hP;
|
|
auto weightStride1 = UP_DIV((int32_t)mFlashAttentionUpperKv, lP) * weightStride2;
|
|
auto weightStride0 = weightStride1 * UP_DIV(mHeadDim, hP);
|
|
|
|
T* value_dst = reinterpret_cast<T*>(addrOfValue(kvHead));
|
|
for (int i = 0; i < seqLen; i++) {
|
|
// int seqLenOut = (mPastLength + i) / lP;
|
|
// int seqLenIn = (mPastLength + i) % lP;
|
|
|
|
int kvSeqIndx = mPastLength + i;
|
|
int idxInner = (kvSeqIndx / (int32_t)mFlashAttentionUpperKv) * weightStride0 +
|
|
(kvSeqIndx % (int32_t)mFlashAttentionUpperKv) / lP * weightStride2 +
|
|
(kvSeqIndx % (int32_t)mFlashAttentionUpperKv) % lP;
|
|
for (int j = 0; j < mHeadDim; j++) {
|
|
int idxBase = (j / hP) * weightStride1 + (j % hP) * lP;
|
|
int out_index = j / hP;
|
|
int in_index = j % hP;
|
|
// value_dst[out_index * stride0 + seqLenOut * stride1 + in_index * lP + seqLenIn] = value_src[j];
|
|
value_dst[idxBase + idxInner] = loadValue(i, channelBase + j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t CPUKVCacheManager::keyIndex(int seq, int dim) const {
|
|
return (seq / hP) * ROUND_UP(mHeadDim, lP) * hP + (dim / lP) * hP * lP + (seq % hP) * lP + (dim % lP);
|
|
}
|
|
|
|
size_t CPUKVCacheManager::valueIndex(int seq, int dim) const {
|
|
auto stride1 = UP_DIV((int32_t)mFlashAttentionUpperKv, lP) * hP * lP;
|
|
auto stride0 = stride1 * UP_DIV(mHeadDim, hP);
|
|
auto seqInBlock = seq % (int32_t)mFlashAttentionUpperKv;
|
|
return (seq / (int32_t)mFlashAttentionUpperKv) * stride0 +
|
|
(dim / hP) * stride1 +
|
|
(seqInBlock / lP) * hP * lP +
|
|
(dim % hP) * lP +
|
|
(seqInBlock % lP);
|
|
}
|
|
|
|
template <typename T>
|
|
void CPUKVCacheManager::moveKV(int src, int dst, int size) {
|
|
for (int h = 0; h < mKvNumHead; ++h) {
|
|
auto kPtr = reinterpret_cast<T*>(addrOfKey(h));
|
|
auto vPtr = reinterpret_cast<T*>(addrOfValue(h));
|
|
for (int i = 0; i < size; i++) {
|
|
for (int j = 0; j < mHeadDim; j++) {
|
|
kPtr[keyIndex(dst + i, j)] = kPtr[keyIndex(src + i, j)];
|
|
vPtr[valueIndex(dst + i, j)] = vPtr[valueIndex(src + i, j)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPUKVCacheManager::onUpdateKV(const Tensor* key, const Tensor* value, int add) {
|
|
auto core = static_cast<CPUBackend*>(mBackend)->functions();
|
|
int seq_len = add;
|
|
auto divPart = UP_DIV(mKvNumHead, 1);
|
|
MNN_CONCURRENCY_BEGIN(tId, 1) {
|
|
auto remainPart = mKvNumHead - tId * divPart;
|
|
if (remainPart > 0) {
|
|
remainPart = ALIMIN(divPart, remainPart);
|
|
int startIdx = tId * divPart;
|
|
int endIdx = startIdx + remainPart;
|
|
for (int h = startIdx; h < endIdx; ++h) {
|
|
if (mBytes == 2) {
|
|
ProcessKey<FLOAT16_T>(key, seq_len, h);
|
|
ProcessValue<FLOAT16_T>(value, seq_len, h);
|
|
} else {
|
|
ProcessKey<float>(key, seq_len, h);
|
|
ProcessValue<float>(value, seq_len, h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
MNN_CONCURRENCY_END();
|
|
mPastLength += seq_len;
|
|
}
|
|
|
|
} // namespace MNN
|
|
|
|
#endif // MNN_SUPPORT_TRANSFORMER_FUSE
|