HostInputService::submit (const DriverAPI *api, uint32_t id,
const Model *model, Buffer *buffer, outputCallback callback)
{
+ if (api == nullptr || model == nullptr)
+ return -EINVAL;
+
taskFunc func = std::bind (&HostInputService::invoke, this,
api, model, buffer, callback);
ThreadTask *task = new ThreadTask (id, func);
- ThreadPool::getInstance().enqueueTask (task);
-
- return 0;
+ return ThreadPool::getInstance().enqueueTask (task);
}
/**
HostInputService::invoke (const DriverAPI *api, const Model *model,
Buffer *buffer, outputCallback callback)
{
- if (api->getState() == 0) {
- input_config_t input_config;
+ input_config_t input_config;
+ npuConstraint constraint;
+ int state;
+
+ state = api->getState();
+ if (state != 0) {
+ logerr (TAG, "device is not available to run inference %d\n", state);
+ goto handle_callback;
+ }
- input_config.enable = 1;
+ if (model == nullptr) {
+ logerr (TAG, "No valid model provided\n");
+ goto handle_callback;
+ }
+
+ input_config.enable = true;
+ input_config.model_id = model->getDmabuf ();
+ if (buffer != nullptr) {
input_config.dmabuf_id = buffer->getDmabuf ();
- input_config.model_id = model->getDmabuf ();
input_config.activation_offset_addr0 = buffer->getOffset ();
input_config.activation_offset_addr1 = buffer->getOffset ();
+ } else {
+ /** some instructions do not require the buffer (e.g., nop) */
+ input_config.dmabuf_id = -1;
+ input_config.activation_offset_addr0 = 0;
+ input_config.activation_offset_addr1 = 0;
+ }
- /** set constraints */
- npuConstraint constraint = model->getConstraint ();
- input_config.timeout_ms = constraint.timeout_ms;
- input_config.priority = constraint.priority;
+ /** set constraints */
+ constraint = model->getConstraint ();
+ input_config.timeout_ms = constraint.timeout_ms;
+ input_config.priority = constraint.priority;
- /** run the inference with the input */
- int status = api->runInput (&input_config);
- if (status != 0) {
- logerr (TAG, "Failed to run the NPU inference: %d\n", status);
- }
- }
+ /** run the inference with the input */
+ state = api->runInput (&input_config);
+ if (state != 0)
+ logerr (TAG, "Failed to run the NPU inference: %d\n", state);
+handle_callback:
/** should call the callback regardless of failure, to avoid deadlock */
- callback ();
+ if (callback != nullptr)
+ callback ();
}
public:
/** @brief submit request to somewhere (depends on each impl.) */
virtual int submit (const DriverAPI *api, uint32_t request_id, const Model *model,
- Buffer *buffer, outputCallback callback) { return 0; }
+ Buffer *buffer, outputCallback callback = nullptr) { return 0; }
/** @brief remove the submitted request (if possible) */
virtual int remove (uint32_t request_id) { return 0; }
- /** @brief invoke the request in one's turn */
- virtual void invoke (const DriverAPI *api, const Model *model, Buffer *bufer,
- outputCallback callback) {}
protected:
InputService () {}
virtual ~InputService () {}
+
+ private:
+ /** @brief invoke the request in one's turn */
+ virtual void invoke (const DriverAPI *api, const Model *model, Buffer *bufer,
+ outputCallback callback) {}
};
/** @brief host input service */
static HostInputService & getInstance ();
int submit (const DriverAPI *api, uint32_t request_id, const Model *model,
- Buffer *buffer, outputCallback callback);
+ Buffer *buffer, outputCallback callback = nullptr);
int remove (uint32_t request_id);
+
+ private:
+ /** do not allow to directly call invoke () */
void invoke (const DriverAPI *api, const Model *model, Buffer *bufer,
outputCallback callback);
- private:
+
static std::unique_ptr<HostInputService> instance_;
static std::once_flag once_flag_;
};
{
std::unique_lock<std::mutex> lock(m_);
+ std::deque<std::unique_ptr<ThreadTask>>::iterator it;
+ uint32_t task_id = task->getID();
+
+ it = std::find_if (queue_.begin(), queue_.end(),
+ [&task_id] (const std::unique_ptr<ThreadTask>& task) {
+ return task->getID() == task_id;
+ });
+ /** does not allow the same ID */
+ if (it != queue_.end())
+ return -EBUSY;
+
queue_.push_back (std::unique_ptr<ThreadTask> (task));
}
+
cv_.notify_one();
return 0;
[&task_id] (const std::unique_ptr<ThreadTask>& task) {
return task->getID() == task_id;
});
- if (it == queue_.end()) {
+ if (it == queue_.end())
return -EBUSY;
- } else {
- queue_.erase (it);
- return 0;
- }
+
+ queue_.erase (it);
+ return 0;
}
if (model == nullptr)
return -EINVAL;
+ /** buffer can be nullptr */
void *buffer = mmap (input_config->dmabuf_id, 0x1000);
- if (buffer == nullptr)
- return -EINVAL;
/** call emulation codes (AIP/NPU_SystemService_Emulator) */
run_npu_emul (static_cast<char*>(model), static_cast<char*>(buffer));
install_dir : join_paths(ne_bindir, 'unittests')
)
test('unittest_ne_core_comm', unittest_ne_core_comm)
+
+ unittest_ne_core_inputservice = executable('unittest_ne_core_inputservice',
+ ['ne_core_inputservice_test.cc'],
+ include_directories: ne_host_inc,
+ dependencies: [gtest_dep, ne_core_dep],
+ install : true,
+ install_rpath : ne_libdir,
+ install_dir : join_paths(ne_bindir, 'unittests')
+ )
+ test('unittest_ne_core_inputservice', unittest_ne_core_inputservice)
endif
--- /dev/null
+/**
+ * Proprietary
+ * Copyright (C) 2020 Samsung Electronics
+ * Copyright (C) 2020 Dongju Chae <dongju.chae@samsung.com>
+ */
+/**
+ * @file ne_core_inputservice_test.cc
+ * @date 10 Apr 2020
+ * @brief UnitTests to test functions in inputservice for NPU Engine
+ * @author Dongju Chae <dongju.chae@samsung.com>
+ * @bug No known bugs except for NYI items
+ */
+
+#include <ne-inputservice.h>
+#include <ne-thread-pool.h> /* just for remove() test */
+
+#include "ne_unittest_utils.h"
+
+/**
+ * @brief test single instances
+ */
+TEST (ne_core_inputservice_test, instance_singleton)
+{
+ HostInputService & host_is = HostInputService::getInstance ();
+ HostInputService & host_is2 = HostInputService::getInstance ();
+
+ EXPECT_EQ (&host_is, &host_is2);
+
+ HwInputService & hw_is = HwInputService::getInstance ();
+ HwInputService & hw_is2 = HwInputService::getInstance ();
+
+ EXPECT_EQ (&hw_is, &hw_is2);
+}
+
+/**
+ * @brief test features of submit() with a buffer
+ */
+TEST (ne_core_inputservice_test, host_submit)
+{
+ std::unique_ptr<DriverAPI> api;
+
+ api = DriverAPI::createDriverAPI (NPUCOND_TRIV_CONN_SOCIP, 0);
+ ASSERT_NE (api.get(), nullptr);
+
+ /** create dummy model & buffer */
+
+ Model * model = new Model (new HWmemDevice);
+ model->setDriverAPI (api.get());
+ EXPECT_EQ (model->alloc (4096), 0);
+
+ Buffer * buffer = new Buffer (new HWmemDevice);
+ buffer->setDriverAPI (api.get());
+ EXPECT_EQ (buffer->alloc (4096), 0);
+
+ HostInputService & service = HostInputService::getInstance ();
+ /** run without buffer */
+ EXPECT_EQ (service.submit (api.get(), 0, model, nullptr), 0);
+ /** run with buffer */
+ EXPECT_EQ (service.submit (api.get(), 1, model, buffer), 0);
+
+ sleep (1);
+
+ int num_called = 0;
+ std::condition_variable cv;
+ std::mutex m;
+
+ auto callback = std::bind (test_callback, &num_called, &m, &cv);
+ EXPECT_EQ (service.submit (api.get(), 2, model, buffer, callback), 0);
+ EXPECT_EQ (service.submit (api.get(), 3, model, buffer, callback), 0);
+ EXPECT_EQ (service.submit (api.get(), 4, model, buffer, callback), 0);
+
+ wait_callbacks (num_called, 3, m, cv);
+ EXPECT_EQ (num_called, 3);
+}
+
+/**
+ * @brief test features of submit() with error handling
+ */
+TEST (ne_core_inputservice_test, host_submit_args_n)
+{
+ std::unique_ptr<DriverAPI> api;
+ api = DriverAPI::createDriverAPI (NPUCOND_TRIV_CONN_SOCIP, 0);
+ ASSERT_NE (api.get(), nullptr);
+
+ /** create dummy model */
+ Model * model = new Model (new HWmemDevice);
+ model->setDriverAPI (api.get());
+ EXPECT_EQ (model->alloc (4096), 0);
+
+ HostInputService & service = HostInputService::getInstance ();
+
+ EXPECT_NE (service.submit (nullptr, 0, nullptr, nullptr), 0);
+ EXPECT_NE (service.submit (api.get(), 0, nullptr, nullptr), 0);
+ EXPECT_NE (service.submit (nullptr, 0, model, nullptr), 0);
+ EXPECT_EQ (service.submit (api.get(), 0, model, nullptr), 0);
+
+ sleep (1);
+}
+
+/**
+ * @brief test features of submit() with error handling
+ */
+TEST (ne_core_inputservice_test, host_submit_same_id_n)
+{
+ std::unique_ptr<DriverAPI> api;
+ api = DriverAPI::createDriverAPI (NPUCOND_TRIV_CONN_SOCIP, 0);
+ ASSERT_NE (api.get(), nullptr);
+
+ /** create dummy model */
+ Model * model = new Model (new HWmemDevice);
+ model->setDriverAPI (api.get());
+ EXPECT_EQ (model->alloc (4096), 0);
+
+ HostInputService & service = HostInputService::getInstance ();
+
+ int num_called = 0;
+ std::condition_variable cv;
+ std::mutex m;
+
+ auto callback = std::bind (test_callback_sleep, &num_called, &m, &cv);
+
+ /** saturate requests up to the maximum (in thread pool) */
+ uint32_t num_threads = ThreadPool::getInstance().getNumThreads();
+
+ for (uint32_t i = 0; i < num_threads + 1; i++)
+ EXPECT_EQ (service.submit (api.get(), i, model, nullptr, callback), 0);
+
+ /** there's a pending task with the same ID */
+ EXPECT_NE (service.submit (api.get(), num_threads, model, nullptr, callback), 0);
+ EXPECT_NE (service.submit (api.get(), num_threads, model, nullptr, callback), 0);
+
+ wait_callbacks (num_called, num_threads + 1, m, cv);
+}
+
+/**
+ * @brief test features of remove() in HostInputService
+ */
+TEST (ne_core_inputservice_test, host_remove)
+{
+ std::unique_ptr<DriverAPI> api;
+ api = DriverAPI::createDriverAPI (NPUCOND_TRIV_CONN_SOCIP, 0);
+ ASSERT_NE (api.get(), nullptr);
+
+ /** create dummy model */
+ Model * model = new Model (new HWmemDevice);
+ model->setDriverAPI (api.get());
+ EXPECT_EQ (model->alloc (4096), 0);
+
+ HostInputService & service = HostInputService::getInstance ();
+
+ int num_called = 0;
+ std::condition_variable cv;
+ std::mutex m;
+
+ auto callback = std::bind (test_callback_sleep, &num_called, &m, &cv);
+
+ /** saturate requests up to the maximum (in thread pool) */
+ uint32_t num_threads = ThreadPool::getInstance().getNumThreads();
+
+ for (uint32_t i = 0; i < num_threads + 1; i++)
+ EXPECT_EQ (service.submit (api.get(), i, model, nullptr, callback), 0);
+
+ /** remove un-resolved task */
+ EXPECT_EQ (service.remove (num_threads), 0);
+ wait_callbacks (num_called, num_threads, m, cv);
+}
+
+/**
+ * @brief test features of remove() with error handling
+ */
+TEST (ne_core_inputservice_test, host_remove_n)
+{
+ HostInputService & service = HostInputService::getInstance ();
+
+ /** no such tasks */
+ EXPECT_NE (service.remove (0), 0);
+ EXPECT_NE (service.remove (1), 0);
+ EXPECT_NE (service.remove (2), 0);
+}
+
+/**
+ * TODO: add testcases for HwInputService when implemeneted
+ */
+
+/**
+ * @brief main function for unit test
+ */
+int
+main (int argc, char **argv)
+{
+ return start_gtest (argc, argv);
+}
EXPECT_EQ (&pool, &pool2);
}
-static void test_callback (int * num_called,
- std::mutex * m, std::condition_variable * cv)
-{
- std::unique_lock<std::mutex> lock (*m);
- (*num_called)++;
- cv->notify_one ();
-}
-
/**
* @brief test enqueueTask()
*/
int max_called = 4;
/** wait untill all callbacks are called */
- {
- std::unique_lock<std::mutex> lock (m);
- cv.wait (lock, [&]() {
- return max_called == num_called;
- });
- }
-
+ wait_callbacks (num_called, max_called, m, cv);
EXPECT_EQ (num_called, max_called);
/** we can reuse previous IDs */
EXPECT_EQ (pool.enqueueTask (new ThreadTask (0, callback)), 0);
EXPECT_EQ (pool.enqueueTask (new ThreadTask (1, callback)), 0);
- {
- std::unique_lock<std::mutex> lock (m);
- cv.wait (lock, [&]() {
- return max_called == num_called;
- });
- }
+ wait_callbacks (num_called, max_called, m, cv);
EXPECT_EQ (num_called, max_called);
}
EXPECT_NE (pool.enqueueTask (nullptr), 0);
}
-static void test_callback2 (int * num_called,
- std::mutex * m, std::condition_variable * cv)
+/**
+ * @brief test enqueueTask() with error handling
+ */
+TEST (ne_core_thread_pool_test, enqueue_task_same_id_n)
{
- {
- std::unique_lock<std::mutex> lock (*m);
- (*num_called)++;
- cv->notify_one ();
- }
-
- /** this worker needs to wait 3 sec for next job */
- sleep (3);
+ ThreadPool & pool = ThreadPool::getInstance ();
+
+ int num_called = 0;
+ std::condition_variable cv;
+ std::mutex m;
+
+ uint32_t num_threads = pool.getNumThreads();
+ auto callback = std::bind (test_callback_sleep, &num_called, &m, &cv);
+
+ /** at least one request should be pending */
+ for (uint32_t i = 0; i < num_threads + 1; i++)
+ EXPECT_EQ (pool.enqueueTask (new ThreadTask (i, callback)), 0);
+
+ EXPECT_NE (pool.enqueueTask (new ThreadTask (num_threads, callback)), 0);
+ EXPECT_NE (pool.enqueueTask (new ThreadTask (num_threads, callback)), 0);
+
+ wait_callbacks (num_called, num_threads + 1, m, cv);
}
/**
std::mutex m;
uint32_t num_threads = pool.getNumThreads();
- auto callback = std::bind (test_callback2, &num_called, &m, &cv);
+ auto callback = std::bind (test_callback_sleep, &num_called, &m, &cv);
/** at least one request should be pending */
for (uint32_t i = 0; i < num_threads + 1; i++)
EXPECT_EQ (pool.removeTask (num_threads), 0);
int max_called = num_threads;
- /** wait untill all callbacks are called */
- {
- std::unique_lock<std::mutex> lock (m);
- cv.wait (lock, [&]() {
- return max_called == num_called;
- });
- }
-
+ wait_callbacks (num_called, max_called, m, cv);
EXPECT_EQ (num_called, max_called);
}
EXPECT_EQ (pool.enqueueTask (new ThreadTask (0, callback)), 0);
int max_called = 1;
- /** wait untill all callbacks are called */
- {
- std::unique_lock<std::mutex> lock (m);
- cv.wait (lock, [&]() {
- return max_called == num_called;
- });
- }
+ wait_callbacks (num_called, max_called, m, cv);
/** remove already resolved task */
EXPECT_NE (pool.removeTask (0), 0);
#ifndef _NE_UNITTEST_UTILS_H_
#define _NE_UNITTEST_UTILS_H_
+/** DO NOT INCLUDE ANY NPU-ENGINE RELATED HEADERS */
#include <gtest/gtest.h>
+#include <condition_variable>
+#include <mutex>
/**
* @brief start gtest with tricky exception handling (due to svace)
*/
-inline int start_gtest (int argc, char **argv) {
+static inline int start_gtest (int argc, char **argv) {
int ret = -1;
try {
testing::InitGoogleTest (&argc, argv);
return ret;
}
+static inline void test_callback (int * num_called,
+ std::mutex * m, std::condition_variable * cv)
+{
+ std::unique_lock<std::mutex> lock (*m);
+ (*num_called)++;
+ cv->notify_one ();
+}
+
+static inline void test_callback_sleep (int * num_called,
+ std::mutex * m, std::condition_variable * cv)
+{
+ {
+ std::unique_lock<std::mutex> lock (*m);
+ (*num_called)++;
+ cv->notify_one ();
+ }
+
+ /** this worker needs to wait 3 sec for next job */
+ sleep (3);
+}
+
+static inline void wait_callbacks (int & num_called, int max_called,
+ std::mutex & m, std::condition_variable & cv)
+{
+ std::unique_lock<std::mutex> lock (m);
+ cv.wait (lock, [&]() {
+ return max_called == num_called;
+ });
+}
+
#endif /* _NE_UNITTEST_UTILS_H_ */