aclnnAdvanceStep
支持的产品型号
Atlas A2 训练系列产品/Atlas 800I A2 推理产品 。
接口原型
每个算子分为两段式接口,必须先调用“aclnnAdvanceStepGetWorkspaceSize”接口获取入参并根据计算流程计算所需workspace大小,再调用“aclnnAdvanceStep”接口执行计算。
aclnnStatus aclnnAdvanceStepGetWorkspaceSize(aclTensor *inputTokensRef, const aclTensor *sampledTokenIds, aclTensor *inputPositionsRef, aclTensor *seqLensRef, aclTensor *slotMappingRef, const aclTensor *blockTables, int64_t numSeqs, int64_t numQueries, int64_t blockSize, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnAdvanceStep( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)
功能描述
算子功能:
vLLM是一个高性能的LLM推理和服务框架,专注于优化大规模语言模型的推理效率。它的核心特点包括PageAttention和高效内存管理。advcance_step算子的主要作用是推进推理步骤,即在每个生成步骤中更新模型的状态并生成新的inputTokensRef、inputPositionsRef、seqLensRef和slotMappingRef,为vLLM的推理提升效率。
计算公式:
aclnnAdvanceStepGetWorkspaceSize
参数说明:
- inputTokensRef(aclTensor*,计算输入/输出):输入/输出张量,公式中的输出
inputTokensRef
,用于更新vLLM模型中的token值,Device侧的aclTensor,数据类型支持INT64。shape维度支持一维,并且长度与numSeqs一致,不支持空tensor。不支持非连续的Tensor,数据格式支持ND。取值范围是大于0的正整数。 - sampledTokenIds(aclTensor*,计算输入):输入张量,用于储存tokenID,公式中的输入
sampledTokenIds
,Device侧的aclTensor,数据类型支持INT64。shape维度支持二维,并且第一维长度与numQueries一致,第二维长度为1,不支持空tensor。不支持非连续的Tensor,数据格式支持ND。取值范围是大于0的正整数。 - inputPositionsRef(aclTensor*,计算输入/输出):输入/输出张量,公式中的输出
inputPositionsRef
,用于记录token的index,Device侧的aclTensor,数据类型支持INT64。shape维度支持一维,并且长度与numSeqs一致,不支持空tensor。不支持非连续的Tensor,数据格式支持ND。取值范围是大于0的正整数。 - seqLensRef(aclTensor*,计算输入/输出):输入/输出张量,用于记录不同blockIdx下seq的长度,公式中的输入/输出
seqLensRef
,Device侧的aclTensor,数据类型支持INT64。shape维度支持一维,并且长度与numSeqs一致,不支持空tensor。不支持非连续的Tensor,数据格式支持ND。取值范围是大于0的正整数。 - slotMappingRef(aclTensor*,计算输入/输出):输入/输出张量,公式中的输出
slotMappingRef
,用于将token值在序列中的位置映射到物理位置,Device侧的aclTensor,数据类型支持INT64。shape维度支持一维,并且长度与numSeqs一致,不支持空tensor。不支持非连续的Tensor,数据格式支持ND。取值范围是大于0的正整数。 - blockTables(aclTensor*,计算输入):输入张量,用于记录不同blockIdx下block的大小,公式中的输入
blockTables
,Device侧的aclTensor,数据类型支持INT64。shape维度支持二维,并且第一维长度与numSeqs一致,第二维大于。不支持空tensor。不支持非连续的Tensor,数据格式支持ND。取值范围是大于0的正整数。 - numSeqs(int64_t,计算输入):记录输入的seq数量,大小与seqLensRef的长度一致。取值范围是大于0的正整数。numSeqs的值大于输入numQueries的值。
- numQueries(int64_t,计算输入):记录输入的Query的数量,大小与sampledTokenIds第一维的长度一致。取值范围是大于0的正整数。
- blockSize(int64_t,计算输入):每个block的大小,对应公式中的
blockSize
。取值范围是大于0的正整数。 - workspaceSize(uint64_t*,出参):返回用户需要在Device侧申请的workspace大小。
- executor(aclOpExecutor**,出参):返回op执行器,包含了算子计算流程。
- inputTokensRef(aclTensor*,计算输入/输出):输入/输出张量,公式中的输出
返回值:
aclnnStatus:返回状态码,具体参见aclnn返回码。
第一段接口完成入参校验,出现以下场景时报错: 返回161001(ACLNN_ERR_PARAM_NULLPTR): 传入的inputTokensRef、sampledTokenIds、inputPositionsRef、seqLensRef、slotMappingRef、blockTables是空指针。 返回161002(ACLNN_ERR_PARAM_INVALID): inputTokensRef、sampledTokenIds、inputPositionsRef、seqLensRef、slotMappingRef、blockTables的数据类型不在支持的范围之内; 返回561002(aclnnAdvanceStepGetWorkspaceSize failed):1. 输入inputTokensRef、inputPositionsRef、seqLensRef、slotMappingRef、blockTables的shape的第一维长度与numSeqs不一致; 2. 输入sampledTokenIds的shape的第一维长度与numQueries不一致,或者shape的第二维长度不为1。 3. 输入numSeqs的值小于等于输入numQueries的值。
aclnnAdvanceStep
参数说明:
- workspace(void*,入参):在Device侧申请的workspace内存地址。
- workspaceSize(uint64_t,入参):在Device侧申请的workspace大小,由第一段接口aclnnAdvanceStepGetWorkspaceSize获取。
- executor(aclOpExecutor*,入参):op执行器,包含了算子计算流程。
- stream(aclrtStream,入参):指定执行任务的AscendCL Stream流。
返回值:
aclnnStatus:返回状态码,具体参见aclnn返回码。
约束与限制
无。
调用示例
示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例。
#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_advance_step.h"
#define CHECK_RET(cond, return_expr) \
do { \
if (!(cond)) { \
return_expr; \
} \
} while (0)
#define LOG_PRINT(message, ...) \
do { \
printf(message, ##__VA_ARGS__); \
} while (0)
int64_t GetShapeSize(const std::vector<int64_t>& shape) {
int64_t shapeSize = 1;
for (auto i : shape) {
shapeSize *= i;
}
return shapeSize;
}
void PrintOutResult(std::vector<int64_t> &shape, void** deviceAddr) {
auto size = GetShapeSize(shape);
std::vector<int64_t> resultData(size, 0);
auto ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]),
*deviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return);
for (int64_t i = 0; i < size; i++) {
LOG_PRINT("mean result[%ld] is: %ld\n", i, resultData[i]);
}
}
int Init(int64_t deviceId, aclrtStream* stream) {
// 固定写法,AscendCL初始化
auto ret = aclInit(nullptr);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
ret = aclrtSetDevice(deviceId);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
ret = aclrtCreateStream(stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
return 0;
}
template <typename T>
int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
aclDataType dataType, aclTensor** tensor) {
auto size = GetShapeSize(shape) * sizeof(T);
// 调用aclrtMalloc申请device侧内存
auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
// 调用aclrtMemcpy将host侧数据复制到device侧内存上
ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
// 计算连续tensor的strides
std::vector<int64_t> strides(shape.size(), 1);
for (int64_t i = shape.size() - 2; i >= 0; i--) {
strides[i] = shape[i + 1] * strides[i + 1];
}
// 调用aclCreateTensor接口创建aclTensor
*tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
shape.data(), shape.size(), *deviceAddr);
return 0;
}
int main() {
// 1. (固定写法)device/stream初始化,参考AscendCL对外接口列表
// 根据自己的实际device填写deviceId
int32_t deviceId = 0;
aclrtStream stream;
auto ret = Init(deviceId, &stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
// 2. 构造输入与输出,需要根据API的接口自定义构造
std::vector<int64_t> inputShape = {8,1};
std::vector<int64_t> input2Shape = {4,1};
std::vector<int64_t> inputHostData = {0, 1, 2, 3, 4, 5, 6, 7};
std::vector<int64_t> input2HostData = {0, 1, 2, 3};
void* input1DeviceAddr = nullptr;
aclTensor* input1 = nullptr;
void* input2DeviceAddr = nullptr;
aclTensor* input2 = nullptr;
void* input3DeviceAddr = nullptr;
aclTensor* input3 = nullptr;
void* input4DeviceAddr = nullptr;
aclTensor* input4 = nullptr;
void* input5DeviceAddr = nullptr;
aclTensor* input5 = nullptr;
void* input6DeviceAddr = nullptr;
aclTensor* input6 = nullptr;
// 创建input aclTensor
ret = CreateAclTensor(inputHostData, inputShape, &input1DeviceAddr, aclDataType::ACL_INT64, &input1);
CHECK_RET(ret == ACL_SUCCESS, return ret);
ret = CreateAclTensor(input2HostData, input2Shape, &input2DeviceAddr, aclDataType::ACL_INT64, &input2);
CHECK_RET(ret == ACL_SUCCESS, return ret);
ret = CreateAclTensor(inputHostData, inputShape, &input3DeviceAddr, aclDataType::ACL_INT64, &input3);
CHECK_RET(ret == ACL_SUCCESS, return ret);
ret = CreateAclTensor(inputHostData, inputShape, &input4DeviceAddr, aclDataType::ACL_INT64, &input4);
CHECK_RET(ret == ACL_SUCCESS, return ret);
ret = CreateAclTensor(inputHostData, inputShape, &input5DeviceAddr, aclDataType::ACL_INT64, &input5);
CHECK_RET(ret == ACL_SUCCESS, return ret);
ret = CreateAclTensor(inputHostData, inputShape, &input6DeviceAddr, aclDataType::ACL_INT64, &input6);
CHECK_RET(ret == ACL_SUCCESS, return ret);
int64_t numseq = 8;
int64_t numqueries = 4;
int64_t blocksize = 2;
// 3. 调用CANN算子库API,需要修改为具体的Api名称
uint64_t workspaceSize = 16 * 1024 * 1024;
aclOpExecutor* executor;
// 调用aclnnAdvanceStep第一段接口
ret = aclnnAdvanceStepGetWorkspaceSize(
input1,input2,input3,input4,input5,input6,
numseq,numqueries,blocksize,
&workspaceSize,
&executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAdvanceStepGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
// 根据第一段接口计算出的workspaceSize申请device内存
void* workspaceAddr = nullptr;
if (workspaceSize > 0) {
ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
}
// 调用aclnnAdvanceStep第二段接口
ret = aclnnAdvanceStep(
workspaceAddr,
workspaceSize,
executor,
stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAdvanceStep failed. ERROR: %d\n", ret); return ret);
// 4. (固定写法)同步等待任务执行结束
ret = aclrtSynchronizeStream(stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
// 5. 获取输出的值,将device侧内存上的结果复制至host侧,需要根据具体API的接口定义修改
PrintOutResult(inputShape, &input1DeviceAddr);
PrintOutResult(inputShape, &input3DeviceAddr);
PrintOutResult(inputShape, &input4DeviceAddr);
PrintOutResult(inputShape, &input5DeviceAddr);
// 6. 释放aclTensor和aclTensor,需要根据具体API的接口定义修改
aclDestroyTensor(input1);
aclDestroyTensor(input2);
aclDestroyTensor(input3);
aclDestroyTensor(input4);
aclDestroyTensor(input5);
aclDestroyTensor(input6);
// 7.释放device资源,需要根据具体API的接口定义修改
aclrtFree(input1DeviceAddr);
aclrtFree(input2DeviceAddr);
aclrtFree(input3DeviceAddr);
aclrtFree(input4DeviceAddr);
aclrtFree(input5DeviceAddr);
aclrtFree(input6DeviceAddr);
if (workspaceSize > 0) {
aclrtFree(workspaceAddr);
}
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
return 0;
}