Использование extern на Halide с GPU

Я пытаюсь использовать функцию extern в Halide. В моем контексте я хочу сделать это на GPU.

Я компилирую в компиляции AOT с оператором opencl. Конечно, opencl все еще может использовать процессор, поэтому я использую это:

halide_set_ocl_device_type("gpu");

На данный момент все запланировано в calculate_root().

Первый вопрос: если я использую calculate_root() и OpenCL gpu, будет ли мой процесс вычисляться на устройстве с некоторыми CopyHtoD и DtoH? (Или это будет в буфере хоста)

Второй вопрос, больше связанный с внешними функциями. Мы используем некоторый внешний вызов, потому что часть нашего алгоритма не реализована в Halide. Внешний вызов:

foo.define_extern("cool_foo", args, Float(32), 4);

Внешнее извлечение: extern "C" int cool_foo(buffer_t * in, int w, int h, int z, buffer_t * out){ .. }

Но, в функции cool_foo, мой buffer_t загружается только в память хоста. Адрес разработчика — 0 (по умолчанию).

Если я попытаюсь скопировать память перед алгоритмом:

halide_copy_to_dev(NULL, &in);

Это ничего не делает.

Если я сделаю доступной только память устройства:

in.host = NULL;

Мой указатель хоста равен нулю, но адрес устройства по-прежнему равен 0.

(в моем случае dev_dirty истинно, а host_dirty ложно)

Есть идеи?

РЕДАКТИРОВАТЬ (чтобы ответить dsharlet)

Вот структура моего кода:

Правильно анализировать данные на ЦП. --> Отправил буфер на GPU (используя halide_copy_to_dev...) --> Войдите в структуру Halide, прочитайте параметр и добавьте граничное условие --> Зайдите в мою внешнюю функцию -->...

У меня нет действительного buffer_t в моей внешней функции. Я планирую все в calculate_root(), но использую HL_TARGET=host-opencl и устанавливаю ocl на gpu. Перед входом в Halide я могу прочитать адрес своего устройства, и все в порядке.

Вот мой код:

До Halide все было процессором (указатель), и мы перенесли его на GPU.

buffer_t k = { 0, (uint8_t *) k_full, {w_k, h_k, num_patch_x * num_patch_y * 3}, {1, w_k, w_k * h_k}, {0}, sizeof(float), };
#if defined( USEGPU )
    // Transfer into GPU
    halide_copy_to_dev(NULL, &k);
    k.host_dirty = false;
    k.dev_dirty = true;
    //k.host = NULL; // It's k_full
#endif
halide_func(&k)

Внутри Галида:

ImageParam ...
Func process;
process = halide_sub_func(k, width, height, k.channels());
process.compute_root();

...

Func halide_sub_func(ImageParam k, Expr width, Expr height, Expr patches)
{
    Func kBounded("kBounded"), kShifted("kShifted"), khat("khat"), khat_tuple("khat_tuple");
    kBounded = repeat_image(constant_exterior(k, 0.0f), 0, width, 0, height, 0, patches);
    kShifted(x, y, pi) = kBounded(x + k.width() / 2, y + k.height() / 2, pi);

    khat = extern_func(kShifted, width, height, patches);
    khat_tuple(x, y, pi) = Tuple(khat(0, x, y, pi), khat(1, x, y, pi));

    kShifted.compute_root();
    khat.compute_root();

    return khat_tuple;
}

За пределами галоида (функция Extern):

inline .... 
{
   //The buffer_t.dev and .host are 0 and null. I expect a null from the host, but the dev..
}

person Darkjay    schedule 08.10.2014    source источник
comment
Можете ли вы поделиться кодом, который определяет и планирует этап перед внешним этапом? Это запланировано на GPU? Если нет, я думаю, что поведение, которое вы видите, ожидаемо.   -  person dsharlet    schedule 09.10.2014


Ответы (2)


Я нахожу решение своей проблемы.

Я публикую ответ в коде только здесь. (Поскольку я провел небольшой автономный тест, имя переменной не совпадает)

Внутри Halide: (Halide_func.cpp)

