opencl_program と引数のバインド – パート 4

インテル® oneTBB

この記事は、インテル® デベロッパー・ゾーンに公開されている「opencl_program and argument binding」(https://software.intel.com/en-us/blogs/2015/12/16/opencl-program-and-argument-binding/) の日本語参考訳です。


この記事は、インテル® スレッディング・ビルディング・ブロック (インテル® TBB) 4.4 Update 2 以降で利用可能な新しいノード opencl_node について説明するシリーズのパート 4 です。このノードは、インテル® TBB のフローグラフで OpenCL* デバイスの利用と連携を可能にします。このシリーズの以前の記事は、opencl_node の概要 (パート 1)opencl_node の基本インターフェイス (パート 2)opencl_node でのデバイスの選択 (パート 3) をご覧ください。

パート 4 では、OpenCL* プログラムを指定し、プログラムからカーネルを選択して、カーネル呼び出しに引数をバインドする方法を説明します。

opencl_program

以前の例では、OpenCL* C ソースコードで提供される opencl_node を使用しました。opencl_node は、SPIR とプリコンパイル済みカーネルもサポートします。プログラムの種類を指定するには、opencl_program クラスを使用します。

enum class opencl_program_type {
    SOURCE,
    PRECOMPILED,
    SPIR
};

template <typename Factory = default_opencl_factory>
class opencl_program {
public:
    opencl_program( opencl_program_type type,
                    const std::string& program_name );
};

SPIR とプリコンパイル済みカーネルは、インテル® SDK for OpenCL* Applications に含まれる Kernel Builder for OpenCL* API を利用して作成できます。

  • SPIR カーネルを作成するには、次のコマンドを実行します。
ioc64 -cmd=build -input=hello_world.cl -spir64=hello_world.spir -bo="-cl-std=CL1.2"
  • インテル® プロセッサー・グラフィックス向けのプリコンパイル済みカーネルを作成するには、次のコマンドを実行します。
ioc64 -cmd=build -input=hello_world.cl -ir=hello_world.clbin -bo="-cl-std=CL1.2" -device=gpu

次の例は、SPIR とプリコンパイル済みカーネルの使用法を示します。

#define TBB_PREVIEW_FLOW_GRAPH_NODES 1
#include "tbb/flow_graph_opencl_node.h"

#include <algorithm>

int main() {
    using namespace tbb::flow;
    
    opencl_graph g;
    opencl_device_list dl = g.available_devices();
    opencl_device_list::iterator it = std::find_if( dl.begin(), dl.end(),
        []( opencl_device d ) -> bool {
            // SPIR 対応のインテル(R) プロセッサー・グラフィックスを搭載した
            // デバイスの選択
            cl_uint bitness;
            d.info( CL_DEVICE_ADDRESS_BITS, bitness );
            bool isSpir = d.extension_available("cl_khr_spir") && bitness == 64;
            bool isPrecompiled = d.platform_name() == "Intel(R) OpenCL"
                                 && d.type() == CL_DEVICE_TYPE_GPU;
            return isSpir && isPrecompiled;
        } );
    if ( it == dl.end() ) return -1;
    g.opencl_factory().init( { *it } );
    
    // SPIR カーネルを使用する opencl_program と opencl_node の作成
    opencl_program<> spirProgram( opencl_program_type::SPIR, "hello_world.spir" );
    opencl_node<tuple<opencl_buffer<cl_char>>> clPrintSPIR( g, 
        spirProgram, "print" );
    // プリコンパイル済みカーネルを使用する opencl_node の作成
    opencl_node<tuple<opencl_buffer<cl_char>>> clPrintPrecompiled( g,
        { opencl_program_type::PRECOMPILED, "hello_world.clbin" }, "print" );
            
    const char str[] = "Hello, World!";
    opencl_buffer<cl_char> b( g, sizeof(str) );
    std::copy_n( str, sizeof(str), b.begin() );
 
    clPrintSPIR.set_ndranges( { 1 } );
    clPrintPrecompiled.set_ndranges( { 1 } );
    
    std::cout << "SPIR kernel: ";
    input_port<0>(clPrintSPIR).try_put( b );
    g.wait_for_all();
    
    std::cout << "Precompiled kernel: ";
    input_port<0>(clPrintPrecompiled).try_put( b );
    g.wait_for_all();
    
    return 0;
}

出力は次のとおりです。

SPIR kernel: OpenCL says 'Hello, World!'
Precompiled kernel: OpenCL says 'Hello, World!'

カスタム factory を使用する場合は、そのカスタム factory で opencl_program をインスタンス化する必要があります。

typedef opencl_factory<MyDeviceFilter> MyFactory;
opencl_program<MyFactory> p(opencl_program_type::SPIR, "hello_world.spir");

引数のバインド

一般に、OpenCL* カーネルには多くの引数が渡されます。デフォルトでは、opencl_node は 1 つ目の入力ポートを 1 つ目のカーネル引数に、2 つ目の入力ポートを 2 つ目のカーネル引数に … というようにバインドします。次の例は、値 ‘a’、2.0f、および 3 をカーネルに渡し、カーネルはそれぞれ char、float、int として受け取ります。

#define TBB_PREVIEW_FLOW_GRAPH_NODES 1
#include "tbb/flow_graph_opencl_node.h"

int main() {
    using namespace tbb::flow;
    
    opencl_graph g;

    opencl_node<tuple<cl_char, cl_float, cl_int>>
                                clNode( g, "several_arguments.cl", "foo" );
    clNode.set_ndranges( { 1 } );
    
    input_port<0>(clNode).try_put( 'a' );
    input_port<1>(clNode).try_put( 2.0f );
    input_port<2>(clNode).try_put( 3 );
    
    g.wait_for_all();
    
    return 0;
}

several_arguments.cl:

kernel void foo ( char c, float f, int i ) {
    printf("OpenCL says: 'kernel arguments are `%c`, %f, %d'\n", c, f, i);    
}

出力は次のとおりです。

OpenCL says: 'kernel arguments are `a`, 2.000000, 3'

引数が常に一定の場合、実行ごとに値を指定する必要はありません。opencl_node の set_args メソッドを使用して、カーネルの特定の引数に値をバインドできます。同様に、N 番目の入力ポートを返す port_ref<N>() ヘルパーを使用して、入力ポートをカーネル引数にバインドすることができます。 次の例は、カーネルの第 1 引数を input_port<0> から取得し、第 2 引数と第 3 引数には値 2.0f と 3 をそれぞれ指定します。 

clNode.set_args( port_ref<0>(), 2.0f, 3 );
input_port<0>(clNode).try_put( 'b' );
input_port<1>(clNode).try_put( 3.0f );
input_port<2>(clNode).try_put( 4 );
g.wait_for_all();

出力は次のとおりです。

OpenCL says: 'kernel arguments are `b`, 2.000000, 3'

バインドにより input_port<0> のみが使用されるため、2 つ目と 3 つ目のポートに渡されたメッセージは無視されます。ただし、opencl_node では、メッセージを実行するためにはすべての input_port にメッセージを送る必要があります。不要なポートは、次のように opencl_node の宣言で省略することができます。

opencl_node<tuple<cl_char>> clNode2( g, "several_arguments.cl", "foo" );
clNode2.set_ndranges( { 1 } );
clNode2.set_args( port_ref<0>(), 2.0f, 3 );
input_port<0>(clNode).try_put( 'b' );
g.wait_for_all();

clNode2 has only one port but a kernel will be executed with three arguments: `b`, 2.0f and 3.

ポートの範囲を指定するには、port_ref<BEGIN,END>() を使用します (END は範囲に含まれます)。

clNode.set_args( port_ref<0,2>() );
input_port<0>(clNode).try_put( 'c' );
input_port<1>(clNode).try_put( 4.0f );
input_port<2>(clNode).try_put( 5 );
g.wait_for_all();

出力は次のとおりです。

OpenCL says: 'kernel arguments are `c`, 4.000000, 5'

opencl_node 実行ごとに ndrange を指定する場合、set_ndranges とともに port_ref を使用することもできます。

#define TBB_PREVIEW_FLOW_GRAPH_NODES 1
#include "tbb/flow_graph_opencl_node.h"

#include <string>
#include <algorithm>
#include <list>

int main() {
    using namespace tbb::flow;
    
    opencl_graph g;
    opencl_node<tuple<opencl_buffer<cl_char>,std::list<size_t>>> 
                                    clPrint( g, "hello_world.cl", "print" );
     
    clPrint.set_ndranges( port_ref<1>() );
    clPrint.set_args( port_ref<0>() );
    

    char str[] = "Hello, World!";
    opencl_buffer<cl_char> b( g, sizeof(str) );
    std::copy_n( str, sizeof(str), b.begin() );
    input_port<0>(clPrint).try_put( b );
    
    std::list<size_t> ndrange;
    ndrange.push_back( 1 );
    input_port<1>(clPrint).try_put( ndrange );
    g.wait_for_all();
   
    return 0;
}

ndrange が入力ポートにバインドされている場合は、明示的に引数をバインドする必要があります。

このシリーズの次の記事では、順序付けの問題を回避するため、型指定したメッセージキーの使用法について述べます。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください

タイトルとURLをコピーしました