(深度学习优化大师)TVM的前辈,介绍下什么是HALIDE

TVM(端到端的优化栈)概述 陈天奇团队宣布推出 TVM,在微博上表示,「我们今天发布了 TVM,和 NNVM 一起组成深度学习到各种硬件的完整优化工具链,支持手机,cuda, opencl, metal, javascript 以及其它各种后端。欢迎对于深度学习,编译原理,高性能计算,硬件加速有兴趣的同学一起加入 dmlc 推动领导开源项目社区 。」 大多数现有系统针对窄范围的服务器级 GPU 进行优化,且需要在包括手机、I... 阅读详情
AI摘要

本文介绍Halide库的基础知识,包括其特点、优化方法及其在图像处理领域的应用。Halide是一种用于图像处理的领域特定语言,能有效提升算法的执行效率。

在开始学习之前推荐大家可以多在  FlyAI竞赛服务平台 多参加训练和竞赛,以此来提升自己的能力。FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台。每周免费提供项目开源算法样例,支持算法能力变现以及快速的迭代算法模型。

前言

前几天又用到了TVM,想着是否能够间接替代TensorRT作为GPU服务器部署的一个后端。TensorRT在自家的GPU上确实强大,也是老黄家重点关照的”开源项目“。但TensorRT还是有一点点小缺点的:

  • 经过infer优化后的模型与特定GPU绑定,例如在1080TI上生成的模型在2080TI上无法使用
  • 高版本的TensorRT依赖于高版本的CUDA版本,而高版本的CUDA版本依赖于高版本的驱动
  • TensorRT尽管好用,但推理优化infer还是闭源的,像深度学习炼丹一样,也像个黑盒子,导入模型我们不确定能否顺利转化成功。如果不成功,不能直接修改源码,总感觉缺点什么

以上缺点单纯吐槽(轻喷轻喷),其实这些问题都不是大问题,如果是自己捣鼓的话问题不大。但是如果放到生产环境中,上面的一些情况可能会造成一些小麻烦(害~毕竟生产环境的条条框框太多了),用起来不是那么得心应手。

不知道什么是TVM的,可以知乎搜一下,也可以看一下我之前写的两篇文章:

不过本文不是讨论TVM,除了上述两文,也写了另外一些TVM的分析(算是第三篇第四篇),但自己懒加上时间有时候不够用就没有整理,等等整理好再发。本文其实是想介绍下自己之前写的Halide一文,与TVM相关,借此机会引入一哈。

什么是Halide

Halide是用C++作为宿主语言的一个图像处理相关的DSL(Domain Specified Language)语言,全程领域专用语言。主要的作用为在软硬层面上(与算法本身的设计无关)实现对算法的底层加速,感觉有必要对其有一定的了解。因为不论是传统的图像处理方法亦或是深度学习应用都或多或少使用到了halide的思想。

其中,在OpenCV(传统图像处理库)中部分算法使用了Halide后端,而TVM(神经网络编译器)也是用了Halide的思想去优化神经网络算子。

HALIDE对算法的加速效果

那么Halide到底是干嘛用的,看上面那张图,同样的一个算法处理(局部拉普拉斯变换),使用直接的C++语言写出来算法速度很慢,Adobe公司使用3个月对这个算法进行了优化(手工优化)使这个算法的速度快了10倍,但是如果你使用了Halide,只需要几行代码,就可以使这个算法比之前普通直接的算法快上20倍。

一句话来说,Halide大大节省了我们手动优化底层算法的时间,让我们只需要关注算法的设计。

而算法与底层优化的的过程,Halide帮咱们做了。

Halide为什么可以优化算法

Halide的特点是其图像算法的计算的实现(Function和Expression)和这些计算在计算硬件单元上的调度(Schedule)是分离的,其调度以Function为单位。最终将整个图像算法转换为高效率的多层for循环,for循环的分部数据范围划分和数据加载都是由Halide来完成的,而且可以实现数据的加载和算法计算的Overlay,掩盖数据加载导致的延迟。Halide的Schedule可以由程序员来指定一些策略,指定硬件的buffer大小,缓冲线的相关设置,这样可以根据不同的计算硬件的特性来实现高效率的计算单元的调度,而图像算法的计算实现却不需要修改。

上面这部分截取于知乎:https://www.zhihu.com/question/294625837/answer/496218375

决定算法在某个硬件平台上执行时性能的“三角力量”如下。

TIM截图20190417101516

