0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

NVIDIA nvCOMP中的两个统一接口和使用

星星科技指导员 来源:NVIDIA 作者:Eric Schmidt 2022-04-06 17:02 次阅读

压缩可以在各种用例中提高性能,例如 DL 工作负载、数据库和通用 HPC 。在 GPU 上,压缩可以加速协作工作流的 GPU 间通信。它可以通过在数据存储到全局内存之前压缩数据来增加单个 GPU 可以处理的数据集的大小。它还可以加速 CPU 和 GPU 之间的数据链路。

为了让这些工作流中的任何一个都变得有用,压缩和解压缩必须快速,并且在给定数据集上以足够高的压缩比运行,才能发挥作用。然而,不同算法的压缩比和吞吐量因数据集而异。如果没有大量关于算法和数据统计的专业知识,可能很难选择最佳算法。

NVIDIA NVCOMP 库使您可以在应用程序中结合高性能 GPU 压缩和解压缩。该库提供了一组统一的 API ,允许您快速交换压缩格式,以在数据集上实现最佳性能,而对代码的更改最少。

使用 nvCOMP ,您可以快速、轻松地使用不同的算法进行实验,以找到最适合您的用例的算法。在最近的版本中,我们更新了 nvCOMP 以进一步改进和统一接口。在新发布的 2.2 版本中,我们提供了一个易于使用的、高级的 C ++ API 和一个通用的低级别批量 C API 。在本文中,我们将详细介绍这两个接口。你还可以学习如何有效地使用它们,以及何时应该选择其中一个。

高级 API

高级 API 更易于使用,并抽象了向 GPU 公开并行性的工作。当您必须将连续缓冲区压缩为连续的压缩缓冲区时,它最有用。例如,在通过网络发送缓冲区或将其保存到磁盘之前压缩缓冲区时,这种方法效果很好。

以下示例使用高通量GDeflate压缩格式。GDeflate类似于 deflate ,可以有效地映射到数据并行架构,如 GPU 。如果您对使用的压缩格式没有限制,那么这是一个很好的起点。

高级接口是基于nvcompManagerBase类层次结构的C++ API。每个派生的Manager类都在nvcomp/include中与其关联的头中声明。例如,本文中使用的GDeflateManager在nvcomp/include/gdeflate.hpp中声明。

首先,构建所需的Manager类。每个Manager构造函数都有一组唯一的参数;然而,有一些观点通常是一致的。所有子类都允许使用指定的流 ID 构造用于所有内核和内存传输。您还可以指定要使用的设备 ID 。如果不为这两个参数指定值,则使用默认的流和设备。

另一个常见的输入是未压缩的块大小。这在压缩过程中用于将缓冲区拆分为独立的块进行处理。较大的块大小通常会导致更高的压缩比,但代价是暴露在 GPU 中的并行性会降低。一个好的起始数据块大小是 64KB ,但是可以自由地使用这些值来探索数据集的相关权衡。

Manager类也使用特定于格式的参数构造。您可以查看nvcomp/include中的相关标题,了解Manager类构造函数参数的描述,并了解如何为所选格式构造Manager对象。

const size_t uncomp_chunk_size = 64 * 1024; cudaStream_t stream;
cudaStreamCreate(&stream));
const int gdeflate_algorithm = 0; // Use standard GDeflate
const int device_id = 0; // Use the default device GdeflateManager gdeflate_manager{chunk_size, gdeflate_algorithm, stream, device_id};

nvcompManager需要一个临时的 scratch 工作区来进行压缩和解压缩。根据特定的压缩格式参数以及压缩和解压缩内核的最大占用率,所需的暂存空间大小是固定的。如果对您的用例有意义,您可以在构造后使用set_scratch_buffernvcompManager对象提供一个临时缓冲区。

size_t scratch_buffer_size = gdeflate_manager.get_required_scratch_buffer_size();
uint8_t* scratch_buffer;
cudaMalloc(&scratch_buffer, scratch_buffer_size);
gdeflate_manager.set_scratch_buffer(scratch_buffer);

手动设置暂存缓冲区可能有助于控制用于此分配的内存分配方案。如果您同意默认设置,我们建议跳过此步骤并启用nvcompManager对象来处理分配。

