Commit 7d1ea179 authored by Joseph Noir's avatar Joseph Noir

Update syntax of spawn_cl

The new syntax describes the signature in form of a variadic number of
in, out and in_out arguments as the last arguments to spawn_cl. This
allows multiple output arguments as well as arguments used for input as
well as output. The type of the argument is passed as a template
parameter to the in, out and in_out classes.

out accepts a function to calculate the size of the result buffer
from the input arguments for a specific calculation. Per default, its
size is equal to the number of work items.

The configuration parameters for the actor (work-items, offsets, ...)
are now wrapped in a spawn_configuration.

Examples and tests have been updated accordingly. For now, the old
spawn_cl syntax is still valid, but marked as deprecated.
parent 626b3456
This diff is collapsed.
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2015 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* Raphael Hiesgen <raphael.hiesgen (at) haw-hamburg.de> *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#ifndef CAF_OPENCL_ARGUMENTS
#define CAF_OPENCL_ARGUMENTS
#include <functional>
#include <type_traits>
#include "caf/message.hpp"
#include "caf/optional.hpp"
namespace caf {
namespace opencl {
/// Use as a default way to calculate output size. 0 will be set to the number
/// of work items at runtime.
struct dummy_size_calculator {
template <class... Ts>
size_t operator()(Ts&&...) const {
return 0;
}
};
/// Mark an a spawn_cl template argument as input only
template <class Arg>
struct in {
using arg_type = typename std::decay<Arg>::type;
};
/// Mark an a spawn_cl template argument as input and output
template <class Arg>
struct in_out {
using arg_type = typename std::decay<Arg>::type;
};
template <class Arg>
struct out {
out() { }
template <class F>
out(F fun) {
fun_ = [fun](message& msg) -> optional<size_t> {
auto res = msg.apply(fun);
size_t result;
if (res) {
res->apply([&](size_t x) { result = x; });
return result;
}
return none;
};
}
optional<size_t> operator()(message& msg) const {
return fun_ ? fun_(msg) : 0;
}
std::function<optional<size_t> (message&)> fun_;
};
///Cconverts C arrays, i.e., pointers, to vectors.
template <class T>
struct carr_to_vec {
using type = T;
};
template <class T>
struct carr_to_vec<T*> {
using type = std::vector<T>;
};
/// Filter types for any argument type.
template <class T>
struct is_opencl_arg : std::false_type {};
template <class T>
struct is_opencl_arg<in<T>> : std::true_type {};
template <class T>
struct is_opencl_arg<in_out<T>> : std::true_type {};
template <class T>
struct is_opencl_arg<out<T>> : std::true_type {};
/// Filter type lists for input arguments, in and in_out.
template <class T>
struct is_input_arg : std::false_type {};
template <class T>
struct is_input_arg<in<T>> : std::true_type {};
template <class T>
struct is_input_arg<in_out<T>> : std::true_type {};
/// Filter type lists for output arguments, in_out and out.
template <class T>
struct is_output_arg : std::false_type {};
template <class T>
struct is_output_arg<out<T>> : std::true_type {};
template <class T>
struct is_output_arg<in_out<T>> : std::true_type {};
/// Filter for arguments that require size, in this case only out.
template <class T>
struct requires_size_arg : std::false_type {};
template <class T>
struct requires_size_arg<out<T>> : std::true_type {};
/// extract types
template <class T>
struct extract_type { };
template <class T>
struct extract_type<in<T>> {
using type = typename std::decay<typename carr_to_vec<T>::type>::type;
};
template <class T>
struct extract_type<in_out<T>> {
using type = typename std::decay<typename carr_to_vec<T>::type>::type;
};
template <class T>
struct extract_type<out<T>> {
using type = typename std::decay<typename carr_to_vec<T>::type>::type;
};
/// Create the return message from tuple arumgent
struct message_from_results {
template <class T, class... Ts>
message operator()(T& x, Ts&... xs) {
return make_message(std::move(x), std::move(xs)...);
}
template <class... Ts>
message operator()(std::tuple<Ts...>& values) {
return apply_args(*this, detail::get_indices(values), values);
}
};
/// Helpers for conversion in deprecated spawn functions
template <class T>
struct to_input_arg {
using type = in<T>;
};
template <class T>
struct to_output_arg {
using type = out<T>;
};
} // namespace opencl
} // namespace caf
#endif // CAF_OPENCL_ARGUMENTS
......@@ -20,41 +20,49 @@
#ifndef CAF_OPENCL_COMMAND_HPP
#define CAF_OPENCL_COMMAND_HPP
#include <tuple>
#include <vector>
#include <numeric>
#include <algorithm>
#include <functional>
#include "caf/detail/logging.hpp"
#include "caf/opencl/global.hpp"
#include "caf/abstract_actor.hpp"
#include "caf/response_promise.hpp"
#include "caf/detail/logging.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/opencl/global.hpp"
#include "caf/opencl/arguments.hpp"
#include "caf/opencl/smart_ptr.hpp"
#include "caf/opencl/opencl_err.hpp"
#include "caf/detail/scope_guard.hpp"
namespace caf {
namespace opencl {
template <typename T, typename R>
template <class FacadeType, class... Ts>
class command : public ref_counted {
public:
command(response_promise handle, intrusive_ptr<T> actor_facade,
std::vector<cl_event> events, std::vector<mem_ptr> arguments,
size_t result_size, message msg)
: result_size_(result_size),
command(response_promise handle, intrusive_ptr<FacadeType> actor_facade,
std::vector<cl_event> events, std::vector<mem_ptr> input_buffers,
std::vector<mem_ptr> output_buffers, std::vector<size_t> result_sizes,
message msg)
: result_sizes_(result_sizes),
handle_(handle),
actor_facade_(actor_facade),
queue_(actor_facade->queue_),
events_(std::move(events)),
arguments_(std::move(arguments)),
result_(result_size),
mem_in_events_(std::move(events)),
input_buffers_(std::move(input_buffers)),
output_buffers_(std::move(output_buffers)),
msg_(msg) {
// nop
}
~command() {
for (auto& e : events_) {
for (auto& e : mem_in_events_) {
v1callcl(CAF_CLF(clReleaseEvent),e);
}
for (auto& e : mem_out_events_) {
v1callcl(CAF_CLF(clReleaseEvent),e);
}
}
......@@ -71,28 +79,35 @@ public:
// OpenCL expects cl_uint (unsigned int), hence the cast
cl_int err = clEnqueueNDRangeKernel(
queue_.get(), actor_facade_->kernel_.get(),
static_cast<cl_uint>(actor_facade_->global_dimensions_.size()),
data_or_nullptr(actor_facade_->global_offsets_),
data_or_nullptr(actor_facade_->global_dimensions_),
data_or_nullptr(actor_facade_->local_dimensions_),
static_cast<cl_uint>(events_.size()),
(events_.empty() ? nullptr : events_.data()), &event_k);
static_cast<cl_uint>(actor_facade_->config_.dimensions().size()),
data_or_nullptr(actor_facade_->config_.offsets()),
data_or_nullptr(actor_facade_->config_.dimensions()),
data_or_nullptr(actor_facade_->config_.local_dimensions()),
static_cast<cl_uint>(mem_in_events_.size()),
(mem_in_events_.empty() ? nullptr : mem_in_events_.data()), &event_k
);
if (err != CL_SUCCESS) {
CAF_LOGMF(CAF_ERROR, "clEnqueueNDRangeKernel: " << get_opencl_error(err));
this->deref(); // or can anything actually happen?
clReleaseEvent(event_k);
this->deref();
return;
} else {
cl_event event_r;
err =
clEnqueueReadBuffer(queue_.get(), arguments_.back().get(), CL_FALSE,
0, sizeof(typename R::value_type) * result_size_,
result_.data(), 1, &event_k, &event_r);
enqueue_read_buffers(event_k, detail::get_indices(result_buffers_));
cl_event marker;
#if defined(__APPLE__)
err = clEnqueueMarkerWithWaitList(queue_.get(), mem_out_events_.size(),
mem_out_events_.data(), &marker);
#else
err = clEnqueueMarker(queue_.get(), &marker);
#endif
if (err != CL_SUCCESS) {
this->deref(); // failed to enqueue command
throw std::runtime_error("clEnqueueReadBuffer: " +
get_opencl_error(err));
CAF_LOGMF(CAF_ERROR, "clSetEventCallback: " << get_opencl_error(err));
clReleaseEvent(marker);
clReleaseEvent(event_k);
this->deref(); // callback is not set
return;
}
err = clSetEventCallback(event_r, CL_COMPLETE,
err = clSetEventCallback(marker, CL_COMPLETE,
[](cl_event, cl_int, void* data) {
auto cmd = reinterpret_cast<command*>(data);
cmd->handle_results();
......@@ -101,32 +116,64 @@ public:
this);
if (err != CL_SUCCESS) {
CAF_LOGMF(CAF_ERROR, "clSetEventCallback: " << get_opencl_error(err));
clReleaseEvent(marker);
clReleaseEvent(event_k);
this->deref(); // callback is not set
return;
}
err = clFlush(queue_.get());
if (err != CL_SUCCESS) {
CAF_LOGMF(CAF_ERROR, "clFlush: " << get_opencl_error(err));
}
events_.push_back(std::move(event_k));
events_.push_back(std::move(event_r));
mem_out_events_.push_back(std::move(event_k));
mem_out_events_.push_back(std::move(marker));
}
}
private:
size_t result_size_;
std::vector<size_t> result_sizes_;
response_promise handle_;
intrusive_ptr<T> actor_facade_;
intrusive_ptr<FacadeType> actor_facade_;
command_queue_ptr queue_;
std::vector<cl_event> events_;
std::vector<mem_ptr> arguments_;
R result_;
std::vector<cl_event> mem_in_events_;
std::vector<cl_event> mem_out_events_;
std::vector<mem_ptr> input_buffers_;
std::vector<mem_ptr> output_buffers_;
std::tuple<Ts...> result_buffers_;
message msg_; // required to keep the argument buffers alive (async copy)
void enqueue_read_buffers(cl_event&, detail::int_list<>) {
// nop, end of recursion
}
template <long I, long... Is>
void enqueue_read_buffers(cl_event& kernel_done, detail::int_list<I, Is...>) {
using container_type =
typename std::tuple_element<I, std::tuple<Ts...>>::type;
using value_type = typename container_type::value_type;
cl_event event;
auto size = result_sizes_[I];
auto buffer_size = sizeof(value_type) * result_sizes_[I];
std::get<I>(result_buffers_).resize(size);
auto err = clEnqueueReadBuffer(queue_.get(), output_buffers_[I].get(),
CL_FALSE, 0, buffer_size,
std::get<I>(result_buffers_).data(),
1, &kernel_done, &event);
if (err != CL_SUCCESS) {
this->deref(); // failed to enqueue command
throw std::runtime_error("clEnqueueReadBuffer: " +
get_opencl_error(err));
}
mem_out_events_.push_back(std::move(event));
enqueue_read_buffers(kernel_done, detail::int_list<Is...>{});
}
void handle_results() {
auto& map_fun = actor_facade_->map_result_;
auto msg = map_fun ? map_fun(result_) : make_message(std::move(result_));
auto& map_fun = actor_facade_->map_results_;
auto msg = map_fun ? apply_args(map_fun,
detail::get_indices(result_buffers_),
result_buffers_)
: message_from_results{}(result_buffers_);
handle_.deliver(std::move(msg));
}
};
......
......@@ -28,13 +28,13 @@
namespace caf {
namespace opencl {
template <typename Signature>
template <class... Ts>
class actor_facade;
/// @brief A wrapper for OpenCL's cl_program.
class program {
template <typename Signature>
template <class... Ts>
friend class actor_facade;
public:
......@@ -45,11 +45,13 @@ public:
const char* options = nullptr, uint32_t device_id = 0);
private:
program(context_ptr context, command_queue_ptr queue, program_ptr program);
program(context_ptr context, command_queue_ptr queue, program_ptr program,
std::map<std::string,kernel_ptr> available_kernels);
context_ptr context_;
program_ptr program_;
command_queue_ptr queue_;
std::map<std::string,kernel_ptr> available_kernels_;
};
} // namespace opencl
......
This diff is collapsed.
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2015 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#ifndef CAF_OPENCL_SPAWN_CONFIG_HPP
#define CAF_OPENCL_SPAWN_CONFIG_HPP
#include "caf/opencl/global.hpp"
namespace caf {
namespace opencl {
class spawn_config {
public:
spawn_config(const opencl::dim_vec& dims,
const opencl::dim_vec& offset = {},
const opencl::dim_vec& local_dims = {})
: dims_{dims},
offset_{offset},
local_dims_{local_dims} {
// nop
}
spawn_config(opencl::dim_vec&& dims,
opencl::dim_vec&& offset = {},
opencl::dim_vec&& local_dims = {})
: dims_{std::move(dims)},
offset_{std::move(offset)},
local_dims_{std::move(local_dims)} {
// nop
}
const opencl::dim_vec& dimensions() const {
return dims_;
}
const opencl::dim_vec& offsets() const {
return offset_;
}
const opencl::dim_vec& local_dimensions() const {
return local_dims_;
}
private:
const opencl::dim_vec dims_;
const opencl::dim_vec offset_;
const opencl::dim_vec local_dims_;
};
} // namespace opencl
} // namespace caf
#endif // CAF_OPENCL_SPAWN_CONFIG_HPP
......@@ -28,6 +28,7 @@
using namespace std;
using namespace caf;
using namespace caf::opencl;
using detail::limited_vector;
......@@ -152,27 +153,28 @@ void multiplier(event_based_actor* self) {
);
};
auto box_res = [] (fvec& result) -> message {
auto box_res = [] (fvec result) -> message {
return make_message(matrix_type{move(result)});
};
// spawn an opencl actor
// 1st arg: source code of one or more opencl kernels
// 2nd arg: name of the kernel to use
auto worker = spawn_cl<fvec (fvec&, fvec&)>(kernel_source, kernel_name,
// 3rd arg: the opencl function operates on vectors,
// this function converts a tuple of two matrices
// to a tuple of vectors; note that this function returns
// an option (an empty results causes the actor to ignore
// the message)
unbox_args,
// 4th arg: converts the ouptut vector back to a matrix that is then
// 3rd arg: the config specifies how many dimensions the kernel uses and how
// many work items are created, creates matrix_size * matrix_size
// global work items in this case
// 4th arg: the opencl function operates on vectors, this function converts
// a tuple of two matrices to a tuple of vectors; note that this
// function returns an option (an empty results causes the actor to
// ignore the message)
// 5th arg: converts the ouptut vector back to a matrix that is then
// used as response message
box_res,
// 5th arg: global dimension arguments for opencl's enqueue,
// creates matrix_size * matrix_size global work items
limited_vector<size_t, 3>{matrix_size, matrix_size}
);
// from 6 : a description of the kernel signature using in/out/in_out classes
// with the argument type packed in vectors
auto worker = spawn_cl(kernel_source, kernel_name,
spawn_config{dim_vec{matrix_size, matrix_size}},
unbox_args, box_res,
in<fvec>{}, in<fvec>{}, out<fvec>{});
// send both matrices to the actor and
// wait for results in form of a matrix_type
......
......@@ -27,11 +27,14 @@
using namespace std;
using namespace caf;
using namespace caf::opencl;
using detail::limited_vector;
namespace {
using fvec = std::vector<float>;
constexpr size_t matrix_size = 8;
constexpr const char* kernel_name = "matrix_mult";
......@@ -55,7 +58,7 @@ constexpr const char* kernel_source = R"__(
} // namespace <anonymous>
void print_as_matrix(const vector<float>& matrix) {
void print_as_matrix(const fvec& matrix) {
for (size_t column = 0; column < matrix_size; ++column) {
for (size_t row = 0; row < matrix_size; ++row) {
cout << fixed << setprecision(2) << setw(9)
......@@ -68,8 +71,8 @@ void print_as_matrix(const vector<float>& matrix) {
void multiplier(event_based_actor* self) {
// the opencl actor only understands vectors
// so these vectors represent the matrices
vector<float> m1(matrix_size * matrix_size);
vector<float> m2(matrix_size * matrix_size);
fvec m1(matrix_size * matrix_size);
fvec m2(matrix_size * matrix_size);
// fill each with ascending values
iota(m1.begin(), m1.end(), 0);
......@@ -81,25 +84,21 @@ void multiplier(event_based_actor* self) {
cout << endl;
// spawn an opencl actor
// template parameter: signature of opencl kernel using proper return type
// instead of output parameter (implicitly
// mapped to the last kernel argument)
// 1st arg: source code of one or more kernels
// 2nd arg: name of the kernel to use
// 3rd arg: global dimension arguments for opencl's enqueue;
// 3rd arg: a spawn configuration that includes:
// - the global dimension arguments for opencl's enqueue
// creates matrix_size * matrix_size global work items
// 4th arg: offsets for global dimensions (optional)
// 5th arg: local dimensions (optional)
// 6th arg: number of elements in the result buffer
auto worker = spawn_cl<float*(float*,float*)>(kernel_source,
kernel_name,
limited_vector<size_t, 3>{matrix_size, matrix_size},
{},
{},
matrix_size * matrix_size);
// - offsets for global dimensions (optional)
// - local dimensions (optional)
// 4th to Nth arg: the kernel signature described by in/out/in_out classes
// that contain the argument type in their template, requires vectors
auto worker = spawn_cl(kernel_source, kernel_name,
spawn_config{dim_vec{matrix_size, matrix_size}},
in<fvec>{}, in<fvec>{}, out<fvec>{});
// send both matrices to the actor and wait for a result
self->sync_send(worker, move(m1), move(m2)).then(
[](const vector<float>& result) {
[](const fvec& result) {
cout << "result: " << endl;
print_as_matrix(result);
}
......@@ -107,7 +106,7 @@ void multiplier(event_based_actor* self) {
}
int main() {
announce<vector<float>>("float_vector");
announce<fvec>("float_vector");
spawn(multiplier);
await_all_actors_done();
shutdown();
......
......@@ -17,6 +17,7 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include <map>
#include <vector>
#include <string>
#include <cstring>
......@@ -34,10 +35,12 @@ namespace caf {
namespace opencl {
program::program(context_ptr context, command_queue_ptr queue,
program_ptr program)
program_ptr program,
map<string, kernel_ptr> available_kernels)
: context_(move(context)),
program_(move(program)),
queue_(move(queue)) {}
queue_(move(queue)),
available_kernels_(move(available_kernels)) {}
program program::create(const char* kernel_source, const char* options,
uint32_t device_id) {
......@@ -91,7 +94,35 @@ program program::create(const char* kernel_source, const char* options,
#endif
throw runtime_error(oss.str());
}
return {context, devices[device_id].cmd_queue_, pptr};
cl_uint number_of_kernels = 0;
clCreateKernelsInProgram(pptr.get(), 0, nullptr, &number_of_kernels);
vector<cl_kernel> kernels(number_of_kernels);
err = clCreateKernelsInProgram(pptr.get(), number_of_kernels, kernels.data(),
nullptr);
if (err != CL_SUCCESS) {
ostringstream oss;
oss << "clCreateKernelsInProgram: " << get_opencl_error(err);
throw runtime_error(oss.str());
}
map<string, kernel_ptr> available_kernels;
for (cl_uint i = 0; i < number_of_kernels; ++i) {
size_t ret_size;
clGetKernelInfo(kernels[i], CL_KERNEL_FUNCTION_NAME, 0, nullptr, &ret_size);
vector<char> name(ret_size);
err = clGetKernelInfo(kernels[i], CL_KERNEL_FUNCTION_NAME, ret_size,
(void*) name.data(), nullptr);
if (err != CL_SUCCESS) {
ostringstream oss;
oss << "clGetKernelInfo (CL_KERNEL_FUNCTION_NAME): "
<< get_opencl_error(err);
throw runtime_error(oss.str());
}
kernel_ptr kernel;
kernel.reset(move(kernels[i]));
available_kernels.emplace(string(name.data()), move(kernel));
}
return {context, devices[device_id].cmd_queue_, pptr,
move(available_kernels)};
}
} // namespace opencl
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment