过渡到 cuTENSOR 2.x¶
cuTENSOR 2.x 比其前代版本带来了显著的增强和优势(参见发行说明)。本指南概述了新的和改进的 API 的结构和主要功能,概述了旧 API 和新 API 之间的主要区别,并演示了如何将现有代码迁移到新 API。
概述¶
cuTENSOR 2.x 旨在为您的项目提供更高的性能、更多功能和更简单的集成。我们为所有支持的操作引入了多阶段 API;图 1 说明了关键阶段(可选阶段为灰色):创建操作描述符、限制内核空间、计划(即,选择内核)和执行。
图 1:cuTENSOR 2.x 引入的多阶段 API¶
这种结构使我们能够通过将所有支持的操作(缩并、归约、逐元素和置换)整合到单个 cutensorOperationDescriptor_t 对象中,并共享相同的步骤来实现执行,从而减少我们的 API 占用空间。此外,新的 API 支持查询计划以确定操作所需的精确工作区大小(通过 cutensorPlanGetAttribute()),从而将应用程序的内存需求减少到最低限度。
一览差异¶
下面列出了旧 API 和新 API 之间的主要差异
cudaDataType
-> cutensorDataType_t (例如,CUDA_R_32F
->CUTENSOR_R_32F
)cutensorComputeType_t
-> cutensorComputeDescriptor_t (例如,CUTENSOR_COMPUTE_32F
->CUTENSOR_COMPUTE_DESC_32F
)先前已弃用的计算类型
CUTENSOR_R_MIN...
和CUTENSOR_C_MIN...
已被移除。
cutensorInitTensorDescriptor
-> cutensorCreateTensorDescriptor()cutensorContractionDescriptor_t
-> cutensorOperationDescriptor_tcutensorInitContractionDescriptor
-> cutensorCreateContraction()cutensorInitContractionFind
-> cutensorCreatePlanPreference()cutensorContractionGetWorkspaceSize
-> cutensorEstimateWorkspaceSize()cutensorInitContractionPlan
-> cutensorCreatePlan()cutensorContraction
-> cutensorContract()cutensorElementwiseBinary
-> cutensorElementwiseBinaryExecute()cutensorElementwiseTrinary
-> cutensorElementwiseTrinaryExecute()cutensorPermutation
-> cutensorPermute()cutensorReduction
-> cutensorReduce()“Init” 函数已变为 “Create/Destroy” 对
cutensorInit
-> cutensorCreate() 和 cutensorDestroy()cutensorInitTensorDescriptor
-> cutensorCreateTensorDescriptor() 和 cutensorDestroyTensorDescriptor()cutensorInitContractionDescriptor
-> cutensorCreateContraction() 和 cutensorDestroyOperationDescriptor()cutensorInitContractionFind
-> cutensorCreatePlanPreference() 和 cutensorDestroyPlanPreference()cutensorInitContractionPlan
-> cutensorCreatePlan() 和 cutensorDestroyPlan()
cutensorOperator_t(例如,
CUTENSOR_OP_IDENTITY
)不再是cutensorTensorDescriptor_t
的一部分,而是已移至每个操作的创建中(例如,cutensorCreateContraction())同样,对齐不再是每个操作的一部分,而是已移至 cutensorCreateTensorDescriptor(),其目的是使
cutensorTensorDescriptor_t
对象描述与张量在内存中的物理布局相关的所有方面。
示例 1:将缩并操作从 1.x 迁移到 2.x¶
数据类型名称必须调整,因为它们的前缀已从 CUDA_
更改为 CUTENSOR_
(例如,CUDA_R_32F
变为 CUTENSOR_R_32F
)
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cudaDataType_t typeA = CUDA_R_32F;
cudaDataType_t typeB = CUDA_R_32F;
cudaDataType_t typeC = CUDA_R_32F;
|
cutensorDataType_t typeA = CUTENSOR_R_32F;
cutensorDataType_t typeB = CUTENSOR_R_32F;
cutensorDataType_t typeC = CUTENSOR_R_32F;
|
由于我们的初始化函数已被 Create/Destroy 对替换,因此不再需要使用指向 cuTENSOR 对象的指针;用户现在可以直接使用 cutensorCreate() 分配结构。由 cutensorCreate() 分配的任何内存都可以使用 cutensorDestroy() 安全释放。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorHandle_t* handle;
|
cutensorHandle_t handle;
|
cutensorInitTensorDescriptor
已被 cutensorCreateTensorDescriptor() 替换。这不仅仅是名称更改;最后一个参数已更改。具体来说,用户不再指定 cutensorOperator_t(现在是操作描述符的一部分),而是指定张量指针的对齐方式(以字节为单位)。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorTensorDescriptor_t descA;
cutensorInitTensorDescriptor(handle,
&descA,
nmodeA,
extentA.data(),
NULL,/*stride*/
typeA, CUTENSOR_OP_IDENTITY);
|
cutensorTensorDescriptor_t descA;
cutensorCreateTensorDescriptor(handle,
&descA,
nmodeA,
extentA.data(),
NULL,/*stride*/
typeA, kAlignment);
|
在新的 API 中,缩并由使用 cutensorCreateContraction() 初始化的 cutensorOperationDescriptor_t 表示。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorContractionDescriptor_t desc;
cutensorInitContractionDescriptor(handle,
&desc,
&descA, modeA.data(), alignmentRequirementA,
&descB, modeB.data(), alignmentRequirementB,
&descC, modeC.data(), alignmentRequirementC,
&descC, modeC.data(), alignmentRequirementC,
typeCompute);
|
cutensorOperationDescriptor_t desc;
cutensorCreateContraction(handle,
&desc,
descA, modeA.data(), CUTENSOR_OP_IDENTITY,
descB, modeB.data(), CUTENSOR_OP_IDENTITY,
descC, modeC.data(), CUTENSOR_OP_IDENTITY,
descC, modeC.data(),
descCompute);
|
cutensorContractionFind_t
已重命名为 cutensorPlanPreference_t,以表明它不仅限于缩并操作,还适用于所有操作。本质上,其功能保持不变:它配置 cutensorCreatePlan() 将如何运行。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorContractionFind_t find;
cutensorInitContractionFind(handle,
&find,
CUTENSOR_ALGO_DEFAULT);
|
cutensorPlanPreference_t planPref;
cutensorCreatePlanPreference(handle,
&planPref,
CUTENSOR_ALGO_DEFAULT,
CUTENSOR_JIT_MODE_NONE);
|
cutensorContractionGetWorkspaceSize
已重命名为 cutensorEstimateWorkspaceSize()。cutensorWorksizePreference_t 有三个值可用;请注意,CUTENSOR_WORKSPACE_RECOMMENDED
已重命名为 CUTENSOR_WORKSPACE_DEFAULT
。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
uint64_t worksize = 0;
cutensorContractionGetWorkspaceSize(handle,
&desc,
&find,
CUTENSOR_WORKSPACE_RECOMMENDED,
&worksize);
|
uint64_t workspaceSizeEstimate = 0;
cutensorEstimateWorkspaceSize(handle,
desc,
planPref,
CUTENSOR_WORKSPACE_DEFAULT,
&workspaceSizeEstimate);
|
cutensorInitContractionPlan
已重命名为 cutensorCreatePlan()。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorContractionPlan_t plan;
cutensorInitContractionPlan(handle,
&plan,
&desc,
&find,
worksize);
|
cutensorPlan_t plan;
cutensorCreatePlan(handle,
&plan,
desc,
planPref,
workspaceSizeEstimate);
|
计划完成后,用户可以查询创建的计划以查找执行操作所需的实际工作区。 actualWorkspaceSize
保证小于或等于上面用于创建计划的 workspaceSizeEstimate
。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
void *work = nullptr;
if (worksize > 0)
if (cudaSuccess != cudaMalloc(&work, worksize))
{
work = nullptr;
worksize = 0;
}
|
uint64_t actualWorkspaceSize = 0;
cutensorPlanGetAttribute(handle,
plan,
CUTENSOR_PLAN_REQUIRED_WORKSPACE,
&actualWorkspaceSize,
sizeof(actualWorkspaceSize));
void *work = nullptr;
if (actualWorkspaceSize > 0)
HANDLE_CUDA_ERROR(cudaMalloc(&work, actualWorkspaceSize));
|
cutensorContraction
已重命名为 cutensorContract()。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorContraction(handle,
&plan,
(void*) &alpha, A_d, B_d,
(void*) &beta, C_d, C_d,
work, worksize, 0 /* stream */);
|
cudaStream_t stream;
HANDLE_CUDA_ERROR(cudaStreamCreate(&stream));
cutensorContract(handle,
plan,
(void*) &alpha, A_d, B_d,
(void*) &beta, C_d, C_d,
work, actualWorkspaceSize, stream);
|
示例 2:将归约操作从 1.x 迁移到 2.x¶
归约操作(以及置换和逐元素操作,请参阅 示例 3:将置换/逐元素操作从 1.x 迁移到 2.x)以前仅通过执行函数(即,单阶段 API)公开;相反,在 cuTENSOR 2.x 中,归约操作使用与所有其他操作相同的多阶段 API。使用新 API 计算归约操作所需的步骤与 示例 1:将缩并操作从 1.x 迁移到 2.x 非常相似,如下所示。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
uint64_t worksize = 0;
cutensorReductionGetWorkspaceSize(handle,
A_d, &descA, modeA.data(),
C_d, &descC, modeC.data(),
C_d, &descC, modeC.data(),
opReduce, typeCompute, &worksize);
void *work = nullptr;
if (worksize > 0)
{
cudaMalloc(&work, worksize);
}
|
const cutensorOperator_t opReduce = CUTENSOR_OP_ADD;
cutensorOperationDescriptor_t desc;
cutensorCreateReduction(handle, &desc,
descA, modeA.data(), CUTENSOR_OP_IDENTITY,
descC, modeC.data(), CUTENSOR_OP_IDENTITY,
descC, modeC.data(), opReduce,
descCompute);
const cutensorAlgo_t algo = CUTENSOR_ALGO_DEFAULT;
cutensorPlanPreference_t planPref;
cutensorCreatePlanPreference(handle,
&planPref,
algo,
CUTENSOR_JIT_MODE_NONE);
uint64_t workspaceSizeEstimate = 0;
const cutensorWorksizePreference_t workspacePref = CUTENSOR_WORKSPACE_DEFAULT;
cutensorEstimateWorkspaceSize(handle,
desc,
planPref,
workspacePref,
&workspaceSizeEstimate);
cutensorPlan_t plan;
cutensorCreatePlan(handle,
&plan,
desc,
planPref,
workspaceSizeEstimate);
uint64_t actualWorkspaceSize = 0;
cutensorPlanGetAttribute(handle,
plan,
CUTENSOR_PLAN_REQUIRED_WORKSPACE,
&actualWorkspaceSize,
sizeof(actualWorkspaceSize));
void *work = nullptr;
if (actualWorkspaceSize > 0)
{
cudaMalloc(&work, actualWorkspaceSize);
}
|
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
const cutensorOperator_t opReduce = CUTENSOR_OP_ADD;
cutensorReduction(handle,
(const void*)&alpha, A_d, &descA, modeA.data(),
(const void*)&beta, C_d, &descC, modeC.data(),
C_d, &descC, modeC.data(),
opReduce, typeCompute, work, worksize, 0 /* stream */);
|
cutensorReduce(handle, plan,
(const void*)&alpha, A_d,
(const void*)&beta, C_d,
C_d, work, actualWorkspaceSize, stream);
|
示例 3:将置换/逐元素操作从 1.x 迁移到 2.x¶
在新的 API 中,置换和逐元素操作也使用相同的多阶段 API。下面说明了使用新 API 计算逐元素二元操作所需的步骤。
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorOperationDescriptor_t desc;
cutensorCreateElementwiseBinary(handle,
&desc,
descA, modeA.data(), CUTENSOR_OP_IDENTITY,
descC, modeC.data(), CUTENSOR_OP_IDENTITY,
descC, modeC.data(), CUTENSOR_OP_ADD,
descCompute);
const cutensorAlgo_t algo = CUTENSOR_ALGO_DEFAULT;
cutensorPlanPreference_t planPref;
cutensorCreatePlanPreference(handle,
&planPref,
algo,
CUTENSOR_JIT_MODE_NONE);
cutensorPlan_t plan;
cutensorCreatePlan(handle,
&plan,
desc,
planPref,
0 /* workspaceSizeLimit */);
|
cuTENSOR 1.x |
cuTENSOR 2.x |
---|---|
cutensorElementwiseBinary(handle,
(void*)&alpha, A_d, &descA, modeA.data(),
(void*)&gamma, C_d, &descC, modeC.data(),
C_d, &descC, modeC.data(),
CUTENSOR_OP_ADD, typeCompute, 0 /* stream */);
|
cutensorElementwiseBinaryExecute(handle,
plan,
(void*)&alpha, A_d,
(void*)&gamma, C_d,
C_d, 0 /* stream */));
|
我们省略了关于 cutensorPermute() 的示例,因为它与上面的示例类似。