其中,算法本身的设计是一方面,一个好的算法往往效率会高很多。而另外一个方面就是算法中计算顺序的组织,而Halide可以改变的就是我们算法在某个硬件平台上的计算顺序:

TIM截图20190417102405

其中Halide可以在硬件平台上为算法实现并行和良好的缓存一致性:

举个例子

我们以Halide中的经典模糊化(blurred)图像的例子来演示一下(以下代码也可以在自己的电脑上测试观察结果),这里用OpenCV来对图像进行操作进行演示:

首先我们设计一个可以对图像进行模糊的操作函数:

// in为输入原始图像 blury为输出模糊后的图像
void box_filter_3x3(const Mat &in, Mat &blury)
{
    Mat blurx(in.size(), in.type());

    for(int x = 1; x < in.cols-1; x ++)
        for(int y = 0 ; y < in.rows; y ++)
            blurx.at<uint8_t >(y, x) = static_cast<uint8_t>(
                    (in.at<uint8_t >(y, x-1) + in.at<uint8_t >(y, x) + in.at<uint8_t >(y, x+1)) / 3);

    for(int x = 0; x < in.cols; x ++)
        for(int y = 1 ; y < in.rows-1; y ++)
            blury.at<uint8_t >(y, x) = static_cast<uint8_t>(
                    (blurx.at<uint8_t >(y-1, x) + blurx.at<uint8_t >(y, x) + blurx.at<uint8_t >(y+1, x)) / 3);

}

对图像模糊操作很简单,我们首先在x轴上对每个像素点以及周围的两个点进行求和平均,然后再到y轴上进行同样的操作,这样相当于一个3x3平均卷积核对整个图像进行操作,这里就不进行详细描述了。

我们准备一张(1920,1080)的图像,对其进行100次上述操作,并记录时间,发现Time used:4521.72 ms。

然后我们简单改变一下执行次序,将上述循环嵌套中的x和y的顺序改变一下:

Mat blurx(in.size(), in.type());

   // 这里进行了嵌套的变换
   for(int y = 0 ; y < in.rows; y ++)
       for(int x = 1; x < in.cols-1; x ++)
           blurx.at<uint8_t >(y, x) = static_cast<uint8_t>(
                   (in.at<uint8_t >(y, x-1) + in.at<uint8_t >(y, x) + in.at<uint8_t >(y, x+1)) / 3);

   // 这里进行了嵌套的变换
   for(int y = 1 ; y < in.rows-1; y ++)
       for(int x = 0; x < in.cols; x ++)
           blury.at<uint8_t >(y, x) = static_cast<uint8_t>(
                   (blurx.at<uint8_t >(y-1, x) + blurx.at<uint8_t >(y, x) + blurx.at<uint8_t >(y+1, x)) / 3);
}

同样,我们执行100次并记录时间:发现Time used:3992.35 ms,可以发现下面的模糊操作执行的速度比上面的快一些。当然大家可能会想,这也没快多少啊。当然这只是一副示例图像, 如果这张图像的长宽差距比较大(例如1:10)、亦或是我们要某一个时刻处理几万次这样的操作,一旦量级起来,那么这两者的差距就不是一点半点了。

硬件层面的原理

为什么会这样呢,上述两种操作执行的算法功能是一样的,但是速度为什么会有差别。究其原因,这差别和算法本身没什么关系,而与硬件的设计是有巨大关系,例如并行性和局部性。

我们可以看到下面是Adobe工程师对上述的算法在硬件层面上极致优化结果,比之前的算法快了10倍,其中用到了SIMD(单指令多数据流)、以及平铺(Tiling)、展开(Unrolling)和向量化(Vectorization)等常用技术。充分利用了硬件的性能,从而不改变算法本身设计的前提下最大化提升程序执行的速度。

TIM截图20190417151611

官方示例

Halide作为一个DSL,我们很容易就可以使用它,这里我们将其源码下下来并进行编译。完成之后我们就可以使用它了(这里省略编译步骤,可自行在官网查阅):

首先我们引用Halide头文件以及其他的文件。

#include "Halide.h"
#include <stdio.h>
#include <algorithm>

using namespace Halide;

初次使用Halide之前,首先需要知道halide中的一些语法:

TIM截图20190417103330

然后我们利用Halide定义两个变量,这两个变量单独使用时没有任何意义,同时我们用字符串x和y为两个变量起了名字:

Var x("x"), y("y");

然后利用Func 定义一个待执行的function,并起名为gradient。

Func gradient("gradient");

