过渡到 cuTENSOR 2.x

cuTENSOR 2.x 比其前代版本带来了显著的增强和优势(参见发行说明)。本指南概述了新的和改进的 API 的结构和主要功能,概述了旧 API 和新 API 之间的主要区别,并演示了如何将现有代码迁移到新 API。

概述

cuTENSOR 2.x 旨在为您的项目提供更高的性能、更多功能和更简单的集成。我们为所有支持的操作引入了多阶段 API;图 1 说明了关键阶段(可选阶段为灰色):创建操作描述符、限制内核空间、计划(即,选择内核)和执行。

Illustraction of the API of cuTENSOR 2.x

图 1:cuTENSOR 2.x 引入的多阶段 API

这种结构使我们能够通过将所有支持的操作(缩并、归约、逐元素和置换)整合到单个 cutensorOperationDescriptor_t 对象中,并共享相同的步骤来实现执行,从而减少我们的 API 占用空间。此外,新的 API 支持查询计划以确定操作所需的精确工作区大小(通过 cutensorPlanGetAttribute()),从而将应用程序的内存需求减少到最低限度。

一览差异

下面列出了旧 API 和新 API 之间的主要差异

示例 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() 的示例,因为它与上面的示例类似。