(Мастер оптимизации глубокого обучения) Старший TVM, расскажите, что такое HALIDE~

глубокое обучение

предисловие

Несколько дней назад использовал его сноваTVM, думая о том, можно ли его косвенно заменитьTensorRTВ качестве бэкэнда для развертывания серверов GPU. TensorRT действительно мощный на своем собственном графическом процессоре, и это также «проект с открытым исходным кодом», на котором сосредоточено старое семейство Huang. ноTensorRTЕсть еще несколько мелких недостатков:

  • Модели, оптимизированные для вывода, привязаны к конкретному графическому процессору, например, модели, созданные на 1080TI, нельзя использовать на 2080TI.
  • Более высокая версия TensorRT зависит от более высокой версии CUDA, а более высокая версия CUDA зависит от более высокой версии драйвера.
  • Хотя TensorRT прост в использовании, вывод оптимизации рассуждений по-прежнему является закрытым исходным кодом.Как и алхимия глубокого обучения, он также похож на черный ящик.Мы не уверены, можно ли успешно преобразовать импортированную модель. Если это не удается, вы не можете напрямую изменить исходный код, и вы всегда чувствуете, что с ним что-то не так.

Вышеуказанные недостатки просто жалуются (легкий спрей и легкий спрей) На самом деле эти проблемы не большие проблемы. Но если поставитьПроизводственная среда, некоторые из вышеперечисленных ситуаций могут вызвать небольшие неприятности (ведь в производственной среде слишком много правил и норм), да и пользоваться им не так удобно.

Если вы не знаете, что такое TVM, вы можете поискать его на Zhihu или посмотреть две статьи, которые я написал ранее:

Тем не менее, эта статья не для обсуждения TVM, помимо двух вышеперечисленных статей, я также написал несколько других анализов TVM (третья и четвертая статьи), но мне лень и иногда не хватает времени, поэтому я не Я не организую это. Я опубликую это после того, как организую это. Эта статья на самом деле пытается представить то, что я написал ранее.HalideОдна статья, связанная с TVM, использует эту возможность, чтобы представить Yiha.

Что такое галид

HalideЭто язык DSL (предметный язык), связанный с обработкой изображений, который использует C++ в качестве основного языка и является доменно-ориентированным языком на протяжении всего процесса. Основная функция заключается в достижении лежащего в основе ускорения алгоритма на мягком и жестком уровне (не связанном с дизайном самого алгоритма), и я чувствую, что необходимо иметь определенное понимание этого. потому что либоТрадиционные методы обработки изображений или приложения для глубокого обученияВсе они более или менее используют идею галогенида.

Среди них некоторые алгоритмы в OpenCV (традиционная библиотека обработки изображений) используют серверную часть Halide, а TVM (компилятор нейронной сети) также использует идею Halide для оптимизации операторов нейронной сети.

HALIDE对算法的加速效果

Итак, для чего используется Halide?Посмотрите на картинку выше,такая же обработка алгоритма(локальное преобразование Лапласа),алгоритм написанный на прямом языке C++ очень медленный,компания Adobe потратила 3 ​​месяца на анализ этого алгоритма.Есть оптимизации(оптимизированные вручную ), которые делают этот алгоритм в 10 раз быстрее, но если вы используете Halide, вы можете сделать этот алгоритм быстрее, чем обычный прямой алгоритм, всего за несколько строк кода.20 раз.

Одним словом,HalideЭто значительно экономит нам время на ручную оптимизацию базового алгоритма, позволяя нам сосредоточиться только на разработке алгоритма.

Алгоритм и лежащий в его основе процесс оптимизации,HalideПомогите нам сделать это.

Почему Halide может оптимизировать алгоритм

