XLA编译优化可以有CPU版本也可以GPU版本,tensorflow特定的接口对机器上的device进行管理,作为推理C++ Serving服务端需要增加资源管理的模块功能;
1、gpu卡的管理
(1)获取gpu卡数量 cudaGetDeviceCount(&gpu_count);
2、封装了一个executor调度器
可以通过下面配置打开,options.enable_internal_executor = true;
主要代码是在executor.cc, 主要实现了当推理消息收到后检测stream状态调度更加均匀,默认的情况随机选择一个stream,然后使用一个锁保证一个线程在调用stream,这样可能存在等待问题;
实际压测发现有效果,基本可以跟tensorrt性能达到一致;
3、设备上下文stream_executor
StatusOr
stream_executor
是表示设备上下文的指针。在这个特定的代码实现中,se::StreamExecutor*
是基于 StreamExecutor
, 它是一个 device 环境的一个抽象类,用于管理 device 上下文以及分配 device 的资源。 Backend
类装饰了StreamExecutor
并提供了管理装备的外部接口的特定实现。 通过这个 StreamExecutor
对象,Backend
类可以执行任何与设备相关的任务,包括在设备上分配和拷贝内存、执行计算任务和与设备通信等等。
4. 获取device内存大小
stream_exec->DeviceMemoryUsage(&available_memory, &total_memory))
5. GPU上的内存管理BFCAllocator
需要使用一个内存分配器来管理设备的内存使用情况。BFCAllocator
是一种在设备上使用的内存分配器,该分配器使用分配器中的 Largest Best Fit (LBF) 算法来自动管理设备上的内存分配和释放。
LBF 算法是一种高效的内存分配算法,它可以避免内存碎片和帮助最大化设备内存的利用率。BFCAllocator
通过实现自己的版本来使用这种算法来管理设备内存,可以在 GPU 和 CNN 等计算密集型任务中提高性能。通过封装 Tensorflow 的设备内存分配器 API,使用 BFCAllocator
类可以更方便地实现设备内存管理,减少了内存分配和释放时的开销。
tensorflow::DeviceMemAllocator
和 tensorflow::BFCAllocator
都是 TensorFlow 中的分配器,用于管理 GPU 设备上的内存分配和释放。
tensorflow::DeviceMemAllocator
是一种简单的分配器,用于在 TensorFLow 中管理单个设备上的内存分配和释放。它基于 CUDA API 实现,在每个设备上维护一个内存池,并支持对内存池的透明管理。DeviceMemAllocator
没有跨设备管理和碎片整理等高级功能,但可以满足一般的内存管理需求。
tensorflow::BFCAllocator
是一种更高级的分配器,它是分布式的分块填充(block-fragmented caching,BFC)分配器,用于管理 TensorFlow 中的 GPU 设备上的内存申请和释放。相比于 DeviceMemAllocator
,BFCAllocator
的特点是可以避免内存碎片,提高设备内存的使用效率。BFCAllocator
也具有跨设备管理的功能,它可以根据预定义的算法在不同设备之间分配内存,以满足需求。
总的来说,tensorflow::DeviceMemAllocator
主要用于简单的单设备内存管理,而 tensorflow::BFCAllocator
主要用于更复杂的分布式设备内存管理和优化,但它们可以共同工作,BFCAllocator
可以使用 DeviceMemAllocator
作为其基础设施来实现更高级的内存功能和优化。
6.local_client句柄通过GetOrCreateLocalClient
获得local_client后,可以通过其管理整个gpu设备,多个设备device的管理
xla::Backend* backend = local_client->mutable_backend()
auto stream_executor = backend->stream_executor(device_ordinal).ValueOrDie();
//device_ordinal gpu设备序号
7、XLA中的Buckets
这个是多少个分片作为一个桶,推理的时候是按照固定的分片划分几个范围,每个桶申请gpu内存
8、xla compile和executable_buckets
每个桶独立编译一个executable 通过device_->compile最底层调用的还是
local_client_->Compile(computation, shapes, build_options);返回值包含是xla::LocalExecutable
最终执行推理的句柄是std::unique_ptr
9、Local_executable和Stream关联
编译后的可执行程序xla::LocalExecutable 执行RunAsync进行推理,
RunAsync()函数推理的参数中有tf_stream参数,使用那个流进行推理,其中流外面加锁了,保证只有一个线程执行该stream,默认是随随机选择一个流执行,可能存在等待问题
gpu_executable->local_executable->RunAsync()
10、RunAsync推理
std::unique_ptr
local_executable->RunAsync()
11、Stream流的线程安全问题
stream_executor
中的 Stream
类并不支持多线程操作。它是一个抽象基类,用于表示异步执行的操作流。实现 Stream
的具体的子类可以支持不同的异构计算加速器(如 GPU、TPU、FPGA 等),并且可以并行地执行多个操作。但是,单个 Stream
对象的操作是串行的,即它们是以 FIFO 的方式在同一队列中运行的,而且这些操作的执行顺序是不可预测的。如果要实现多线程操作,需要单独创建多个 Stream
对象,然后在多个线程中向这些 Stream
对象提交操作。这些 Stream
对象可以在不同的设备上执行操作,或者在同一个设备上使用不同的计算资源(如 CUDA 核心或张量核心)执行操作。需要注意的是,在实际操作中,多线程的并行操作可能会产生与单线程不同的结果,因此需要注意调试和测试。
12、stream和GPU设备的关系
一个GPU卡可以设置4个stream对象,其实stream也可以用在不同gpu设备上,但是值得注意的是,应该避免在不同 GPU 之间频繁地切换 Stream
对象,因为这会导致数据传输和同步开销,从而降低计算效率。