Files
oneDNN/tests/gtests/test_iface_sparse.cpp
2025-02-12 19:26:38 -08:00

409 lines
13 KiB
C++

/*******************************************************************************
* Copyright 2022-2025 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
#include "dnnl_test_common.hpp"
#include "gtest/gtest.h"
#include "oneapi/dnnl/dnnl.hpp"
namespace dnnl {
using dt = memory::data_type;
class iface_sparse_test_t : public ::testing::Test {};
TEST(iface_sparse_test_t, TestSparseMDCreation) {
const int nnz = 12;
memory::desc md;
// CSR.
ASSERT_NO_THROW(
md = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
// COO.
ASSERT_NO_THROW(md = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
// Packed.
ASSERT_NO_THROW(md = memory::desc::packed({64, 128}, dt::f32, nnz));
}
TEST(iface_sparse_test_t, TestSparseMDComparison) {
const int nnz = 12;
memory::desc md1;
memory::desc md2;
// CSR.
// Different index data types.
ASSERT_NO_THROW(
md1 = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
ASSERT_NO_THROW(
md2 = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s8, dt::s32));
ASSERT_NE(md1, md2);
// Different pointer data types.
ASSERT_NO_THROW(
md1 = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
ASSERT_NO_THROW(
md2 = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s8));
ASSERT_NE(md1, md2);
// Different nnz.
ASSERT_NO_THROW(
md1 = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
ASSERT_NO_THROW(md2
= memory::desc::csr({64, 128}, dt::f32, nnz + 1, dt::s32, dt::s32));
ASSERT_NE(md1, md2);
// COO.
// Different value data types.
ASSERT_NO_THROW(md1 = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
ASSERT_NO_THROW(md2 = memory::desc::coo({64, 128}, dt::f16, nnz, dt::s32));
ASSERT_NE(md1, md2);
// Different index data types.
ASSERT_NO_THROW(md1 = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
ASSERT_NO_THROW(md2 = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s8));
ASSERT_NE(md1, md2);
// Different nnz.
ASSERT_NO_THROW(md1 = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
ASSERT_NO_THROW(
md2 = memory::desc::coo({64, 128}, dt::f32, nnz + 1, dt::s32));
ASSERT_NE(md1, md2);
// Packed.
// Equal memory descriptors.
ASSERT_NO_THROW(md1 = memory::desc::packed({64, 128}, dt::f32, nnz));
ASSERT_NO_THROW(md2 = memory::desc::packed({64, 128}, dt::f32, nnz));
ASSERT_EQ(md1, md2);
// Different nnz.
ASSERT_NO_THROW(md1 = memory::desc::packed({64, 128}, dt::f32, nnz));
ASSERT_NO_THROW(md2 = memory::desc::packed({64, 128}, dt::f32, nnz + 1));
ASSERT_NE(md1, md2);
}
TEST(iface_sparse_test_t, TestSparseMDQueries) {
const int nnz = 12;
const auto indices_dt = dt::s8;
const auto pointers_dt = dt::s32;
const memory::dims dims = {64, 128};
const memory::data_type data_type = dt::f32;
memory::desc md;
// CSR.
ASSERT_NO_THROW(md
= memory::desc::csr(dims, data_type, nnz, indices_dt, pointers_dt));
ASSERT_EQ(md.get_dims(), dims);
ASSERT_EQ(md.get_data_type(), data_type);
ASSERT_EQ(md.get_data_type(0), data_type);
ASSERT_EQ(md.get_format_kind(), memory::format_kind::sparse);
ASSERT_EQ(md.get_nnz(), nnz);
ASSERT_EQ(md.get_sparse_encoding(), memory::sparse_encoding::csr);
ASSERT_EQ(md.get_data_type(1), indices_dt);
ASSERT_EQ(md.get_data_type(2), pointers_dt);
// COO.
ASSERT_NO_THROW(md = memory::desc::coo(dims, data_type, nnz, indices_dt));
ASSERT_EQ(md.get_dims(), dims);
ASSERT_EQ(md.get_data_type(), data_type);
ASSERT_EQ(md.get_data_type(0), data_type);
ASSERT_EQ(md.get_format_kind(), memory::format_kind::sparse);
ASSERT_EQ(md.get_nnz(), nnz);
ASSERT_EQ(md.get_sparse_encoding(), memory::sparse_encoding::coo);
ASSERT_EQ(md.get_data_type(1), indices_dt);
ASSERT_EQ(md.get_data_type(2), indices_dt);
// Packed.
ASSERT_NO_THROW(md = memory::desc::packed(dims, data_type, nnz));
ASSERT_EQ(md.get_dims(), dims);
ASSERT_EQ(md.get_data_type(), data_type);
ASSERT_EQ(md.get_data_type(0), data_type);
ASSERT_EQ(md.get_format_kind(), memory::format_kind::sparse);
ASSERT_EQ(md.get_nnz(), nnz);
ASSERT_EQ(md.get_sparse_encoding(), memory::sparse_encoding::packed);
}
TEST(iface_sparse_test_t, TestSparseMDSize) {
const int nnz = 12;
memory::desc md;
// CSR.
ASSERT_NO_THROW(
md = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
// Size of values.
size_t exp_values_size = nnz * memory::data_type_size(md.get_data_type());
// Default.
ASSERT_EQ(md.get_size(), exp_values_size);
// Explicit.
ASSERT_EQ(md.get_size(0), exp_values_size);
// Size of indices.
size_t exp_indices_size = nnz * memory::data_type_size(md.get_data_type(1));
ASSERT_EQ(md.get_size(1), exp_indices_size);
// Size of pointers.
size_t exp_pointers_size = (md.get_dims()[0] + 1)
* memory::data_type_size(md.get_data_type(2));
ASSERT_EQ(md.get_size(2), exp_pointers_size);
// COO.
ASSERT_NO_THROW(md = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
// Size of values.
exp_values_size = nnz * memory::data_type_size(md.get_data_type());
// Default.
ASSERT_EQ(md.get_size(), exp_values_size);
// Explicit.
ASSERT_EQ(md.get_size(0), exp_values_size);
// Size of indices.
exp_indices_size = nnz * memory::data_type_size(md.get_data_type(1));
ASSERT_EQ(md.get_size(1), exp_indices_size);
ASSERT_EQ(md.get_size(2), exp_indices_size);
// Packed.
// The user-created memory descriptor for packed encoding cannot
// be queried for sizes.
ASSERT_NO_THROW(md = memory::desc::packed({64, 128}, dt::f32, nnz));
// Size of values.
// Default.
ASSERT_EQ(md.get_size(), 0u);
// Explicit.
ASSERT_EQ(md.get_size(0), 0u);
// Size of offsets.
ASSERT_EQ(md.get_size(1), 0u);
// Size of bitmask.
ASSERT_EQ(md.get_size(2), 0u);
}
HANDLE_EXCEPTIONS_FOR_TEST(iface_sparse_test_t, TestSparseMemoryCreation) {
engine eng = get_test_engine();
const bool is_unimplemented = (eng.get_kind() == engine::kind::gpu
|| DNNL_CPU_RUNTIME == DNNL_RUNTIME_SYCL);
if (is_unimplemented) return;
const int nnz = 12;
memory::desc md;
// CSR.
ASSERT_NO_THROW(
md = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
memory mem;
// Default memory constructor.
mem = memory(md, eng);
// User provided buffers.
{
std::vector<float> values(1);
std::vector<int> indices(1);
std::vector<int> pointers(1);
EXPECT_NO_THROW(
mem = memory(md, eng,
{values.data(), indices.data(), pointers.data()}));
}
// COO.
ASSERT_NO_THROW(md = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
// Default memory constructor.
mem = memory(md, eng);
// User provided buffers.
{
std::vector<float> values(1);
std::vector<int> row_indices(1);
std::vector<int> col_indices(1);
EXPECT_NO_THROW(mem = memory(md, eng,
{values.data(), row_indices.data(),
col_indices.data()}));
}
}
HANDLE_EXCEPTIONS_FOR_TEST(
iface_sparse_test_t, TestSparseMemorySetGetDataHandles) {
engine eng = get_test_engine();
const bool is_unimplemented = (eng.get_kind() == engine::kind::gpu
|| DNNL_CPU_RUNTIME == DNNL_RUNTIME_SYCL);
if (is_unimplemented) return;
const int nnz = 12;
// CSR.
memory::desc md;
ASSERT_NO_THROW(
md = memory::desc::csr({64, 128}, dt::f32, nnz, dt::s32, dt::s32));
memory mem;
int nhandles = 3;
// Default memory constructor.
mem = memory(md, eng);
{
for (int i = 0; i < nhandles; i++) {
void *h = mem.get_data_handle(i);
ASSERT_NE(h, nullptr);
}
// Creating a memory object without underlying buffers.
for (int i = 0; i < nhandles; i++) {
EXPECT_NO_THROW(mem.set_data_handle(DNNL_MEMORY_NONE, i));
}
for (int i = 0; i < nhandles; i++) {
void *h = mem.get_data_handle(i);
ASSERT_EQ(h, nullptr);
}
}
// User provided buffers.
{
std::vector<float> values(1);
std::vector<int> indices(1);
std::vector<int> pointers(1);
ASSERT_NO_THROW(mem.set_data_handle(values.data(), 0));
ASSERT_NO_THROW(mem.set_data_handle(indices.data(), 1));
ASSERT_NO_THROW(mem.set_data_handle(pointers.data(), 2));
ASSERT_EQ(mem.get_data_handle(0), values.data());
ASSERT_EQ(mem.get_data_handle(1), indices.data());
ASSERT_EQ(mem.get_data_handle(2), pointers.data());
}
// COO.
ASSERT_NO_THROW(md = memory::desc::coo({64, 128}, dt::f32, nnz, dt::s32));
// Default memory constructor.
mem = memory(md, eng);
{
for (int i = 0; i < nhandles; i++) {
void *h = mem.get_data_handle(i);
ASSERT_NE(h, nullptr);
}
// Creating a memory object without underlying buffers.
for (int i = 0; i < nhandles; i++) {
EXPECT_NO_THROW(mem.set_data_handle(DNNL_MEMORY_NONE, i));
}
for (int i = 0; i < nhandles; i++) {
void *h = mem.get_data_handle(i);
ASSERT_EQ(h, nullptr);
}
}
// User provided buffers.
{
std::vector<float> values(1);
std::vector<int> row_indices(1);
std::vector<int> col_indices(1);
ASSERT_NO_THROW(mem.set_data_handle(values.data(), 0));
ASSERT_NO_THROW(mem.set_data_handle(row_indices.data(), 1));
ASSERT_NO_THROW(mem.set_data_handle(col_indices.data(), 2));
ASSERT_EQ(mem.get_data_handle(0), values.data());
ASSERT_EQ(mem.get_data_handle(1), row_indices.data());
ASSERT_EQ(mem.get_data_handle(2), col_indices.data());
}
}
TEST(iface_sparse_test_t, TestSparseMemoryMapUnmap) {
engine eng = get_test_engine();
const bool is_unimplemented = (eng.get_kind() == engine::kind::gpu
|| DNNL_CPU_RUNTIME == DNNL_RUNTIME_SYCL);
if (is_unimplemented) return;
const int nnz = 2;
// CSR.
memory::desc md;
ASSERT_NO_THROW(
md = memory::desc::csr({2, 2}, dt::f32, nnz, dt::s32, dt::s32));
std::vector<float> values = {1.5, 2.5};
std::vector<int> indices = {0, 1};
std::vector<int> pointers = {0, 1, 2};
memory mem(md, eng, {values.data(), indices.data(), pointers.data()});
float *mapped_values = nullptr;
int *mapped_indices = nullptr;
int *mapped_pointers = nullptr;
ASSERT_NO_THROW(mapped_values = mem.map_data<float>(0));
ASSERT_NO_THROW(mapped_indices = mem.map_data<int>(1));
ASSERT_NO_THROW(mapped_pointers = mem.map_data<int>(2));
for (size_t i = 0; i < values.size(); i++)
ASSERT_EQ(values[i], mapped_values[i]);
for (size_t i = 0; i < indices.size(); i++)
ASSERT_EQ(indices[i], mapped_indices[i]);
for (size_t i = 0; i < pointers.size(); i++)
ASSERT_EQ(pointers[i], mapped_pointers[i]);
ASSERT_NO_THROW(mem.unmap_data(mapped_values, 0));
ASSERT_NO_THROW(mem.unmap_data(mapped_indices, 1));
ASSERT_NO_THROW(mem.unmap_data(mapped_pointers, 2));
// COO.
ASSERT_NO_THROW(md = memory::desc::coo({2, 2}, dt::f32, nnz, dt::s32));
std::vector<float> coo_values = {1.5, 2.5};
std::vector<int> row_indices = {0, 1};
std::vector<int> col_indices = {0, 1};
memory coo_mem(md, eng,
{coo_values.data(), row_indices.data(), col_indices.data()});
float *mapped_coo_values = nullptr;
int *mapped_row_indices = nullptr;
int *mapped_col_indices = nullptr;
ASSERT_NO_THROW(mapped_coo_values = coo_mem.map_data<float>(0));
ASSERT_NO_THROW(mapped_row_indices = coo_mem.map_data<int>(1));
ASSERT_NO_THROW(mapped_col_indices = coo_mem.map_data<int>(2));
for (size_t i = 0; i < coo_values.size(); i++)
ASSERT_EQ(values[i], mapped_values[i]);
for (size_t i = 0; i < row_indices.size(); i++)
ASSERT_EQ(row_indices[i], mapped_row_indices[i]);
for (size_t i = 0; i < col_indices.size(); i++)
ASSERT_EQ(col_indices[i], mapped_col_indices[i]);
ASSERT_NO_THROW(mem.unmap_data(mapped_coo_values, 0));
ASSERT_NO_THROW(mem.unmap_data(mapped_row_indices, 1));
ASSERT_NO_THROW(mem.unmap_data(mapped_col_indices, 2));
}
} // namespace dnnl