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

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

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

halide_set_ocl_device_type("gpu");

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

Первый вопрос: если я использую compute_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 в моем случае — true, а host_dirty — false)

Любая идея?

РЕДАКТИРОВАТЬ (ответить на dsharlet)

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

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

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

Вот мой код:

До Halide все было CPU (указатель), и мы перенесли его в 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;
}

Внешний галогенид (внешняя функция):

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

1

Решение

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

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

Внутри 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.

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

  • Compute_root не вызывает устройство, если вы используете его в одиночку.
  • Нам нужен gpu () gpu_tile () в коде для вызова процедуры GPU. (Кстати, вам нужно поместить все свои переменные внутри)
  • gpu_tile les, чем ваш предмет, вылетит из ваших вещей.
  • BoundaryCondition хорошо работает в GPU.
  • Перед вызовом функции extern, Func, который используется в качестве ввода, должен быть:
    f.compute_root (); f.gpu_tile (х, у, …, …); Compute_root на средней стадии не является неявным.
  • Если адрес dev равен 0, это нормально, мы повторно отправляем измерение, и extern будет вызван снова.
  • Последний этап как неявный compute_root ().
3

Другие решения

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

Если вы проверили, что указатели хоста не равны 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.

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

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

0

По вопросам рекламы [email protected]