#include <Halide.h>


 using namespace Halide;

 using namespace Halide::BoundaryConditions;

 Func thirdPartyFunction(ImageParam f);
 Func fourthPartyFunction(ImageParam f);
 Var x, y;

 int main(int argc, char **argv) {
    // Input:
    ImageParam f( Float( 32 ), 2, "f" );

    printf(" Argument: %d\n",argc);

    int test = atoi(argv[1]);

    if (test == 1) {
        Func f1;
        f1(x, y) = f(x, y) + 1.0f;
        f1.gpu_tile(x, 256);
        std::vector<Argument> args( 1 );
        args[ 0 ] = f;
        f1.compile_to_file("halide_func", args);

    } else if (test == 2) {
        Func fOutput("fOutput");
        Func fBounded("fBounded");
        fBounded = repeat_image(f, 0, f.width(), 0, f.height());
        fOutput(x, y) = fBounded(x-1, y) + 1.0f;


        fOutput.gpu_tile(x, 256);
        std::vector<Argument> args( 1 );
        args[ 0 ] = f;
        fOutput.compile_to_file("halide_func", args);

    } else if (test == 3) {
        Func h("hOut");

        h = thirdPartyFunction(f);

        h.gpu_tile(x, 256);
        std::vector<Argument> args( 1 );
        args[ 0 ] = f;
        h.compile_to_file("halide_func", args);

    } else {
        Func h("hOut");

        h = fourthPartyFunction(f);

        std::vector<Argument> args( 1 );
        args[ 0 ] = f;
        h.compile_to_file("halide_func", args);
    }
 }

 Func thirdPartyFunction(ImageParam f) {
     Func g("g");
     Func fBounded("fBounded");
     Func h("h");
     //Boundary
     fBounded = repeat_image(f, 0, f.width(), 0, f.height());
     g(x, y) = fBounded(x-1, y) + 1.0f;
     h(x, y) = g(x, y) - 1.0f;

     // Need to be comment out if you want to use GPU schedule.
     //g.compute_root(); //At least one stage schedule alone
     //h.compute_root();

     return h;
 }

Func fourthPartyFunction(ImageParam f) {
    Func fBounded("fBounded");
    Func g("g");
    Func h("h");

    //Boundary
    fBounded = repeat_image(f, 0, f.width(), 0, f.height());

    // Preprocess
    g(x, y) = fBounded(x-1, y) + 1.0f;

    g.compute_root();
    g.gpu_tile(x, y, 256, 1);


    // Extern
    std::vector < ExternFuncArgument > args = { g, f.width(), f.height() };
    h.define_extern("extern_func", args, Int(16), 3);

    h.compute_root();
    return h;
}

Внешняя функция: (external_func.h)

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <cinttypes>
#include <cstring>
#include <fstream>
#include <map>
#include <vector>
#include <complex>
#include <chrono>
#include <iostream>


#include <clFFT.h> // All OpenCL I need are include.

using namespace std;
// Useful stuff.
void completeDetails2D(buffer_t buffer) {
    // Read all elements:
    std::cout << "Buffer information:" << std::endl;
    std::cout << "Extent: " << buffer.extent[0] << ", " << buffer.extent[1] << std::endl;
    std::cout << "Stride: " << buffer.stride[0] << ", " << buffer.stride[1] << std::endl;
    std::cout << "Min: " << buffer.min[0] << ", " << buffer.min[1] << std::endl;
    std::cout << "Elem size: " << buffer.elem_size << std::endl;
    std::cout << "Host dirty: " << buffer.host_dirty << ", Dev dirty: " << buffer.dev_dirty << std::endl;
    printf("Host pointer: %p, Dev pointer: %" PRIu64 "\n\n\n", buffer.host, buffer.dev);
}

extern cl_context _ZN6Halide7Runtime8Internal11weak_cl_ctxE;
extern cl_command_queue _ZN6Halide7Runtime8Internal9weak_cl_qE;


extern "C" int extern_func(buffer_t * in, int width, int height, buffer_t * out)
{
    printf("In extern\n");
    completeDetails2D(*in);
    printf("Out extern\n");
    completeDetails2D(*out);

    if(in->dev == 0) {
        // Boundary stuff
        in->min[0] = 0;
        in->min[1] = 0;
        in->extent[0] = width;
        in->extent[1] = height;
        return 0;
    }

    // Super awesome stuff on GPU
    // ...

    cl_context & ctx = _ZN6Halide7Runtime8Internal11weak_cl_ctxE; // Found by zougloub
    cl_command_queue & queue = _ZN6Halide7Runtime8Internal9weak_cl_qE; // Same

    printf("ctx: %p\n", ctx);

    printf("queue: %p\n", queue);

    cl_mem buffer_in;
    buffer_in = (cl_mem) in->dev;
    cl_mem buffer_out;
    buffer_out = (cl_mem) out->dev;

    // Just copying data from one buffer to another
    int err = clEnqueueCopyBuffer(queue, buffer_in, buffer_out, 0, 0, 256*256*4, 0, NULL, NULL);

    printf("copy: %d\n", err);

    err = clFinish(queue);

    printf("finish: %d\n\n", err);

    return 0;
}

Наконец, вещи, не относящиеся к Halide: (Halide_test.cpp)

#include <halide_func.h>
#include <iostream>
#include <cinttypes>

#include <external_func.h>

// Extern function available inside the .o generated.
#include "HalideRuntime.h"