Отличительной чертой Halide является то, что реализация вычислений алгоритма изображения (функция и выражение) и планирование (расписание) этих вычислений на вычислительном аппаратном блоке разделены., отправка которого находится в Function. Наконец, весь алгоритм изображения преобразуется в высокоэффективный многоуровневый цикл for.Частичное разделение диапазона данных и загрузка данных цикла for выполняются Halide, а загрузка данных и вычисление алгоритма могут быть реализованы с наложением.Задержка. Расписание Halide может быть запрограммировано программистом для указания некоторых стратегий, указания размера буфера оборудования и соответствующих настроек линии буфера, чтобы можно было достичь эффективного планирования вычислительных блоков в соответствии с характеристиками различного вычислительного оборудования, в то время как вычислительная реализация алгоритма изображения не требует коррекции.

Приведенная выше часть взята из Чжиху:Ууху. Call.com/question/29…

«Треугольные силы», определяющие производительность алгоритма при выполнении на определенной аппаратной платформе, следующие.

TIM截图20190417101516

Среди них дизайн самого алгоритма является одним из аспектов, и хороший алгоритм часто намного эффективнее. Другой аспект — это организация порядка вычислений в алгоритме, а то, что Halide может изменить, — это порядок вычислений нашего алгоритма на определенной аппаратной платформе:

TIM截图20190417102405

Среди них Halide может быть реализован для алгоритма на аппаратной платформепараллельнои хорошокогерентность кэша:

Например

Продемонстрируем на примере классического размытого изображения в Halide (следующий код также может проверить наблюдения на вашем собственном компьютере), здесь мы используем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 (Single Instruction Multiple Data Stream), а также тайлинг ( Мозаика), расширение (распространенные методы, такие как развертывание и векторизация. Он полностью использует производительность оборудования, чтобы максимизировать скорость выполнения программы без изменения конструкции самого алгоритма.

TIM截图20190417151611

Официальный пример

Галид как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, чтобы определить функцию, которая будет выполняться, и назовите ее.gradient.

Func gradient("gradient");

В это время мы определяем логику выполнения каждой точки в функции, для(x,y)Логика, выполняемая в этот момент,x + y. где x и y оба являются Var, иx + yЭта операция будет автоматически преобразована в тип 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(...) = ...

Сплит Сплит

Мы можем разделить каждое измерение, если мы по-прежнему находимся в начале строки, но мы разделяем ось 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);
        }
    }
}

плавкий предохранитель

Или мы не разделяем и не объединяем оси 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.Однако этот метод операции не является практичным, то есть фактическимПорядок расчета не изменился.

кафельная плитка

На этом этапе мы войдем в 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

Преимущество этой плитки в том, что вы можетеЧтобы воспользоваться соседними пикселями, например, для операций размытия, мы используем перекрывающиеся входные данные (то есть есть случай, когда элемент используется дважды).), если принять этот метод расчета, производительность расчета может быть значительно ускорена.

векторизованный вектор

Векторизация означает, что мы используем технологию 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);

Выражается на С++ как:

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

развернуть

Если несколько пикселей в изображении совместно используют перекрывающиеся данные одновременно, мы можем развернуть цикл, чтобы общие данные вычислялись или загружались только один раз.

В дальнейшем мы разобьем ось абсцисс на внутреннюю и внешнюю, потому что каждый раз, когда значение внутренней функции увеличивается с 0 до 1, если мы расширим ось абсцисс внутреннего тестового цикла, нет необходимости читать его здесь. каждый раз Значение 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);
        }
    }
}

Слияние, мозаичное размещение и распараллеливание

На этом этапе мы объединяем операции слияния, разбиения на мозаику и параллельные операции для работы с изображением 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);
        }
    }
}

После визуализации можно увидеть, что четыре области верхний левый, нижний левый, верхний правый и нижний правый в 8х8 выполняются почти одновременно (tile_index), и каждая область рассчитывается так же, как и предыдущий участок тайла, но на этот раз заменены параллельными вычислениями:

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 для написания описанного в начале статьи алгоритма размытия, то он будет выглядеть так:

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, базовая библиотека оптимизации, имеет несколько замечательных функций:

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.