这时我们定义function中每个点的执行逻辑,对于(x,y)这个点执行的逻辑为x + y。 其中x和y都是Var,而x + y这个操作在赋予给gradient的时候会自动转化为Expr类型,这里可以理解为将x + y这个代数表达式的逻辑赋予了gradient,最后,通过realize函数来执行整个逻辑:

gradient(x, y) = x + y;
// realize 即为实现这个操作 到了这一步才会对上述的操作进行编译并执行
Buffer<int> output = gradient.realize(4, 4);

这个逻辑我们用C++来表示即为:

for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 4; x++) {
        printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
    }
}

而上述实现的Halide伪代码为:

produce gradient:
  for y:
    for x:
      gradient(...) = ...

Halide默认的计算顺序是行优先的,也就是x代表每一行的元素位置,y代表每一列的元素位置:

lesson_05_row_major

如果我们将其中y和x的计算顺序换一下:

// 将y的顺序提到x之前
gradient.reorder(y, x);

最终的计算过程就为列优先:

lesson_05_col_major

相应的伪代码为:

produce gradient_col_major:
  for x:
    for y:
      gradient_col_major(...) = ...

拆分 Split

我们可以对每个维度进行拆分,假如我们依然是行优先计算,但是我们对x轴进行拆分,将其拆成一个外循环一个里循环,y轴不进行变动:

Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 2);

这时对应的C++代码实现为:

for (int y = 0; y < 4; y++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        for (int x_inner = 0; x_inner < 2; x_inner++) {
            int x = x_outer * 2 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
    }
}

融合 fuse

或者我们不进行拆分,对x和y两个轴进行融合:

Var fused;
gradient.fuse(x, y, fused);

此时对应的C++实现代码为:

for (int fused = 0; fused < 4*4; fused++) {
    int y = fused / 4;
    int x = fused % 4;
    printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}

但是要知道,上述拆分和融合操作只是对Halide所能进行的操作进行一下演示而是,这种操作方式并没有实际用处,也就是说实际中的计算顺序并没有改变。

平铺 tile

这一步中就要进入Halide中比较重要的部分了,这一步中我们将x和y轴以4为因子间隔进行划分,并且重新对计算的路径进行重排序:

Var x_outer, x_inner, y_outer, y_inner;
gradient.split(x, x_outer, x_inner, 4);
gradient.split(y, y_outer, y_inner, 4);
gradient.reorder(x_inner, y_inner, x_outer, y_outer);

// 上面的步骤其实可以简化成
gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);

对应的C++计算代码为:

for (int y_outer = 0; y_outer < 2; y_outer++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        for (int y_inner = 0; y_inner < 4; y_inner++) {
            for (int x_inner = 0; x_inner < 4; x_inner++) {
                int x = x_outer * 4 + x_inner;
                int y = y_outer * 4 + y_inner;
                printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
            }
        }
    }
}

可视化一下就是这个样子(注意这里的示例大小为(8,8)):

lesson_05_tiled

这种平铺的好处是可以充分利用相邻的像素,例如模糊操作,我们会使用重叠的输入数据(也就是存在一个元素使用两次的情况),如果采用这种计算方式,可以大大加快计算性能。

向量化 vector

向量化即我们使用cpu中的SIMD技术,一次性计算多个数据,充分利用硬件的特点,例如在x86中我们可以利用SSE技术来实现这个功能。

在Halide中,我们首先将x轴的循环嵌套按照,内侧循环因子4的方式,拆分为两个(也就是内侧循环x执行四次,外侧根据总数进行计算,下例是2*4=8),然后将内侧的x循环转化为向量的形式:

Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 4);
gradient.vectorize(x_inner);

用C++来表示即为:

for (int y = 0; y < 4; y++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        // The loop over x_inner has gone away, and has been
        // replaced by a vectorized version of the
        // expression. On x86 processors, Halide generates SSE
        // for all of this.
        int x_vec[] = {x_outer * 4 + 0,
                        x_outer * 4 + 1,
                        x_outer * 4 + 2,
                        x_outer * 4 + 3};
        int val[] = {x_vec[0] + y,
                        x_vec[1] + y,
                        x_vec[2] + y,
                        x_vec[3] + y};
        printf("Evaluating at <%d, %d, %d, %d>, <%d, %d, %d, %d>:"
                " <%d, %d, %d, %d>\n",
                x_vec[0], x_vec[1], x_vec[2], x_vec[3],
                y, y, y, y,
                val[0], val[1], val[2], val[3]);
    }
}

可视化后就比较明显了,外部x每一行执行两次,内侧x变为向量的形式,一个指令集就可以执行完成:

lesson_05_vectors