int main(int argc, char **argv) {

    // Init the kernel in GPU
    halide_set_ocl_device_type("gpu");

    // Create a buffer
    int width = 256;
    int height = 256;
    float * bufferHostIn = (float*) malloc(sizeof(float) * width * height);
    float * bufferHostOut = (float*) malloc(sizeof(float) * width * height);

    for( int j = 0; j < height; ++j) {
        for( int i = 0; i < width; ++i) {
            bufferHostIn[i + j * width] = i+j;
        }
    }

    buffer_t bufferHalideIn = {0, (uint8_t *) bufferHostIn, {width, height}, {1, width, width * height}, {0, 0}, sizeof(float), true, false};
    buffer_t bufferHalideOut = {0, (uint8_t *) bufferHostOut, {width, height}, {1, width, width * height}, {0, 0}, sizeof(float), true, false};

    printf("IN\n");
    completeDetails2D(bufferHalideIn);
    printf("Data (host): ");
    for(int i = 0; i < 10; ++ i) {
        printf(" %f, ", bufferHostIn[i]);
    }
    printf("\n");

    printf("OUT\n");
    completeDetails2D(bufferHalideOut);

    // Send to GPU
    halide_copy_to_dev(NULL, &bufferHalideIn);
    halide_copy_to_dev(NULL, &bufferHalideOut);
    bufferHalideIn.host_dirty = false;
    bufferHalideIn.dev_dirty = true;
    bufferHalideOut.host_dirty = false;
    bufferHalideOut.dev_dirty = true;
    // TRICKS Halide to force the use of device.
    bufferHalideIn.host = NULL;
    bufferHalideOut.host = NULL;

    printf("IN After device\n");
    completeDetails2D(bufferHalideIn);

    // Halide function
    halide_func(&bufferHalideIn, &bufferHalideOut);

    // Get back to HOST
    bufferHalideIn.host = (uint8_t*)bufferHostIn;
    bufferHalideOut.host = (uint8_t*)bufferHostOut;
    halide_copy_to_host(NULL, &bufferHalideOut);
    halide_copy_to_host(NULL, &bufferHalideIn);

    // Validation
    printf("\nOUT\n");
    completeDetails2D(bufferHalideOut);
    printf("Data (host): ");
    for(int i = 0; i < 10; ++ i) {
        printf(" %f, ", bufferHostOut[i]);
    }
    printf("\n");

    // Free all
    free(bufferHostIn);
    free(bufferHostOut);

}

Вы можете скомпилировать halide_func с тестом 4, чтобы использовать все функциональные возможности Extern.

Вот некоторые выводы, которые у меня есть. (Спасибо Zalman и zougloub)

  • Compute_root не вызывает устройство, если вы используете его отдельно.
  • Нам нужен gpu() из gpu_tile() в коде для вызова подпрограммы GPU. (Кстати, вам нужно поместить всю свою переменную внутрь)
  • gpu_tile, чем ваш предмет приведет к сбою.
  • BoundaryCondition хорошо работает в графическом процессоре.
  • Прежде чем вызывать внешнюю функцию, Func, которая используется в качестве входных данных, должна быть: f.compute_root(); f.gpu_tile(x,y,...,...); Compute_root на промежуточном этапе не является неявным.
  • Если адрес разработчика равен 0, это нормально, мы повторно отправляем измерение, и внешний вызов будет вызван снова.
  • Последний этап как неявный метод calculate_root().
person Darkjay    schedule 16.10.2014

Знаете ли вы о протоколе вывода границ для функций внешнего массива? Это происходит, когда указатель хоста любого буфера равен NULL. (Вкратце, в этом случае вам нужно заполнить поля экстента структур buffer_t, которые имеют NULL указатели хоста, и больше ничего не делать.) Если вы уже позаботились об этом, то игнорируйте вышеизложенное.

Если вы проверили, что указатели хоста не равны NULL для всех буферов, вызов halide_copy_to_dev должен работать. Возможно, вам придется заранее явно установить для host_dirty значение true, чтобы выполнить часть копирования, в зависимости от того, откуда был взят буфер. (Я надеюсь, что Halide понимает это правильно, и он уже установлен, если буфер получен из предыдущей стадии конвейера на ЦП. Но если буфер получен из чего-то вне Halide, грязные биты, вероятно, ложны из-за инициализации. Кажется, halide_dev_malloc должен установить dev_dirty, если он выделяет память устройства, а в настоящее время это не так.)

Я ожидаю, что поле dev будет заполнено после вызова halide_copy_to_dev, так как первое, что он делает, это вызывает halide_dev_malloc. Вы можете попробовать вызвать halide_dev_malloc самостоятельно, установить host_dirty и затем вызвать halide_copy_to_dev.

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

Этот API нуждается в доработке. Я занимаюсь первым рефакторингом того, что может помочь, но в конечном итоге это потребует изменения структуры buffer_t. Можно заставить работать большинство вещей, но для этого потребуется изменить биты host_dirty и dev_dirty, а также правильно вызвать API-интерфейсы halide_dev*. Спасибо за терпеливость.

person Zalman Stern    schedule 08.10.2014
comment
Спасибо, Залман. Если мой буфер равен NULL, я заполняю поля экстентов и выхожу из extern. Но если я хочу, чтобы хост был NULL, а у Dev что-то было, что мне нужно сделать? Предыдущий этап моего конвейера (до halide) выполняется на CPU, но я хочу использовать свою часть в Halide только на GPU. И последнее, если я запланирую в calculate_root и использую HL_TARGET=host-opencl и выберу графический процессор, будет ли код работать на графическом процессоре (без оптимизации) или на процессоре? - person Darkjay; 09.10.2014