此缓冲区可用于nvcompManager执行的所有压缩和解压缩操作。如果nvcompManager对象分配了暂存缓冲区,则在销毁该对象时会释放该缓冲区。

压缩

现在可以压缩缓冲区了。首先,使用configure_compression API 配置压缩。此异步操作返回CompressionConfig对象。

配置步骤只需要input-uncompressed缓冲区的大小。您必须分配一个 GPU 可访问的内存缓冲区,其大小至少为该大小,以用作压缩例程的结果缓冲区。有了这些信息,可以执行压缩,如下面的代码示例所示:

CompressionConfig comp_config = gdeflate_manager.configure_compression(input_buffer_len); uint8_t* comp_buffer;
cudaMallocAsync(&comp_buffer, comp_config.max_compressed_buffer_size, stream); gdeflate_manager.compress(uncomp_buffer, comp_buffer, comp_config);

您还可以在 GPU 上排队进行其他压缩。

uint8_t* comp_buffer1, comp_buffer2;
CompressionConfig comp_config1 = gdeflate_manager.configure_compression(input_buffer_len1); cudaMallocAsync(&comp_buffer1, comp_config1.max_compressed_buffer_size, stream); gdeflate_manager.compress(uncomp_buffer1, comp_buffer1, comp_config1); CompressionConfig comp_config2 = gdeflate_manager.configure_compression(input_buffer_len2); cudaMallocAsync(&comp_buffer2, comp_config2.max_compressed_buffer_size, stream); gdeflate_manager.compress(uncomp_buffer2, comp_buffer2, comp_config2); cudaStreamSynchronize(stream);

减压

高级接口压缩产生的缓冲区在压缩数据之前包含一个头(图 1 )。此标题包含有关缓冲区如何压缩的信息,因此您可以从压缩的缓冲区构造nvcompManager对象,而不知道它是如何压缩的。这使您可以在不知道缓冲区是如何压缩的情况下对其进行解压缩。

A diagram showing an example nvCOMP HLIF-compressed bufferA diagram showing an example nvCOMP HLIF-compressed buffer

图 1 HLIF 压缩数据格式

为此,请使用nvcompManagerFactory.hpp中声明的create_manager API 。这个同步 API 将压缩的缓冲区以及可选的流和设备 ID 作为输入。

auto decomp_nvcomp_manager = create_manager(comp_buffer, stream);

如果您已经掌握了有关缓冲区压缩方式的信息,那么可以使用前面描述的配置构造一个新的管理器。您还可以重用用于压缩的同一nvcompManager对象来执行解压缩。这些方法的优点是不需要同步流。

给定一个nvcompManager对象和一个压缩的缓冲区,解压的执行与压缩类似,但有几个细微的区别。首先,有两种可能的方式来进行解压缩配置。如果压缩使用CompressionConfig对象,则可以完全异步配置解压缩。

DecompressionConfig decomp_config = gdeflate_manager->configure_decompression(comp_config);

该 API 的一个示例用例是大型神经网络的训练。可以使用的神经网络或训练集的大小取决于 GPU 的内存容量。使用压缩,您可以有效地增加此容量,而无需将数据卸载到 CPU 或使用多个 GPU 。

具体来说,基于反向传播的训练包括在向前传球时计算激活图,然后在向后传球的计算中重用它们。这些激活映射比较大且相对稀疏,因此非常适合压缩。使用gdeflate_manager压缩地图,并在内存中保存网络各层的压缩缓冲区和CompressionConfig对象。这可以实现完全异步的反向传播,包括解压缩。

如果没有使用的CompressionConfig对象,也可以使用压缩缓冲区配置解压缩。这是一个同步操作,必须从设备执行cudaMemcpyAsync操作。所有同步都在nvcompManager构造函数中指定的流上,并且不是设备范围的。

DecompressionConfig decomp_config = gdeflate_manager->configure_decompression(comp_buffer);

与压缩一样,在同步流之前,您可以一次将多个解压缩项目排队。

