Arm Compute Library Architecture

Core vs Runtime libraries

Core库是低级算法实现的集合,它被设计为嵌入到现有的项目和应用程序中:

  • 它不分配任何内存(所有的内存分配/映射必须由调用者处理)。
  • 它不执行任何类型的多线程(但向调用者提供有关工作负载如何拆分的信息)。

运行时库是Core库非常基本的封装,可用于快速原型,这是意味着它很基础:

  • 它使用标准的malloc()分配图像和张量。
  • 它使用非常简单的线程池,以非常基本的方式实现NEON代码的多线程。
  • 对于OpenCL,它对所有映射操作和内核使用默认的CLScheduler命令队列。

为了获得最大的性能,用户需要重新实现一个等效的运行时库,以更好地满足需求(更智能的多线程策略,NEON和OpenCL之间的负载平衡等)

Windows, kernels, multi-threading and functions

Windows

Windows表示要执行的工作负载,它最多可处理维度为 Coordinates::num_max_dimensions。每个维度由start、end和step定义。

只要以下所有规则保持为真,它就可以拆分为子窗口:

  • max[n].start() <= sub[n].start() < max[n].end()
  • sub[n].start() < sub[n].end() <= max[n].end()
  • max[n].step() == sub[n].step()
  • (sub[n].start() - max[n].start()) % max[n].step() == 0
  • (sub[n].end() - sub[n].start()) % max[n].step() == 0

Kernels

IKernel接口的每个实现(核心库中所有内核的基类)都以相同的方式工作:

OpenCL kernels:

// Initialize the CLScheduler with the default context and default command queue
// Implicitly initializes the CLKernelLibrary to use ./cl_kernels as location for OpenCL kernels files and sets a default device for which OpenCL programs are built.
CLScheduler::get().default_init();
cl::CommandQueue q = CLScheduler::get().queue();
//Create a kernel object:
MyKernel kernel;
// Initialize the kernel with the input/output and options you want to use:
kernel.configure( input, output, option0, option1);
// Retrieve the execution window of the kernel:
const Window& max_window = kernel.window();
// Run the whole kernel in the current thread:
kernel.run( q, max_window ); // Enqueue the kernel to process the full window on the default queue
// Wait for the processing to complete:
q.finish();

NEON/CPP kernels:

//Create a kernel object:
MyKernel kernel;
// Initialize the kernel with the input/output and options you want to use:
kernel.configure( input, output, option0, option1);
// Retrieve the execution window of the kernel:
const Window& max_window = kernel.window();
// Run the whole kernel in the current thread:
kernel.run( max_window ); // Run the kernel on the full window

多线程

上一节介绍如何在当前线程中运行NEON/CPP kernel,但如果系统有多个CPU核心,则可能需要kernel使用多个核心。这是如何做到的:

    ThreadInfo info;
    info.cpu_info = _info;
    const Window      &max_window     = kernel->window();
    const unsigned int num_iterations = max_window.num_iterations(split_dimension);
    info.num_threads                  = std::min(num_iterations, _num_threads);
    if(num_iterations == 0)
    {
   
        return;
    }
    if(!kernel->is_parallelisable() || info.num_threads == 1)
    {
   
        kernel->run(max_window, info);
    }
    else
    {
   
        int  t         = 0;
        auto thread_it = _threads.begin();
        for(; t < info.num_threads - 1; ++t, ++thread_it)
        {
   
            Window win     = max_window.split_window(split_dimension, t, info.num_threads);
            info.thread_id = t;
            thread_it->start(kernel, win, info);
        }
        // Run last part on main thread
        Window win     = max_window.split_window(split_dimension, t, info.num_threads);
        info.thread_id = t;
        kernel->run(win, info);
        try
        {
   
            for(auto &thread : _threads)
            {
   
                thread.wait();
            }
        }
        catch(const std::system_error &e)
        {
   
            std::cerr << "Caught system_error with code " << e.code() << " meaning " << e.what() << '\n';
        }
    }

这是所有NEON函数在NEON运行时库中使用的非常基本的实现。

也可以看看
CPPScheduler。

注意 一些像NEHistogramKernel这样的内核需要一些本地的临时缓冲来执行它们的计算。 为了避免线程之间的内存损坏,本地缓冲区必须具有以下大小:memory_needed_per_thread * num_threads,必须将0和num_threads之间唯一的thread_id分配给传递给run函数的ThreadInfo对象。

