Skip to content

Commit

Permalink
refactor C++ gtest suites to work with any device
Browse files Browse the repository at this point in the history
  • Loading branch information
yshekel committed Nov 21, 2024
1 parent 96549d6 commit 08fb680
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 255 deletions.
55 changes: 55 additions & 0 deletions icicle/tests/test_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <gtest/gtest.h>
#include "icicle/runtime.h"
#include "icicle/utils/log.h"

using FpMiliseconds = std::chrono::duration<float, std::chrono::milliseconds::period>;
#define START_TIMER(timer) auto timer##_start = std::chrono::high_resolution_clock::now();
#define END_TIMER(timer, msg, enable) \
if (enable) \
printf("%s: %.3f ms\n", msg, FpMiliseconds(std::chrono::high_resolution_clock::now() - timer##_start).count());
#define END_TIMER_AVERAGE(timer, msg, enable, iters) \
if (enable) \
printf( \
"%s: %.3f ms\n", msg, FpMiliseconds(std::chrono::high_resolution_clock::now() - timer##_start).count() / iters);

#define UNKOWN_DEVICE "UNKNOWN"

class IcicleTestBase : public ::testing::Test
{
public:
static inline std::vector<std::string> s_registered_devices;
static inline std::string s_main_device = UNKOWN_DEVICE;
static inline std::string s_ref_device = "CPU"; // assuming always present
// SetUpTestSuite/TearDownTestSuite are called once for the entire test suite
static void SetUpTestSuite()
{
#ifdef BACKEND_BUILD_DIR
setenv("ICICLE_BACKEND_INSTALL_DIR", BACKEND_BUILD_DIR, 0 /*=replace*/);
#endif
icicle_load_backend_from_env_or_default();
s_registered_devices = get_registered_devices_list();
ASSERT_GT(s_registered_devices.size(), 0);
ASSERT_LE(s_registered_devices.size(), 2); // assuming we have a single device except for CPU
if (s_registered_devices.size() > 1) {
for (auto& device : s_registered_devices) {
// looking for first device that is not CPU and use that as main device
if (device != s_ref_device) {
s_main_device = device;
break;
}
}
}
ICICLE_LOG_INFO << "Main-device=" << s_main_device << ", Reference-device=" << s_ref_device;
}
static void TearDownTestSuite() {}

// SetUp/TearDown are called before and after each test
void SetUp() override {}
void TearDown() override {}

bool is_main_device_available() const { return s_main_device != UNKOWN_DEVICE && s_main_device != "CPU"; }
const std::string& main_device() const { return s_main_device; }
const std::string& reference_device() const { return s_ref_device; }
};
75 changes: 25 additions & 50 deletions icicle/tests/test_curve_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <fstream>
#include <list>
#include <random>
#include "dlfcn.h"

#include "icicle/runtime.h"
#include "icicle/ntt.h"
Expand All @@ -15,48 +14,16 @@
#include "icicle/backend/msm_config.h"
#include "icicle/backend/ntt_config.h"

#include "test_base.h"

using namespace curve_config;
using namespace icicle;

using FpMicroseconds = std::chrono::duration<float, std::chrono::microseconds::period>;
#define START_TIMER(timer) auto timer##_start = std::chrono::high_resolution_clock::now();
#define END_TIMER(timer, msg, enable) \
if (enable) \
printf( \
"%s: %.3f ms\n", msg, FpMicroseconds(std::chrono::high_resolution_clock::now() - timer##_start).count() / 1000);

static bool VERBOSE = true;
static inline std::string s_main_target;
static inline std::string s_ref_target;

class CurveApiTest : public ::testing::Test
class CurveApiTest : public IcicleTestBase
{
public:
static inline std::list<std::string> s_registered_devices;

// SetUpTestSuite/TearDownTestSuite are called once for the entire test suite
static void SetUpTestSuite()
{
#ifdef BACKEND_BUILD_DIR
setenv("ICICLE_BACKEND_INSTALL_DIR", BACKEND_BUILD_DIR, 0 /*=replace*/);
#endif
icicle_load_backend_from_env_or_default();

const bool is_cuda_registered = is_device_registered("CUDA");
if (!is_cuda_registered) { ICICLE_LOG_ERROR << "CUDA device not found. Testing CPU vs CPU"; }
s_main_target = is_cuda_registered ? "CUDA" : "CPU";
s_ref_target = "CPU";
}
static void TearDownTestSuite()
{
// make sure to fail in CI if only have one device
ICICLE_ASSERT(is_device_registered("CUDA")) << "missing CUDA backend";
}

// SetUp/TearDown are called before and after each test
void SetUp() override {}
void TearDown() override {}

template <typename T>
void random_scalars(T* arr, uint64_t count)
{
Expand Down Expand Up @@ -103,8 +70,8 @@ class CurveApiTest : public ::testing::Test
END_TIMER(MSM_sync, oss.str().c_str(), measure);
};

run(s_main_target, result_main.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
run(s_ref_target, result_ref.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
run(IcicleTestBase::main_device(), result_main.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
run(IcicleTestBase::reference_device(), result_ref.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
for (int res_idx = 0; res_idx < batch; ++res_idx) {
ASSERT_EQ(true, P::is_on_curve(result_main[res_idx]));
ASSERT_EQ(true, P::is_on_curve(result_ref[res_idx]));
Expand Down Expand Up @@ -154,13 +121,13 @@ class CurveApiTest : public ::testing::Test
}
END_TIMER(MSM_sync, oss.str().c_str(), measure);
};
if (s_ref_target == "CPU") {
run(s_ref_target, result_multi_thread.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
if (IcicleTestBase::reference_device() == "CPU") {
run(IcicleTestBase::reference_device(), result_multi_thread.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
// Adjust config to have one worker thread
ConfigExtension ext;
ext.set(CpuBackendConfig::CPU_NOF_THREADS, 1);
config.ext = &ext;
run(s_ref_target, result_single_thread.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);
run(IcicleTestBase::reference_device(), result_single_thread.get(), "msm", VERBOSE /*=measure*/, 1 /*=iters*/);

for (int res_idx = 0; res_idx < batch; ++res_idx) {
ASSERT_EQ(true, P::is_on_curve(result_multi_thread[res_idx]));
Expand Down Expand Up @@ -195,12 +162,20 @@ class CurveApiTest : public ::testing::Test
END_TIMER(MONTGOMERY, oss.str().c_str(), measure);
};

run(s_main_target, elements.get(), main_output.get(), true /*into*/, VERBOSE /*=measure*/, "to-montgomery", 1);
run(s_ref_target, elements.get(), ref_output.get(), true /*into*/, VERBOSE /*=measure*/, "to-montgomery", 1);
run(
IcicleTestBase::main_device(), elements.get(), main_output.get(), true /*into*/, VERBOSE /*=measure*/,
"to-montgomery", 1);
run(
IcicleTestBase::reference_device(), elements.get(), ref_output.get(), true /*into*/, VERBOSE /*=measure*/,
"to-montgomery", 1);
ASSERT_EQ(0, memcmp(main_output.get(), ref_output.get(), N * sizeof(T)));

run(s_main_target, main_output.get(), main_output.get(), false /*from*/, VERBOSE /*=measure*/, "to-montgomery", 1);
run(s_ref_target, ref_output.get(), ref_output.get(), false /*from*/, VERBOSE /*=measure*/, "to-montgomery", 1);
run(
IcicleTestBase::main_device(), main_output.get(), main_output.get(), false /*from*/, VERBOSE /*=measure*/,
"to-montgomery", 1);
run(
IcicleTestBase::reference_device(), ref_output.get(), ref_output.get(), false /*from*/, VERBOSE /*=measure*/,
"to-montgomery", 1);
ASSERT_EQ(0, memcmp(main_output.get(), ref_output.get(), N * sizeof(T)));
}
};
Expand Down Expand Up @@ -249,8 +224,8 @@ TEST_F(CurveApiTest, ecntt)
ntt_release_domain<scalar_t>();
};

run(s_main_target, out_main.get(), "ecntt", VERBOSE /*=measure*/, 1);
run(s_ref_target, out_ref.get(), "ecntt", VERBOSE /*=measure*/, 1);
run(IcicleTestBase::main_device(), out_main.get(), "ecntt", VERBOSE /*=measure*/, 1);
run(IcicleTestBase::reference_device(), out_ref.get(), "ecntt", VERBOSE /*=measure*/, 1);

// note that memcmp is tricky here because projetive points can have many representations
for (uint64_t i = 0; i < N; ++i) {
Expand Down Expand Up @@ -318,9 +293,9 @@ TEST_F(CurveApiTest, ecnttDeviceMem)
ICICLE_CHECK(ntt_release_domain<scalar_t>());
};

run(s_main_target, out_main.get(), "ecntt", false /*=measure*/, 1 /*=iters*/); // warmup
run(s_ref_target, out_ref.get(), "ecntt", VERBOSE /*=measure*/, 1 /*=iters*/);
run(s_main_target, out_main.get(), "ecntt", VERBOSE /*=measure*/, 1 /*=iters*/);
run(IcicleTestBase::main_device(), out_main.get(), "ecntt", false /*=measure*/, 1 /*=iters*/); // warmup
run(IcicleTestBase::reference_device(), out_ref.get(), "ecntt", VERBOSE /*=measure*/, 1 /*=iters*/);
run(IcicleTestBase::main_device(), out_main.get(), "ecntt", VERBOSE /*=measure*/, 1 /*=iters*/);
// note that memcmp is tricky here because projetive points can have many representations
for (uint64_t i = 0; i < N; ++i) {
ASSERT_FALSE(projective_t::is_zero(out_ref[i]));
Expand Down
51 changes: 6 additions & 45 deletions icicle/tests/test_device_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,12 @@

#include "icicle/runtime.h"
#include "icicle/memory_tracker.h"
#include "dlfcn.h"

using FpNanoseconds = std::chrono::duration<float, std::chrono::nanoseconds::period>;
#define START_TIMER(timer) auto timer##_start = std::chrono::high_resolution_clock::now();
#define END_TIMER(timer, msg, enable, iters) \
if (enable) \
printf( \
"%s: %.3f ns\n", msg, FpNanoseconds(std::chrono::high_resolution_clock::now() - timer##_start).count() / iters);
#include "test_base.h"

using namespace icicle;

#define UNKOWN_DEVICE "UNKNOWN"

class DeviceApiTest : public ::testing::Test
class DeviceApiTest : public IcicleTestBase
{
public:
static inline std::vector<std::string> s_registered_devices;
static inline std::string s_main_device = UNKOWN_DEVICE;
static inline std::string s_ref_device = "CPU"; // assuming always present
// SetUpTestSuite/TearDownTestSuite are called once for the entire test suite
static void SetUpTestSuite()
{
#ifdef BACKEND_BUILD_DIR
setenv("ICICLE_BACKEND_INSTALL_DIR", BACKEND_BUILD_DIR, 0 /*=replace*/);
#endif
icicle_load_backend_from_env_or_default();
s_registered_devices = get_registered_devices_list();
ASSERT_GT(s_registered_devices.size(), 0);
ASSERT_LE(s_registered_devices.size(), 2); // assuming we have a single device except for CPU
if (s_registered_devices.size() > 1) {
for (auto& device : s_registered_devices) {
// looking for first device that is not CPU and use that as main device
if (device != s_ref_device) {
s_main_device = device;
break;
}
}
}
ICICLE_LOG_INFO << "Main-device=" << s_main_device << ", Reference-device=" << s_ref_device;
}
static void TearDownTestSuite() {}

// SetUp/TearDown are called before and after each test
void SetUp() override {}
void TearDown() override {}
};

TEST_F(DeviceApiTest, UnregisteredDeviceError)
Expand Down Expand Up @@ -173,7 +134,7 @@ TEST_F(DeviceApiTest, AvailableMemory)
{
icicle::Device dev = {"CUDA", 0};
const bool is_cuda_registered = eIcicleError::SUCCESS == icicle_is_device_available(dev);
if (!is_cuda_registered) { return; } // TODO implement for CPU too
if (!is_cuda_registered) { GTEST_SKIP(); } // most devices do not support this

icicle_set_device(dev);
size_t total, free;
Expand Down Expand Up @@ -211,21 +172,21 @@ TEST_F(DeviceApiTest, memoryTracker)
for (auto& it : allocated_addresses) {
icicle_malloc(&it, ALLOC_SIZE);
}
END_TIMER(allocation, "memory-tracker: malloc average", true, NOF_ALLOCS);
END_TIMER_AVERAGE(allocation, "memory-tracker: malloc average", true, NOF_ALLOCS);

START_TIMER(insertion);
for (auto& it : allocated_addresses) {
tracker.add_allocation(it, ALLOC_SIZE, main_device);
}
END_TIMER(insertion, "memory-tracker: insert average", true, NOF_ALLOCS);
END_TIMER_AVERAGE(insertion, "memory-tracker: insert average", true, NOF_ALLOCS);

START_TIMER(lookup);
for (auto& it : allocated_addresses) {
// identify addresses identified correctly (to active device)
const void* addr = (void*)((size_t)it + rand() % ALLOC_SIZE);
ICICLE_CHECK(icicle_is_active_device_memory(addr));
}
END_TIMER(lookup, "memory-tracker: lookup (and compare) average", true, NOF_ALLOCS);
END_TIMER_AVERAGE(lookup, "memory-tracker: lookup (and compare) average", true, NOF_ALLOCS);

// test host pointers are identified as host memory
auto host_mem = std::make_unique<int>();
Expand Down
Loading

0 comments on commit 08fb680

Please sign in to comment.