uint8_t* res_decomp_buffer1, res_decomp_buffer2;
DecompressionConfig decomp_config1 = gdeflate_manager->configure_decompression(comp_config1);
DecompressionConfig decomp_config2 = gdeflate_manager->configure_decompression(comp_config2); cudaMallocAsync(&res_decomp_buffer1, decomp_config1.decomp_data_size, stream);
cudaMallocAsync(&res_decomp_buffer2, decomp_config2.decomp_data_size, stream); gdeflate_manager->decompress(res_decomp_buffer1, comp_buffer1, decomp_config1);
gdeflate_manager->decompress(res_decomp_buffer2, comp_buffer2, decomp_config2); cudaStreamSynchronize(stream));

最后,在高级 API 中有两种类型的错误检查:std::runtime_error异常和检查nvcompStatus_t值。

如果任何 CUDA API 失败,就会引发std::runtime_error异常。您可以在应用程序中捕获这些错误,也可以不处理它们,在这种情况下,您的应用程序会失败,并显示一条描述错误的消息。例如,如果您提供的输出缓冲区大小不足或无法在 GPU 上访问,就会发生这种情况。

错误检查的第二种形式是检查CompressionConfig或DecompressionConfig对象中的nvcompStatus_t值。此状态在相关的内核调用期间设置。损坏的输入缓冲区和其他错误会触发它。

低级 API

低级 API 为更高级的工作流提供了 C API 。低级 API 同时压缩和解压缩您提供的一批独立块。这取决于您对数据进行分块,并提供足够数量的分块来利用 GPU 的并行处理能力。

如果有许多独立的、不连续的缓冲区,这是处理数据最有效的方法。低级 API 避免了将生成的压缩块打包到单个连续的压缩缓冲区的工作量。它还避免了与在高级 API 中保存有关缓冲区如何压缩的信息相关的压缩比开销。

该工作流非常适合数据库应用程序,例如,在这些应用程序中,往往需要压缩或解压缩许多独立的列。这个 API 用于 RAPIDS 和 NVIDIA Spark 实现。

压缩

对于低级 API 中的压缩,必须分配一个临时暂存缓冲区。临时缓冲区与高级 API 中描述的类似。然而,缓冲区大小取决于输入缓冲区的大小,因此必须重新定义它,并可能与每一组新的用户输入一起重新分配。

size_t temp_bytes;
nvcompBatchedGdeflateCompressGetTempSize(batch_size, chunk_size, nvcompBatchedGdeflateDefaultOpts, &temp_bytes); void* device_temp_ptr;
cudaMalloc(&device_temp_ptr, temp_bytes);

接下来,应该计算批处理中压缩块的最大大小。这允许您分配一组结果缓冲区。在下面的示例中,batch_size是要处理的块数。结果指针的设备数组在复制到设备之前在固定的主机内存中构造。

size_t max_out_bytes;
nvcompBatchedGdeflateCompressGetMaxOutputChunkSize(chunk_size, nvcompBatchedGdeflateDefaultOpts, &max_out_bytes); // Allocate output space on the device
void ** host_compressed_ptrs;
cudaMallocHost((void**)&host_compressed_ptrs, sizeof(size_t) * batch_size);
for(size_t ix_chunk = 0; ix_chunk < batch_size; ++ix_chunk) { cudaMalloc(&host_compressed_ptrs[ix_chunk], max_out_bytes);
} void** device_compressed_ptrs;
cudaMalloc(&device_compressed_ptrs, sizeof(size_t) * batch_size);
cudaMemcpy( device_compressed_ptrs, host_compressed_ptrs, sizeof(size_t) * batch_size,cudaMemcpyHostToDevice);

通过计算所有这些输入,您现在可以异步进行压缩,如图所示。