函数

函数将自动分配上面提到的临时缓冲区,并使用上一节中介绍的非常基本的调度程序自动执行多线程内核的执行。

简单函数只能调用一个内核(例如NEConvolution3x3),而更复杂的函数则由多个内核构成流水线(如NEGaussianPyramid,NEHarrisCorners)。检查他们的文档,找出每个函数使用哪个内核。

//Create a function object:
MyFunction function;
// Initialize the function with the input/output and options you want to use:
function.configure( input, output, option0, option1);
// Execute the function:
function.run();

警告
Compute Library需要Mali OpenCL DDK r8p0或更高版本(使用-cl-arm-non-uniform-work-group-size标志编译OpenCL内核)

注意
运行时库中的所有OpenCL函数和对象都使用与CLScheduler关联的命令队列来执行所有操作,但真正的实现将使用不同的队列来映射操作和内核,以便达到更好的GPU利用率。

OpenCL调度程序和内核库

Compute Library运行时对所有操作使用单个命令队列和上下文。

用户可以通过CLScheduler的接口来获取/设置该上下文和命令队列。

用户可以通过CLScheduler的接口获取/设置目标GPU设备。

注意
确保应用程序使用与OpenCL相同的上下文,跨上下文共享对象是禁止的。这是通过在应用程序的开头调用CLScheduler::init()或CLScheduler::default_init()来完成的。
确保在创建函数类后,调度程序的目标不会更改。

本库使用的所有OpenCL内核均构建并存储在CLKernelLibrary中。如果编译库时设置embed_kernels=0,则应用程序可以通过调用CLKernelLibrary::init()来设置OpenCL内核的路径,默认情况下路径设置为“./cl_kernels”

OpenCL事件和同步

为了阻塞直到CLScheduler的命令队列中的所有作业完成,用户可以调用CLScheduler::sync()或使用CLScheduler::enqueue_sync_event()创建同步事件

例如:

        PPMLoader     ppm;
        constexpr int scale_factor = 2;
        CLScheduler::get().default_init();
        if(argc < 2)
        {
   
            // Print help
            std::cout << "Usage: ./build/cl_events [input_image.ppm]\n\n";
            std::cout << "No input_image provided, creating a dummy 640x480 image\n";
            // Create an empty grayscale 640x480 image
            src.allocator()->init(TensorInfo(640, 480, Format::U8));
        }
        else
        {
   
            ppm.open(argv[1]);
            ppm.init_image(src, Format::U8);
        }
        TensorInfo dst_info(src.info()->dimension(0) / scale_factor, src.info()->dimension(1) / scale_factor, Format::U8);
        // Configure the temporary and destination images
        dst.allocator()->init(dst_info);
        tmp_scale_median.allocator()->init(dst_info);
        tmp_median_gauss.allocator()->init(dst_info);
        //Configure the functions:
        scale.configure(&src, &tmp_scale_median, InterpolationPolicy::NEAREST_NEIGHBOR, BorderMode::REPLICATE);
        median.configure(&tmp_scale_median, &tmp_median_gauss, BorderMode::REPLICATE);
        gauss.configure(&tmp_median_gauss, &dst, BorderMode::REPLICATE);
        // Allocate all the images
        src.allocator()->allocate();
        dst.allocator()->allocate();
        tmp_scale_median.allocator()->allocate();
        tmp_median_gauss.allocator()->allocate();
        // Fill the input image with the content of the PPM image if a filename was provided:
        if(ppm.is_open())
        {
   
            ppm.fill_image(src);
            output_filename = std::string(argv[1]) + "_out.ppm";
        }

OpenCL/NEON互操作性

您可以混合使用OpenCL和NEON内核和函数。但是,用户需要处理OpenCL对象的映射/解映射,例如:

        PPMLoader ppm;
        CLScheduler::get().default_init();
        if(argc < 2)
        {
   
            // Print help
            std::cout << "Usage: ./build/cl_convolution [input_image.ppm]\n\n";
            std::cout << "No input_image provided, creating a dummy 640x480 image\n"

你可能感兴趣的:(arm,DeepLearning,DeepLearning,arm)