展开 unrolling

如果在图像中多个像素同时共享有重叠的数据,这个时候我们就可以将循环展开,从而使那些可以共享使用的数据只计算一次亦或是只加载一次。

在下面中我们将x轴拆分为内侧和外侧,因为每次内侧的数值增长都是从0到1,如果我们将内测循环的x轴展开,就不需要每次循环到这里再读取内测循环的x的值了:

Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 2);
gradient.unroll(x_inner);

相应的C++代码为:

printf("Equivalent C:\n");
for (int y = 0; y < 4; y++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        // Instead of a for loop over x_inner, we get two
        // copies of the innermost statement.
        {
            int x_inner = 0;
            int x = x_outer * 2 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
        {
            int x_inner = 1;
            int x = x_outer * 2 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
    }
}

融合、平铺、并行 Fusing, tiling, and parallelizing

这一步中,我们将融合、平铺和并行操作都融合到一起,来对一个8x8的图像进行操作。首先,我们将x轴和y轴都按照4因子进行平铺操作。随后我们将外侧的y和外侧的x轴循环进行融合(2+2=4),再将这个融合后的操作进行并行操作,也就是同时执行这四个(2+2=4)操作:

Var x_outer, y_outer, x_inner, y_inner, tile_index;
gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);
gradient.fuse(x_outer, y_outer, tile_index);
gradient.parallel(tile_index);

相应的C++代码为:

