Y表示两功能不冲突,N表示两功能冲突。
控制可计算batch |
全量/增量分离 |
高精度 |
clamp缩放 |
压缩mask |
kv-bypass |
logN缩放 |
qkv全量化 |
BNSD维度输入 |
kv tensorlist格式输入 |
MLA合并输入kvcache功能 |
SWA |
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
控制可计算batch |
- |
Y |
Y |
Y |
Y |
Y |
Y |
N |
Y |
Y |
N |
N |
全量/增量分离 |
- |
- |
Y |
Y |
Y |
Y |
Y |
N |
Y |
Y |
N |
Y |
高精度 |
- |
- |
- |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
N |
clamp缩放 |
- |
- |
- |
- |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
N |
压缩mask |
- |
- |
- |
- |
- |
Y |
Y |
Y |
Y |
Y |
N |
Y |
kv-bypass |
- |
- |
- |
- |
- |
- |
Y |
N |
Y |
Y |
N |
Y |
logN缩放 |
- |
- |
- |
- |
- |
- |
- |
N |
N |
N |
N |
N |
qkv全量化 |
- |
- |
- |
- |
- |
- |
- |
- |
N |
N |
Y |
N |
BNSD维度输入 |
- |
- |
- |
- |
- |
- |
- |
- |
- |
N |
N |
N |
kv tensorlist格式输入 |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
N |
N |
MLA合并输入kvcache功能 |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
N |
SWA |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
指定某几个batch参与attention计算。
参数“batchRunStatusEnable”置为true,并传入batchRunStatus作为输入tensor。
batchRunStatus为0,1组成的tensor。0代表该位置的batch不参与计算,1代表参与计算。
当模型接入paged attention时,全量阶段选用SelfAttention且“calcType”置为PA_ENCODER,增量阶段选用PagedAttention。而当模型使用传统flash attention时,增量与全量阶段都使用SelfAttention;此时为提高计算效率,可在全量与增量阶段选用不同的calcType。
全量阶段:参数“calcType”置为ENCODER。
增量阶段:参数“calcType”置为DECODER。
无。
在进行attention计算时,Q∙KT的结果有可能溢出float16,导致算子输出tensor中间出现NAN值;此时可开启此功能,算子内部使用float32承载中间结果。
参数“kernelType”置为KERNELTYPE_HIGH_PRECISION。
开启此功能时,传入的mask中需把-inf换成1。
高精度功能只在
相当于对传入的q做torch.clamp。
参数“clampType”置为CLAMP_TYPE_MIN_MAX。
传入参数“clampMin”与参数“clampMax”以给出clamp的上下界。
不支持
又名mask-free。
在长序列场景下,由于seqLen较大,需要的mask的大小也会变大。此时可开启此功能,传入压缩后的mask,以减小显存占用。
参数“isTriuMask”置为1。
alibi压缩mask场景需传入slopes。
alibi压缩mask只有“calcType”置为PA_ENCODER时生效。
MASK_TYPE_ALIBI_COMPRESS_LEFT_ALIGN仅支持
不同压缩mask的构造方法:
对应maskType为MASK_TYPE_NORM_COMPRESS。
mask为128 * 128的倒三角,其中:
alibi mask可以拆解为
其中,alibi coefficient为每个head各不相同的系数,triangularMask代表倒三角mask。压缩mask的场景下,输入tensor中的mask即为压缩后的alibi bias,slopes即为alibi coefficient。针对alibi bias的压缩有如下三种情况:
对应“maskType”为MASK_TYPE_ALIBI_COMPRESS。
如下图所示,为512 * 512的压缩前的alibi bias。
对应的压缩后的256 * 256的压缩后的alibi bias如下图所示。
对应“maskType”为MASK_TYPE_ALIBI_COMPRESS_SQRT。
对应“maskType”为MASK_TYPE_ALIBI_COMPRESS_LEFT_ALIGN。
压缩前的alibi bias如下图所示。
压缩后的alibi bias如下图所示。
注意:左对齐的压缩mask只支持
在某些场景下,客户想自行管理kvcache的过程,不想使用加速库做把kv刷新到kvcache的动作。
参数“kvCacheCfg”置为K_BYPASS_V_BYPASS。同时输入tensor不传k, v。
此功能只有“calcType”为非PA_ENCODER时生效。
在一般的情况下,上文计算公式中的Zoom函数,其缩放系数由参数qkScale给出。当用户想要使用logN形式的缩放函数时,可开启此功能。
参数“scaleType”置为SCALE_TYPE_LOGN。
传入logN作为输入tensor。
“kernelType”置为KERNELTYPE_HIGH_PRECISION。
支持量化好的q, k, v传入,降低显存占用。
参数“quantType”置为TYPE_QUANT_QKV_OFFLINE或TYPE_QUANT_QKV_ONLINE, 分别为离线量化与在线量化。
在线全量化需要添加四个输入tensor,分别是qkDescale、qkOffset、vpvDescale、vpvOffset,当采用离线量化时,需要在在线全量化新增的输入tensor的基础上额外传入pScale作为输入tensor。
若干输入tensor需要传入指定的维度,如下表所示:
参数 |
维度 |
数据类型 |
格式 |
npu or cpu |
描述 |
---|---|---|---|---|---|
query |
[nTokens, head_num, head_size] |
int8 |
ND |
npu |
query矩阵。 |
key |
[nTokens, head_num, head_size] |
int8 |
ND |
npu |
key矩阵。 |
value |
[nTokens, head_num, head_size] |
int8 |
ND |
npu |
value矩阵。 |
mask |
float16/bf16 |
ND/NZ |
npu |
四种shape分别对应: 1.所有batch相同,方阵。 2. batch不同时的方阵。 3. batch不同时的向量。 4. alibi场景。 当maskType为undefined时不传此tensor。 |
|
seqLen |
[batch] |
int32 |
ND |
cpu |
等于1时,为增量或全量;大于1时,为全量。 |
slopes |
[head_num] |
|
ND |
npu |
当maskType为alibi压缩,且mask为[256,256]时需传入此tensor,为alibi mask每个head的系数。 |
qkDescale |
[head_num] |
float |
ND |
npu |
为Q*K^T的反量化scale参数。 |
qkOffset |
[head_num] |
int32 |
ND |
npu |
作为Q*K^T的反量化offset参数。预留tensor,需传任意非空tensor,实际暂未使用。 |
vpvDescale |
[head_num] |
float |
ND |
npu |
quantType=2时,为P*V的反量化scale参数;quantType=3时,为V的反量化scale参数。 |
vpvOffset |
[head_num] |
int32 |
ND |
npu |
仅全量化场景传此tensor(即quantType=2或3)。quantType=2时,为P*V的反量化offset参数;quantType=3时,为V的反量化offset参数。 预留tensor,需传任意非空tensor,实际暂未使用。 |
pScale |
[head_num] |
float |
ND |
npu |
P的离线量化scale参数,当开启离线全量化时需要传此tensor(即quantType=2)。当开启在线全量化时不传此tensor(即quantType=3)。 |
output |
[nTokens, head_num, head_size] |
float16/bf16 |
ND |
npu |
输出。 |
其中pScale仅在离线量化场景传入,在线量化场景则不传此输入tensor。
使用全量化时需要指定输出tensor的数据类型,具体为使用参数outDataType,该参数只能是ACL_FLOAT16或ACL_BF16。
“calctype”须为PA_ENCODER。
不支持
一般的,传入SelfAttention算子的q,k,v的维度为[batch, seqLen, head_num, head_dim],即[b, s, n, d],或者是它合轴后的变种。在某些场景下,传入[b, n, s, d]性能更好。
参数“inputLayout”置为TYPE_BNSD。
输入tensor的维度如下:
输入tensor |
维度 |
---|---|
query |
[batch, head_num, seq_len, head_size] |
cacheK |
[layer, batch, head_num, seq_len, head_size] |
cacheV |
[layer, batch, head_num, seq_len, head_size] |
输入tensor |
维度 |
---|---|
query |
[batch, head_num, seq_len, head_size] |
cacheK |
[layer, batch*head_num, head_size / 16, kv_max_seq, 16] |
cacheV |
[layer, batch*head_num, head_size / 16, kv_max_seq, 16] |
支持以tensorlist,即二级指针结构的形式而非整个tensor作为cacheK与cacheV的输入。
如何构造该场景中的variantPack:
atb Tensor定义:
struct Tensor { //! \brief Tensor描述信息 TensorDesc desc; //! \brief TensorNPU内存地址。 void *deviceData = nullptr; //! \brief TensorCPU内存地址。 void *hostData = nullptr; //! \brief “deviceData”或“hostData”指向内容的内存大小。 uint64_t dataSize = 0; };
variantPack中的输入tensors,在需要使能tensor list时,将对应tensor的deviceData置为nullptr,将hostData指向构造的tensorList即可。
此功能只在开启kv-bypass功能(即参数“kvcacheCfg”置为K_BYPASS_V_BYPASS),且“calcType”为DECODER时生效。
过去attention算子会将k和v存储在显存中,随着模型越来越大,输入序列越来越长,kvcache也越来越大,对显存的容量造成很大压力,造成性能上的瓶颈。因此Multi-Head Latent Attention提出将kvcache压缩成一个一个较小的向量,代替原来的kvcache输入进fa算子进行计算。
Mistral 7B,Attention部分在GQA(Grouped-query attention)的基础上,叠加了SWA(Sliding window attention)的优化,可以进一步提高推理速度,并降低显存。
当前FA算子在进行推理时采用的是取所有KV进行计算,可以应用SWA提升推理速度,越远距离的信息,对当前位置的重要性越低,对于距离超过窗口大小的两个token不参与注意力分数计算。
通过设计mask实现n>m或m-n>window_len(n为kv_id, m为q_id)时,注意力分数跳过计算。如图7,若window_size = 3, “The”将不会计算与“the”之间的注意力分数。
Decoder支持仅长度为window_len的最新KV历史参与计算。
KV Cache优化:比如窗口大小=4,则当第5个token需要缓存,直接替换掉第1个token,这样就可以保持kv缓存有一个最大值(为窗口大小),而不会无限增长。
当windowSize >= seqlen时,mask和不开启SWA时一样,为上三角,否则mask生成可参考如下:
以 max(seqLen)为5,windowSize为3为例。
python代码示例:
1 2 3 4 |
swa_mask = np.ones(shape=[maxseq,maxseq]) * -65536.0 triu_mask = np.triu(swa_mask, 1) tril_mask = np.tril(swa_mask, -window_size) swa_mask = triu_mask + tril_mask |
C++代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
static constexpr uint32_t SWA_MASK_SIZE = maxSeq; std::vector<float> create_attention_mask(uint32_t windowSize, uint32_t embeddim) { std::vector<float> attention_mask(SWA_MASK_SIZE * SWA_MASK_SIZE, -65536.0); for (uint32_t i = 0; i < SWA_MASK_SIZE; ++i) { uint32_t offset = i >= windowSize ? (i - windowSize + 1) : 0; for (uint32_t j = offset; j < i + 1; ++j) { attention_mask[i * SWA_COMPRESS_MASK_SIZE + j] = 0.0; } } return attention_mask; } |
以 max(seqLen)为5,windowSize为3为例。
python代码示例:
1 2 3 4 |
swa_mask = np.ones(shape=[maxseq,maxseq]) * 1.0 triu_mask = np.triu(swa_mask, 1) tril_mask = np.tril(swa_mask, -window_size) swa_mask = triu_mask + tril_mask |
C++代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
static constexpr uint32_t SWA_MASK_SIZE = maxSeq; std::vector<float> create_attention_mask(uint32_t windowSize, uint32_t embeddim) { std::vector<float> attention_mask(SWA_MASK_SIZE * SWA_MASK_SIZE, 1.0); for (uint32_t i = 0; i < SWA_MASK_SIZE; ++i) { uint32_t offset = i >= windowSize ? (i - windowSize + 1) : 0; for (uint32_t j = offset; j < i + 1; ++j) { attention_mask[i * SWA_COMPRESS_MASK_SIZE + j] = 0.0; } } return attention_mask; } |
python代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
swa_mask = np.ones(shape=(1, 512, 512)) * -65536.0 pp_n = 128 if head_size <= 128 else 64 # head_size为每个注意力头的嵌入向量的大小 if window_size <= pp_n * 3: true_size = window_size else: if window_size % pp_n == 0: true_size = pp_n * 3 else: true_size = pp_n * 2 + window_size % pp_n triu_mask = np.triu(swa_mask, 1) tril_mask = np.tril(swa_mask, -true_size) swa_mask = triu_mask + tril_mask |
C++代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static constexpr uint32_t SWA_COMPRESS_MASK_SIZE = 512; std::vector<float> create_attention_mask(uint32_t windowSize, uint32_t embeddim) { std::vector<float> attention_mask(SWA_COMPRESS_MASK_SIZE * SWA_COMPRESS_MASK_SIZE, -65536.0); uint32_t blockSize = embeddim > 128 ? (16384 / embeddim / 16 * 16) : 128; uint32_t compressWindow = windowSize > 3 * blockSize ? (2 * blockSize + windowSize % blockSize) : windowSize; for (uint32_t i = 0; i < SWA_COMPRESS_MASK_SIZE; ++i) { uint32_t offset = i >= compressWindow ? (i - compressWindow + 1) : 0; for (uint32_t j = offset; j < i + 1; ++j) { attention_mask[i * SWA_COMPRESS_MASK_SIZE + j] = 0.0; } } return attention_mask; } |
python代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
swa_mask = np.ones(shape=(1, 512, 512)) * 1 pp_n = 128 if head_size <= 128 else 64 # head_size为每个注意力头的嵌入向量的大小 if window_size <= pp_n * 3: true_size = window_size else: if window_size % pp_n == 0: true_size = pp_n * 3 else: true_size = pp_n * 2 + window_size % pp_n triu_mask = np.triu(swa_mask, 1) tril_mask = np.tril(swa_mask, -true_size) swa_mask = triu_mask + tril_mask |
C++代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static constexpr uint32_t SWA_COMPRESS_MASK_SIZE = 512; std::vector<float> create_attention_mask(uint32_t windowSize, uint32_t embeddim) { std::vector<float> attention_mask(SWA_COMPRESS_MASK_SIZE * SWA_COMPRESS_MASK_SIZE, 1.0); uint32_t blockSize = embeddim > 128 ? (16384 / embeddim / 16 * 16) : 128; uint32_t compressWindow = windowSize > 3 * blockSize ? (2 * blockSize + windowSize % blockSize) : windowSize; for (uint32_t i = 0; i < SWA_COMPRESS_MASK_SIZE; ++i) { uint32_t offset = i >= compressWindow ? (i - compressWindow + 1) : 0; for (uint32_t j = offset; j < i + 1; ++j) { attention_mask[i * SWA_COMPRESS_MASK_SIZE + j] = 0.0; } } return attention_mask; } |