2014-10-08 2 views
1

Halide에서 extern 함수를 사용하려고합니다. 내 맥락에서, 나는 GPU에서 그것을하고 싶다.GPU가있는 Halide에서 extern 사용

opencl 문을 사용하여 AOT 컴파일로 컴파일합니다. 물론 는 OpenCL을 여전히 CPU를 사용할 수 있습니다, 그래서이 사용 : 지금

halide_set_ocl_device_type("gpu"); 

을, 모든 compute_root에서 일정이다().

첫 번째 질문은 compute_root() 및 OpenCL gpu를 사용하면 내 프로세스가 일부 CopyHtoD 및 DtoH가있는 장치에서 계산 되었습니까? (또는 호스트 버퍼에있을 것입니다)

두 번째 질문은 extern 기능과 관련이 있습니다. 일부 알고리즘은 Halide가 아니기 때문에 extern 호출을 사용합니다. 통근 호 :

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

통근자가 검색 : 통근 "C"의 INT (w INT에서 buffer_t *, INT의 H, INT z의 buffer_t * 아웃) cool_foo {...}

그러나에 cool_foo 함수, 내 buffer_t는 호스트 메모리에만로드됩니다. dev 주소는 0 (기본값)입니다.

내가 알고리즘 전에 메모리를 복사하려고하면 :

halide_copy_to_dev(NULL, &in); 

그것은 아무것도하지 않습니다.

나는에서만 사용 가능 장치 메모리 한 경우 : null 인

in.host = NULL; 

내 호스트 포인터하지만, 장치 주소는 여전히 0

입니다 (내 경우에 dev_dirty있는 경우에 true와 host_dirty은 false입니다)

아이디어가 있으십니까?

편집

여기 내 코드의 구조이다 (dsharlet에 응답하려면) :

구문 분석 데이터를 올바르게 CPU에. -> GPU에 버퍼 전송 (Halide_copy_to_dev ... 사용) -> Halide 구조에 입력, 파라미터 읽기 및 경계 조건 추가 -> 내 외부 함수로 이동 -> ...

I 내 extern 함수에 유효한 buffer_t가 없다. 나는 compute_root()에서 모든 것을 스케줄하지만 HL_TARGET = host-opencl을 사용하고 ocl을 gpu로 설정합니다. 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) 

내부 할로겐을 TRANSFERT했다 : 여기

내 코드의

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.. 
} 
+0

외부 스테이지 전에 스테이지를 정의하고 예약하는 코드를 공유 할 수 있습니까? GPU에서 일정이 잡혀 있습니까? 그렇지 않다면 나는 당신이보고있는 행동이 기대된다고 생각합니다. – dsharlet

답변

0

외부 배열 함수에 대한 경계 유추 프로토콜을 알고 있습니까? 이것은 버퍼의 호스트 포인터가 NULL 일 때 발생합니다. (간단히 말해,이 경우에는 NULL 호스트 포인터가있는 buffer_t 구조체의 extent 필드를 채워야하고, 다른 작업은하지 않아야합니다.) 이미 처리했다면 위의 내용을 무시하십시오.

호스트 포인터가 모든 버퍼에 대해 NULL이 아닌지 테스트 한 경우 halide_copy_to_dev를 호출해야합니다. 버퍼가 어디서 왔는지에 따라 복사 부분이 발생하도록 미리 host_dirty를 true로 명시 적으로 설정해야 할 수도 있습니다. (Halide가이 권리를 얻고 버퍼가 CPU의 이전 파이프 라인 단계에서 나온 것이라면 이미 설정 되었으면 좋겠다.하지만 버퍼가 Halide 외부의 것으로부터 나온다면 더티 비트는 초기화에서 거짓 일 것입니다 .hide_dev_malloc이 설정해야합니다. 장치 메모리를 할당하는 경우 dev_dirty이고 현재는 그렇지 않습니다.)

hal_copy_to_dev를 호출 한 후 dev 필드가 채워 져야합니다. 가장 먼저 할 일은 halide_dev_malloc입니다. 당신은 스스로 명시 적으로 halogen_dev_malloc을 호출하고, host_dirty를 설정 한 다음 halogen_copy_to_dev를 호출 해 볼 수 있습니다.

이전 단계는 호스트 또는 GPU에 있습니까? 그것이 GPU에 있다면, 나는 또한 입력 버퍼가 GPU에있을 것으로 기대한다.

이 API는 작업이 필요합니다. 내가 도움이 될 somethings의 첫 번째 리팩토링 중간에 있지만 궁극적으로 그것은 buffer_t 구조를 변경해야합니다. 대부분의 작업을 수행 할 수는 있지만, 올바른 방법으로 halide_dev * API를 호출 할뿐만 아니라 host_dirty 및 dev_dirty 비트를 수정해야합니다. 양해 해 주셔서 감사합니다.

+0

고맙습니다 잘만. 내 버퍼가 NULL이면 익스텐트 필드를 채우고 extern을 종료합니다. 그러나 호스트가 NULL이고 Dev가 무언가를 갖고 싶다면 무엇을해야합니까? 내 파이프 라인의 이전 단계 (할라이드 이전)는 CPU에 있지만, GPU에서만 Halide에서 내 부분을 사용하고 싶습니다. 마지막으로, compute_root에서 예약하고 HL_TARGET = host-opencl을 사용하고 GPU를 선택하면 코드가 GPU (최적화되지 않은) 또는 CPU에서 실행됩니까? – Darkjay

3

내 문제의 해결책을 찾았습니다.

답변을 코드에 게시하려면 여기를 클릭하십시오. (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_test.cpp) 당신은 일을 가진 halide_func을 컴파일 할 수

#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); 

} 

모든 Extern 기능을 사용하도록 테스트합니다 (4).

여기에 내가 가진 결론이 있습니다. (Zalman and zougloub에게 감사드립니다)

  • Compute_root 단독으로 사용하는 경우 장치를 호출하지 마십시오.
  • GPU 루틴을 호출하기 위해 코드에서 gpu_tile()의 gpu()가 필요합니다. (BTW, 내부에 모든 변수를 넣어야합니다.)
  • gpu_tile보다 항목이 크래쉬됩니다.
  • BoundaryCondition은 GPU에서 잘 작동합니다.
  • extern 함수를 호출하기 전에 입력으로 전달되는 Func은 다음과 같아야합니다. f.compute_root(); f.gpu_tile (x, y, ..., ...); 중간 단계의 compute_root는 함축적이지 않습니다.
  • dev 주소가 0이면 정상이며 치수를 다시 보내면 extern이 다시 호출됩니다.
  • 마지막 단계는 compute_root() 암시 적입니다.
관련 문제