Автоматический поиск может использоваться каждым оптимизатором с пространством поиска, потому что каждый раз, когда выполняется операция оптимизации, коэффициент оптимизации неизвестен.Для различного оборудования разные конфигурации могут привести к выполнению Скорость также отличается. Таким образом, необходим автоматический случайный фактор пространства поиска.

метапрограммирование

Идеи Халида тесно связаны с метапрограммированием, не только с его идеями дизайна или идеями его исполнения, но также следуют идеям метапрограммирования, то есть код не имеет четкой логики выполнения перед компиляцией, только после компиляции будет это форма Выполнить логику.

TIM截图20190417103037

Другие связанные

halideПоскольку это независимый от алгоритмов низкоуровневый оптимизатор, должно быть много приложений в сочетании с современным глубоким обучением.OpenCVБиблиотека использует halide для оптимизации базового оператора нейронной сети, и соответствующие выводы тестов находятся вздесь, но мы обнаружили, что скорость нейронной сети, использующей halide, не так хороша, как у обычной реализации на C++. Причина не уверена, связано ли это с конструкцией самого галоида или с совместимостью галоидной оптимизации и операторов нейронной сети.Короче говоря, если вы хотите использовать галоид для достижения реального ускорения, вам все равно нужно немного подождать (вы все еще используете TVM после долгого ожидания). ).

TIM截图20190417101052

Связанные вопросы:

stackoverflow.com/questions/4…

Кроме того, есть два способа запустить Halide, один из нихJITрежим, другойAOTрежим. Режим JIT более удобен в использовании, вы можете напрямую инкапсулировать алгоритм и генератор кода Halide в класс, вызывать этот класс в других частях программы, а затем компилировать и использовать его.

Во встроенной среде или среде кросс-компиляции обычно используется режим AOT.В это время необходимо вызвать функцию компилятора для генерации кода алгоритма и генератор кода Halide для компиляции кода целевой машины и создания файла . o объектный файл и заголовочный файл .h заранее. Затем вызовите функцию вычисления, реализованную алгоритмом, через заголовочный файл в исходном коде проекта приложения независимой целевой машины и свяжите файл .o при сборке, чтобы получить алгоритм, реализованный Halide, который можно запустить на целевая машина.программа.

постскриптум

Эта статья лишь кратко знакомит с базовыми знаниями о Halide.Для детской обуви, которые хотят глубже понять Halide, вы можете прочитать официальное руководство или прочитать исходный код, независимо от того, являемся ли мы инженерами-алгоритмами, разрабатывающими алгоритмы, или инженерами низкого уровня, которые реализуют функции переноса. на соответствующих аппаратных платформах.Идеи Halide достойны нашей ссылки и послевкусия.

Галогенид имеет широкий спектр применения.Я хочу знать о галогениде, потому что я использую егоTVMБиблиотека TVM использует идею Halide для оптимизации оператора нейронной сети и достижения хороших результатов. TVM также может реализовать некоторые собственные алгоритмы таким образом, что совместимо сhalideА лучше использовать, в яму рекомендуется зайти всем.

В будущем я напишу несколько статей о TVM... Я усердно работаю над обзором. Если это не так, пожалуйста, распылите его слегка.

Использованная литература:

Часть изображения, использованного выше, взята из этого:

дразни меня

  • Если вы похожи на меня, Лао Пан очень хочет с вами общаться;
  • Если вам нравится контент Лао Пана, подпишитесь и поддержите его.
  • Если вам понравилась моя статья, надеюсь, она вам понравится? Избранное ? Комментарии ? Три в ряд~

Я хочу знать, как Лао Пан научился наступать на яму, и я хочу обменяться со мной вопросами ~ Пожалуйста, обратите внимание на публичный аккаунт "старый блог". Лао Пан также организует некоторые из своих частных коллекций, надеясь помочь всем, нажмитеТаинственный порталПолучать.