// This outermost loop should be a parallel for loop, but that's hard in C.
for (int tile_index = 0; tile_index < 4; tile_index++) {
    int y_outer = tile_index / 2;
    int x_outer = tile_index % 2;
    for (int y_inner = 0; y_inner < 4; y_inner++) {
        for (int x_inner = 0; x_inner < 4; x_inner++) {
            int y = y_outer * 4 + y_inner;
            int x = x_outer * 4 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
    }
}

可视化后的结果,可以看到8x8中左上、左下、右上、右下四个区域是几乎同时进行的(tile_index),而每个区域和之前tile那一节的计算方式是一样的,只不过这次换成了并行计算:

lesson_05_parallel_tiles

整合

这次来点大点的图像,我们输入的图像大小为350 x 250,对其进行最优化的操作:

首先我们将其按照64 x 64的因子进行平铺,其次融合y轴和x轴外侧的循环操作数,最后对其进行并行操作 (这里注意下,我们可以看到350或者250并不能被64整除,这个不用担心,Halide会自动处理多余或者不够的部分)。

Var x_outer, y_outer, x_inner, y_inner, tile_index;
gradient_fast
    .tile(x, y, x_outer, y_outer, x_inner, y_inner, 64, 64)
    .fuse(x_outer, y_outer, tile_index)
    .parallel(tile_index);
// 可以这样连续使用.写,因为对象函数返回的是对象本身的引用

这样还不够,上面我们已经将整个图像平铺为6*4个部分,而这一步中对每个平铺后的部分再进行一次平铺操作,这次将每个小块按照4x2的形式平铺为,其中y_inner_outer分成两个(每个为y_pairs),x_inner_outer分成四个(每个为x_vectors),然后将每个x_vectors并行化,将y_pairs展开。

Var x_inner_outer, y_inner_outer, x_vectors, y_pairs;
gradient_fast
    .tile(x_inner, y_inner, x_inner_outer, y_inner_outer, x_vectors, y_pairs, 4, 2)
    .vectorize(x_vectors)
    .unroll(y_pairs);

以下可视化的结果为:

lesson-05-full

对应的c++展示代码为:

for (int tile_index = 0; tile_index < 6 * 4; tile_index++) {
    int y_outer = tile_index / 4;
    int x_outer = tile_index % 4;
    for (int y_inner_outer = 0; y_inner_outer < 64/2; y_inner_outer++) {
        for (int x_inner_outer = 0; x_inner_outer < 64/4; x_inner_outer++) {
            // We're vectorized across x
            int x = std::min(x_outer * 64, 350-64) + x_inner_outer*4;
            int x_vec[4] = {x + 0,
                            x + 1,
                            x + 2,
                            x + 3};

            // And we unrolled across y
            int y_base = std::min(y_outer * 64, 250-64) + y_inner_outer*2;
            {
                // y_pairs = 0
                int y = y_base + 0;
                int y_vec[4] = {y, y, y, y};
                int val[4] = {x_vec[0] + y_vec[0],
                                x_vec[1] + y_vec[1],
                                x_vec[2] + y_vec[2],
                                x_vec[3] + y_vec[3]};

                // Check the result.
                for (int i = 0; i < 4; i++) {
                    if (result(x_vec[i], y_vec[i]) != val[i]) {
                        printf("There was an error at %d %d!\n",
                                x_vec[i], y_vec[i]);
                        return -1;
                    }
                }
            }
            {
                // y_pairs = 1
                int y = y_base + 1;
                int y_vec[4] = {y, y, y, y};
                int val[4] = {x_vec[0] + y_vec[0],
                                x_vec[1] + y_vec[1],
                                x_vec[2] + y_vec[2],
                                x_vec[3] + y_vec[3]};

                // Check the result.
                for (int i = 0; i < 4; i++) {
                    if (result(x_vec[i], y_vec[i]) != val[i]) {
                        printf("There was an error at %d %d!\n",
                                x_vec[i], y_vec[i]);
                        return -1;
                    }
                }
            }
        }
    }
}

到这里Halide中的基本操作就介绍完毕了。

还有一点

哦,对了,如果用Halide来写文章一开头描述的模糊(blur)算法的话,会是这个样子:

Func blur_3x3(Func input) {
  Func blur_x, blur_y;
  Var x, y, xi, yi;

  // The algorithm - no storage or order
  blur_x(x, y) = (input(x-1, y) + input(x, y) + input(x+1, y))/3;
  blur_y(x, y) = (blur_x(x, y-1) + blur_x(x, y) + blur_x(x, y+1))/3;

  // The schedule - defines order, locality; implies storage
  blur_y.tile(x, y, xi, yi, 256, 32)
        .vectorize(xi, 8).parallel(y);
  blur_x.compute_at(blur_y, x).vectorize(x, 8);

  return blur_y;
}

这段著名的代码同时就在官方的主页上挂着,算是一个比较好的示例。

Halide的特点

Halide这个底层优化库有几个比较亮眼的特点:

Explicit programmer control

The compiler does exactly what you say. Schedules cannot influence correctness. Exploration is fast and easy.

明确的程序控制,也就是说,我们如何按照这个计算的顺序(与算法本身无关)是确定的,一旦我们已经设定好就不会再改变。

Stochastic search (autotuning)

Pick your favorite high-dimensional search.

而自动搜索则是每个具有搜索空间的优化器都可以使用的,因为每次进行优化操作的时候,优化的因子都是不确定的,对于不同的硬件来说,不同的配置可能导致的执行速度也不一样。因此自动随机搜索空间因子是有必要的。

元编程

Halide的思想与元编程有着密切的关系,不仅是其设计思路或者是其执行思路,都遵循了元编程的思想,也就是代码在编译之前并没有明确的执行逻辑,只有编译过后,才会形成执行逻辑。

TIM截图20190417103037

其他相关

halide既然作为与算法无关的底层优化器,与当今大伙的深度学习的结合应用肯定也是非常多的。OpenCV库就使用了halide去优化底层的神经网络算子,相应的benchmark结论在这里,但是我们发现使用了halide的神经网络运行的速度竟然不如普通的C++实现版。原因不确定是否halide本身的设计有关,还是与halide优化和神经网络算子的兼容性有关,总之,如果想要利用halide真正的实现加速还是需要等待一段时间(等了好久还是用到TVM上了)。

TIM截图20190417101052

相关提问:

https://stackoverflow.com/questions/47202895/why-is-opencv-dnn-slower-if-i-use-halide

另外提一下,Halide的运行有两种方式,一种是JIT的模式,另一种是AOT的模式。JIT模式使用起来比较方便,可以直接将算法和Halide的代码生成generator封装成一个类,在程序的其他部分调用这个类,然后再编译使用。

而在嵌入式环境或交叉编译环境下一般使用AOT模式,此时需要调用compiler函数将算法代码和Halide的代码生成generator编译位目标机器的代码,提前生成一个.o目标文件和.h头文件。然后在独立的目标机器的应用的工程的源代码中通过头文件调用算法实现的计算函数,并在build的时候链接上.o文件,这样就得到一个可以在目标机器上运行的用Halide实现算法的程序了。

后记

本文只是简单介绍了Halide的基本知识,对于想要深入理解Halide的童鞋可以看官方的教程或者阅读源码,不论我们是设计算法的算法工程师亦或是在相关硬件平台上实现移植功能的底层工程师,Halide的思想都是值得我们去借鉴和回味的。

Halide的利用范围很广,我之所以想要了解下Halide是因为使用了TVM库,TVM借助了Halide的思想去实现神经网络算子的优化并且取得了不错的效果。TVM也可以通过这种方式实现自己的一些算法,算是兼容了halide而且更好用,还是建议大家入坑的。

之后还会写一些TVM的文章...努力复习中。如果不对的地方,大家轻喷呀。

参考资料:

上述所采用的图像部分来源于此:


更多精彩内容请访问FlyAI-AI竞赛服务平台;为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台;每周免费提供项目开源算法样例,支持算法能力变现以及快速的迭代算法模型。

挑战者,都在FlyAI!!!

Halide 构建 hello halide 随着人工智能,VR等领域的发展,我们对于高性能图像处理程序的需求越来越大;当前编程工具开发的高性能图像处理程序牺牲了程序的可读性,模块性,可移植性等;其主要原因是,当前程序将算法描述, 数据存储以及计算糅杂在一起。同时手工编写高性能图像处理程序开发效率较低,难以快速构建大量高性能图像处理代码。 在这样的背景下,MIT的研究人员专门为图像处理设计了一种程序语言——Halid... 阅读详情

相关推荐

深度版系统优化大师

很强的一个CMD,杀毒,优化,还有一个游戏

(三)大话深度学习编译器中的自动调优·Empirical Search

前面的第一篇与第二篇分别介绍了背景与一些相关概念,这第三篇我们开始切入正题,看下现代深度学习编译器中的自动调优(Auto-tuning)方法。Schedule的自动生成,一类方法是基于解析模型(Analytical model),然后使用一些经验公式来产生解;另一类方法是多面体编译技术。它将循环嵌套迭代空间建模为多面体,然后用如整数规划等数学方法求出能提升局部性与并行性的循环变换;还有一类就是经验搜索(Empirical search)方法。.........

世事难料,保持低调 5470

宝塔面板 python管理器2.0安装Mrdoc

自从python管理器从1.9升级2.0后,导致原教程不能使用。 故关于2.0新开教程如下。 关于python管理器2.0 最大的区别在于: python管理器2.0不再需要进入虚拟环境,直接在当前环境进行操作,但是需要指定python路径及pip路径。 1、新建mrdoc工程目录,下载源码: 新建目录【mrdoc】,进入mrdoc目录,并在宝塔ssh或或远程ssh中执行 ,下载源码 git clone https://gitee.com/yang_lite/MrDoc.git .

debj031634的专栏 5991

TVM: End-to-End Optimization Stack for Deep Learning 论文阅读

TVM是一个端到端的深度学习优化编译器栈,旨在解决跨异构硬件后端的高效部署问题。它通过分离算法描述、调度和硬件接口,支持从多种深度学习前端框架(如TensorFlow、MXNet、PyTorch等)到不同硬件目标的自动优化代码生成。TVM引入创新的调度原语,解决GPU和专用加速器中的跨线程内存重用、张量化计算和延迟隐藏等关键挑战。实验表明,TVM在嵌入式设备、服务器GPU和FPGA加速器上均能生成高性能代码,性能优于现有框架的手动优化实现。TVM的开源设计为深度学习系统的跨平台部署提供了灵活高效的解决方案。

人无远虑,必有近忧 2572

OpenAI,从推出更强大的Sora 2模型展开探索与未来发展前景+部署方法大全+文末邀请码*12

摘要 OpenAI推出的Sora 2是第二代AI视频生成模型,在多模态内容生成领域实现重大突破。文末足足有12个邀请码,速来速享!!!

aqfcca的博客 2248

【ros2】ros2_control 全面解析与实战指南

ros2_control 是 ROS2 生态中用于机器人实时控制的核心框架,旨在统一硬件接入接口、复用控制算法,避免重复开发,实现仿真与实机硬件的无缝迁移。其核心目标是简化不同硬件(传感器、执行器、完整系统)的集成流程,让开发者专注于控制逻辑而非硬件适配。创建类型功能包,依赖rclcpp。实现on_initupdate在中声明控制器插件,编译生成动态库。在 YAML 配置文件中指定自定义控制器类型,加载测试。

EnHengNa的博客 1471

AI编译器技术深探:TVM与Triton的算子优化哲学对比

通过构建多层可扩展IR系统(如Affine Dialect、GPU Dialect),MLIR实现了从算法描述到硬件指令的全流程统一表达。现代AI编译器的核心目标是通过计算与调度分离(Separation of Computation and Scheduling)实现算子的极致性能优化。TVM继承了Halide的衣钵,构建了分层编译架构。其Relay中间表示层专注于计算图的全局优化(如算子融合、常量折叠),而TVM层通过调度模板(Schedule Template)实现算子级优化。

AladdinEdu,你的AI学习实践工作坊。让想法落地,让研究加速。助力高校AI人才成长,点亮创新未来。 2648

STM32L051低功耗模式下LPUART的使用

STM32L0系列低功耗UART配置代码清单及注释,大家可参考学习使用。

嵌入式工程师 2104

Halide学习笔记--04--Vectorize, parallelize, unroll and tile

前言 接上, 本文学习Halide lesson_05_scheduling ** schedule Func in different ways ** 内容 本节主要学习几个概念: 矢量, 并行, 展开, 平铺 用来加速图像像素级计算的过程; row-major && column-major //row-major Func gradient; gradient(x, y) = x + y; ;;; //column-major gradient.reorder(y, x);

weixin_45331269的博客 537

Jetson AGX Orin平台Jetpack5升级Jetpack6,自研载板USB不工作问题调试

1. 问题描述将AGX Orin工业的新板放到定制的载板上当在ubuntu 22和Jetpack 6.1上使用SDKManager刷机时,lsusb为空,并且在引导时没有发现/注册xusb设备。USB C端口正常打开并供电,但似乎没有数据传输。在Ubuntu 18 Jetpack 5.1.4 SDKManager上同样刷机之后,usb C集线器在引导时被正确地发现,显示在lsusb中,并识别/挂载外部驱动器。

jzwjzw19900922的博客 1509

【思考模型框架】MECE原则,确保分析时,考虑到的所有因素,既不重叠又无遗漏

MECE原则,是Mutually Exclusive and Collectively Exhaustive的缩写,中文译为“相互独立、完全穷尽”。MECE原则,这是一种逻辑思维和组织信息的方法,旨在确保分析时既没有遗漏也没有重复。MECE原则,相互独立、完全穷尽(Mutually Exclusive,Collectively Exhaustive)的原则MECE原则,确保分析时考虑到的所有因素既不重叠又无遗漏。

奶爸的编程之路,也就一周冷个三天 2752

Halide教程Part01:build、语法、调度、流水线

本教程为Halide入门教程Part01。包含Halide的编译,基本语法,基本调度,流水线基本调度。

He11o_Liu的专栏 8091

25、BDS-3卫星信号性能与PPP-B2b增强消息完整性分析

本文对BDS-3卫星信号性能及PPP-B2b增强消息的完整性进行了系统分析。评估了MEO、IGSO和GEO卫星的轨道与时钟精度,得出BDS-3总体SISRE为0.25 m,系统完整性可覆盖99.999%的误差。分析表明SIS中断主要由时钟调整和轨道机动引起,个别卫星存在因境外监测受限导致的非计划中断。通过自研解码软件对PPP-B2b增强消息进行采集与解析,实验结果显示BDS-3和GPS的平均历元完整性率均高于98.55%,其中卫星掩码和时钟校正消息的完整性率超过99.8%。研究还发现IGSO卫星相比MEO卫

quiet的专栏 144

深度学习编译器架构解析:从高级IR到硬件优化的全流程设计

本文深入解析深度学习编译器从高级IR到硬件优化的全流程设计。作为连接AI模型与多样化硬件的关键桥梁,深度学习编译器通过前端、中端、后端三阶段,将高级模型描述转化为硬件高效执行的代码,并利用算子融合、自动调优等技术最大化硬件性能,是解决模型部署性能瓶颈的核心工具。

web99的专栏 363

「UG/NX」Block UI 曲线收集器CurveCollector

曲线收集器块用于选择一条或多条曲线,包括通过选择意图连接的末端曲线链。曲线选择意图用于此块,但它不支持“交点处停止”、“跟随圆角”、“中间链”和“特征内链”等规则选项。

何曾参静谧的博客 1090

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

快速入门Halide

Halide代码流程: 声明函数,变量,表达式 用变量、表达式等实现算法 使用调度策略对算法进行调度调优 调用函数的realize成员函数,对函数进行实现 写出数据 example 1: Halide::Func gradient; // Func对象表示了一个pipeline阶段。它是一个纯函数,定义了每个像素点对应的值。 Halide::Var x, y; // Var对象是Func的定义域,或者说是Func的参数。它们本身没有任何意义。Var用来索引 Halide::Expr e = x

Time_remember的专栏 1290

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

halide编程技术指南(连载二)

本文是halide编程指南的连载,已同步至公众号 目录 第二章 图像处理 第三章 检查生成的代码 第四章 使用tracing, print, 和print_when进行调试 第五章 Vectorize, parallelize, unroll and tile 你的代码 第二章 图像处理 本章演示如何传递输入图像并进行操作。 // halide提供了对应方法用于导入png图像...

Aoulun的博客 1381

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Halide教程:AOT编译实战与静态库生成

Halide作为一种图像处理语言,支持两种主要的编译模式:即时编译(JIT)和提前编译(AOT)。本教程重点讲解AOT( Ahead-Of-Time )编译模式,这种模式将Halide管道预先编译为静态库,而不是在运行时进行即时编译。 与JIT模式相比,AOT编译具有以下显著优势: 1. **运行时零编译开销**:所有编译工作都在构建阶段完成 2. **部署简单**:生成的可执行文件不依赖Ha

gitblog_00112的博客 556

halide编程技术指南(连载六)

本文是halide编程指南的连载,已同步至公众号 第十章 halide 编译 (AOT 编译) ​ // 第一部分:halide的编译 // 本节演示如何用halide到达传统编译器的功能,也就是提前编译. // 本课程分为两个文件。第一个构建一个halide管道并将其编译为静态库和头。第二节,使用该静态库实际运行管道。这意味着编译此代码是一个多步骤的过程。 // 在linux上,你可以像这样编译和运行: // g++ lesson_10_generate.cpp -g -std=c++1...

Aoulun的博客 834

TVM系列 - 图优化 - 算子融合

TVM系列 - 图优化 - 算子融合 图优化综述 声明一下,本文所有的理解都是基于个人理解。 图优化算是一个推理框架前端比较成熟的操作了,一般来说,针对模型做图优化有两个目的(对于通用框架来说,就加速减少计算一个目的了): 减少Node的数量, 不管是算子融合,还是无效节点去除,共同的目的就是减少整个graph中node的数量,因为对于框架来说,从一个node到另一个node之间就意味着数据的搬运。举个例子,我们知道算子合并最经典的就是将BN层融合到Conv层,我们姑且不论融合后计算量的减少,单单是减少了

Artyze的博客 1万+

halide 入门实战(1)

halide 入门实战

czwcc的博客 855

Halide 入门教程第1讲:熟悉Funcs,Vars和Exprs

// Halide tutorial lesson 1: Getting started with Funcs, Vars, and Exprs // Halide入门教程第一课:了解Funcs(函数),Vars(变量)和Exprs(表达式) // This lesson demonstrates basic usage of Halide as a JIT compiler for imagi...

tbzj_2000的博客 2204

自动生成硬件优化内核:陈天奇等人发布深度学习编译器TVM

有了 TVM,业界与学界开发者们可以快速、轻松地在各个系统(包括手机、嵌入式设备与低功耗芯片)上部署深度学习应用程序,同时无须担心资源与速度的限制。「TVM 作为神经网络和硬件后端之间的共同层,消除了为每类设备或服务器优化各自基础架构的需要。」TVM 项目负责人陈天奇表示,「我们的框架允许开发人员快速、轻松地部署和优化大量硬件设备上的深度学习系统。」项目链接:https://github.com/...

omnispace的博客 9645

深度学习编译中间件之NNVM(五)TVM论文阅读

参考文档 https://mp.weixin.qq.com/s/irvBbPKENiZX9G_6wh5c-Q 陈天奇等人提出TVM:深度学习自动优化代码生成器 https://arxiv.org/abs/1802.04799v1 TVM: End-to-End Optimization Stack for Deep Learning 摘要:现今,像Tensorflow,MXNet,C...

sanallen的专栏 1万+

Halide学习笔记----Halide tutorial源码阅读1

Halide入门教程01// Halide tutorial lesson 1: Getting started with Funcs, Vars, and Exprs // Halide入门教程第一课:了解Funcs(函数),Vars(变量)和Exprs(表达式)// This lesson demonstrates basic usage of Halide as a JIT compiler

luzhanbo207的博客 6923

Halide图像处理之(二)术语汇总

参考文档 https://en.wikipedia.org/wiki/Reduction_Operator 计算优化类术语 Reduction(约简) 在计算机科学中,reduction表示一种运算符类型(满足交换律[Commutative]、结合律[Associative]),常见于并行编程,用来缩减阵列中的元素到单个输出结果。reduction运算符可以将一个计算任务分解为...

sanallen的专栏 1663
上一篇: ICLR 2021杰出论文奖公布,DeepMind是最大赢家
下一篇: [论文]读 Transformers as Soft Reasoners over Language Harold Finch
iFlyAI
博客等级 码龄9年 238粉丝 134原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值