nvcompStatus_t comp_res = nvcompBatchedGdeflateCompressAsync( device_uncompressed_ptrs, device_uncompressed_bytes, chunk_size, batch_size, device_temp_ptr, temp_bytes, device_compressed_ptrs, device_compressed_bytes, nvcompBatchedGdeflateDefaultOpts,

减压

要开始解压,请根据压缩的缓冲区预计算解压的大小。如果您已经有此信息,请跳过此步骤。

nvcompBatchedGdeflateGetDecompressSizeAsync( device_compressed_ptrs, device_compressed_bytes, device_uncompressed_bytes, batch_size, stream);

与压缩类似,您还必须计算所需的临时大小,并分配临时暂存缓冲区。

size_t decomp_temp_bytes;
nvcompBatchedGdeflateDecompressGetTempSize(batch_size, chunk_size, &decomp_temp_bytes);
void * device_decomp_temp;
cudaMalloc(&device_decomp_temp, decomp_temp_bytes);

最后,可以进行异步解压缩。

nvcompStatus_t decomp_res = nvcompBatchedGdeflateDecompressAsync( device_compressed_ptrs, device_compressed_bytes, device_uncompressed_bytes, device_actual_uncompressed_bytes, batch_size, device_decomp_temp, decomp_temp_bytes, device_uncompressed_ptrs, device_statuses, stream);

标杆管理

nvCOMP 为低级和高级格式的每种格式提供了一组基准。图 2 比较了在几个不同的数据集上使用大型连续缓冲区时高级和低级的性能。使用 A100 GPU 收集结果。

Bar chart shows decompression throughputs on different datasets. The high-level performance nearly matches the low-level performance.

Bar chart shows decompression throughputs on different datasets. The high-level performance nearly matches the low-level performance.

图 2a 各种数据集的解压缩吞吐量。

Bar chart shows compression ratios on different datasets. The high-level performance nearly matches the low-level performance.

Bar chart shows compression ratios on different datasets. The high-level performance nearly matches the low-level performance.

图 2b 各种数据集的压缩比。

Bar chart shows compression ratios on different datasets. The high-level performance nearly matches the low-level performance

Bar chart shows compression ratios on different datasets. The high-level performance nearly matches the low-level performance

图 2c 各种数据集的压缩吞吐量

从结果中可以看出,在使用大型连续缓冲区时,低级和高级 API 之间的性能差异可以忽略不计。使用哪一个取决于您的用例。如果有许多小缓冲区,请使用低级 API ,或者避免与高级 API 相关的内存占用。

图 3 显示了日志规模下不同缓冲区大小的性能。为了产生这些结果,图 2 中显示的 mortgage int 数据集被分成许多批batchSize,如图所示。该文件超过 314 MB 。对于 1 MB 的批量大小,执行 315 次压缩和解压缩操作。批量大小为 400 MB 时,执行单个压缩和解压缩操作。

以这种方式批处理数据不会影响低级批处理 API 。

a bar chart showing high-level compression throughput at various batch sizes, compared to the low-level performance. The high-level performance suffers for smaller batch sizes.

a bar chart showing high-level compression throughput at various batch sizes, compared to the low-level performance. The high-level performance suffers for smaller batch sizes.

图 3a :在 314 MB 文件上运行的各种批量大小的压缩吞吐量。

a bar chart showing high-level decompression throughput at various batch sizes, compared to the low-level performance. The high-level performance suffers for smaller batch sizes.

a bar chart showing high-level decompression throughput at various batch sizes, compared to the low-level performance. The high-level performance suffers for smaller batch sizes.

在 314 MB 文件上操作的各种批量大小的解压缩吞吐量。

正如所证明的,对于小批量,高级接口的性能会严重下降。这显示了在压缩或解压缩许多较小的缓冲区时使用低级批处理 API 的实用性。低级批处理 API 可以使用更少、占用率更高的内核来完成操作,而高级 API 需要许多具有相关尾部效应和占用率问题的小型内核启动。

我们在库中加入了基准测试应用程序,以便您可以尝试不同的压缩格式,并查看哪种格式对您的数据最有效。提供的基准是benchmark_hlif和benchmark_《format》_chunked。有关更多信息,请参阅 nvCOMP README 。

总结

现在,您已经了解了如何使用高级 nvCOMP API 来轻松压缩和解压缩。您已经了解了何时使用低级 API 更好,以及如何使用它。

关于作者

Eric Schmidt 是 NVIDIA 的高级开发技术工程师。 Eric 目前正在 GPU 上加速压缩例程。在 2021 加入 NVIDIA 之前,埃里克在航天工业中花了 11 年的时间在应用数学中开发软件和研究算法。

审核编辑:郭婷

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • NVIDIA
    +关注

    关注

    14

    文章

    4996

    浏览量

    103213
  • gpu
    gpu
    +关注

    关注

    28

    文章

    4744

    浏览量

    129017
收藏 人收藏

    评论

    相关推荐

    单相电机两个绕组都在定子上吗

    单相电机的两个绕组,即起动线圈(或称为辅助绕组、副绕组)和运行线圈(或称为主绕组),都位于定子上 。这两个绕组在电机起着关键作用,共同协作以产生旋转磁场,从而使电机能够运转。 单相电机通常由
    的头像 发表于 09-03 15:10 856次阅读

    ad如何设置两个元器件的距离

    之间应保持的最小距离,以确保电路板的电气性能和制造过程的可靠性。以下是如何在AD设置两个元器件之间距离的步骤: 、进入规则设置界面 打开AD软件 :首先,确保你已经打开了Altium Designer软件,并加载了需要进行元
    的头像 发表于 09-02 15:31 7485次阅读

    功放机AB两个声道输出怎么接

    功放机AB两个声道输出的接线方式,主要取决于您想要实现的音频效果以及音箱的配置。以下将详细介绍几种常见的接线方式,以及它们各自的特点和适用场景。 、基础接线方式 在大多数情况下,功放机的AB两个
    的头像 发表于 08-23 10:40 3085次阅读

    触发器的两个稳定状态分别是什么

    触发器作为数字电路的基本逻辑单元,具有两个稳定状态,这两个状态通常用于表示二进制数码的0和1。
    的头像 发表于 08-12 11:01 1363次阅读

    双稳态电路的两个稳定状态是什么

    双稳态电路是种具有两个稳定状态的电子电路,广泛应用于数字电路、通信系统、存储器等领域。 双稳态电路的基本概念 双稳态电路是种具有两个稳定状态的电路,即在没有外部输入信号的情况下,电
    的头像 发表于 08-11 15:00 1535次阅读

    双稳态触发器的两个基本性质是什么

    双稳态触发器(Bistable Trigger)是种具有两个稳定状态的逻辑电路,广泛应用于数字电路设计。它具有两个基本性质:记忆性和切换性。
    的头像 发表于 08-11 10:08 728次阅读

    两个PLC之间如何交互信号

    在工业自动化系统,PLC(Programmable Logic Controller,可编程逻辑控制器)是核心的控制设备。在许多复杂的应用场景,需要两个或多个PLC之间进行信号交互,以实现更高
    的头像 发表于 06-14 16:57 4509次阅读

    怎么让工程同时存在两个ioc文件?

    你好,我现在需要在工程兼容两个不同的项目,这两个项目有不同的配置文件,请问可否让两个ioc
    发表于 05-23 07:50

    两个铜片可以形成原电池吗

    两个铜片本身不能形成原电池,因为原电池的工作原理依赖于两个不同电位的电极材料之间的氧化还原反应。
    的头像 发表于 05-21 16:23 998次阅读

    为什么在交流电桥至少需要两个可调参数?

    在交流电桥的测量,至少需要两个可调参数的原因与电桥的工作原理、测量的准确性以及校准过程有关。
    的头像 发表于 05-15 17:49 1910次阅读

    原电池中的两个电极能是相同的吗?

    在原电池的设计和运作两个电极是否可以相同,这取决于电池的类型和所需的电化学反应。
    的头像 发表于 04-26 17:32 2488次阅读

    arcgis如何关联两个属性表

    在ArcGIS,关联两个属性表是重要的操作,可以通过此操作将两个的数据关联起来,以便进
    的头像 发表于 02-25 11:01 4303次阅读

    两个电位器地控制变频器,如何接线?

    两个电位器地控制变频器,如何接线? 接线方式如下: 1. 首先,明确需要使用的电器设备。在这个场景,我们需要
    的头像 发表于 02-05 10:13 5418次阅读

    CYW54591 SoC有两个物理独立的wifi接口吗?

    我有关于 CYW54591的问题。 这个 SoC 有两个物理独立的 wifi 接口吗? 这意味着单独的 MAC 地址以及像两个 wifi
    发表于 01-23 07:28

    两个机器的时钟怎么同步?

    两个机器的时钟怎么同步? 在现代社会中,时间同步对于各种科学研究、工业生产和通信技术都具有重要意义。在许多应用程序,如分布式系统、计算机网络和数据同步等领域,为了确保数据的致性和准确性,需要确保
    的头像 发表于 01-16 14:26 1725次阅读