From 95b02fdd7f562f9599f1f8b7f30083ef6da3b71f Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Wed, 20 May 2026 10:57:05 +0800 Subject: [PATCH] feat: add operator C API --- CMakeLists.txt | 5 +- cmake/InfiniOpsConfig.cmake.in | 3 + cmake/infiniops.pc.in | 10 + include/infini/ops.h | 135 ++++++++ src/CMakeLists.txt | 77 ++++- src/infini/ops.cc | 599 +++++++++++++++++++++++++++++++++ src/infini/ops.map | 6 + tests/test_c_api.py | 219 ++++++++++++ 8 files changed, 1051 insertions(+), 3 deletions(-) create mode 100644 cmake/InfiniOpsConfig.cmake.in create mode 100644 cmake/infiniops.pc.in create mode 100644 include/infini/ops.h create mode 100644 src/infini/ops.cc create mode 100644 src/infini/ops.map create mode 100644 tests/test_c_api.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 9973438c..615a56fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.18) -project(InfiniOps LANGUAGES CXX) +project(InfiniOps VERSION 0.1.0 LANGUAGES CXX) + +include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -351,6 +353,7 @@ endif() # If all other platforms are not enabled, CPU is enabled by default. if(NOT WITH_NVIDIA AND NOT WITH_ILUVATAR AND NOT WITH_METAX AND NOT WITH_MOORE AND NOT WITH_CAMBRICON AND NOT WITH_ASCEND) + set(WITH_CPU ON CACHE BOOL "Enable CPU backend" FORCE) add_compile_definitions(WITH_CPU=1) endif() diff --git a/cmake/InfiniOpsConfig.cmake.in b/cmake/InfiniOpsConfig.cmake.in new file mode 100644 index 00000000..af1f5079 --- /dev/null +++ b/cmake/InfiniOpsConfig.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/InfiniOpsTargets.cmake") diff --git a/cmake/infiniops.pc.in b/cmake/infiniops.pc.in new file mode 100644 index 00000000..6ee9d130 --- /dev/null +++ b/cmake/infiniops.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: InfiniOps +Description: InfiniOps C operator API +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -linfiniops +Cflags: -I${includedir} diff --git a/include/infini/ops.h b/include/infini/ops.h new file mode 100644 index 00000000..c8260c53 --- /dev/null +++ b/include/infini/ops.h @@ -0,0 +1,135 @@ +#ifndef INFINI_OPS_H_ +#define INFINI_OPS_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) +#if defined(INFINI_OPS_BUILD_SHARED) +#define INFINI_OPS_API __declspec(dllexport) +#elif defined(INFINI_OPS_USE_SHARED) +#define INFINI_OPS_API __declspec(dllimport) +#else +#define INFINI_OPS_API +#endif +#else +#if defined(INFINI_OPS_BUILD_SHARED) +#define INFINI_OPS_API __attribute__((visibility("default"))) +#else +#define INFINI_OPS_API +#endif +#endif + +typedef enum InfiniOpsStatus { + INFINI_OPS_STATUS_SUCCESS = 0, + INFINI_OPS_STATUS_INVALID_ARGUMENT = 1, + INFINI_OPS_STATUS_NOT_SUPPORTED = 2, + INFINI_OPS_STATUS_OUT_OF_MEMORY = 3, + INFINI_OPS_STATUS_INTERNAL_ERROR = 4, +} InfiniOpsStatus; + +typedef enum InfiniOpsDataType { + INFINI_OPS_DATA_TYPE_INVALID = 0, + INFINI_OPS_DATA_TYPE_FLOAT16 = 1, + INFINI_OPS_DATA_TYPE_FLOAT32 = 2, + INFINI_OPS_DATA_TYPE_FLOAT64 = 3, + INFINI_OPS_DATA_TYPE_BFLOAT16 = 4, + INFINI_OPS_DATA_TYPE_INT8 = 5, + INFINI_OPS_DATA_TYPE_INT16 = 6, + INFINI_OPS_DATA_TYPE_INT32 = 7, + INFINI_OPS_DATA_TYPE_INT64 = 8, + INFINI_OPS_DATA_TYPE_UINT8 = 9, + INFINI_OPS_DATA_TYPE_UINT16 = 10, + INFINI_OPS_DATA_TYPE_UINT32 = 11, + INFINI_OPS_DATA_TYPE_UINT64 = 12, +} InfiniOpsDataType; + +typedef enum InfiniOpsDeviceType { + INFINI_OPS_DEVICE_TYPE_INVALID = 0, + INFINI_OPS_DEVICE_TYPE_CPU = 1, + INFINI_OPS_DEVICE_TYPE_CUDA = 2, + INFINI_OPS_DEVICE_TYPE_CAMBRICON = 3, + INFINI_OPS_DEVICE_TYPE_ASCEND = 4, + INFINI_OPS_DEVICE_TYPE_METAX = 5, + INFINI_OPS_DEVICE_TYPE_MOORE = 6, + INFINI_OPS_DEVICE_TYPE_ILUVATAR = 7, +} InfiniOpsDeviceType; + +typedef struct InfiniOpsTensorDescriptorPrivate + *InfiniOpsTensorDescriptor; +typedef struct InfiniOpsTensorPrivate *InfiniOpsTensor; +typedef struct InfiniOpsStreamPrivate *InfiniOpsStream; +typedef struct InfiniOpsHandlePrivate *InfiniOpsHandle; +typedef struct InfiniOpsConfigPrivate *InfiniOpsConfig; + +typedef struct InfiniOpsTensorDescriptorAttributes { + size_t structure_size; + InfiniOpsDataType data_type; + InfiniOpsDeviceType device_type; + int32_t rank; + const int64_t *shape; + const int64_t *stride; + uint64_t reserved[8]; +} InfiniOpsTensorDescriptorAttributes; + +typedef struct InfiniOpsTensorAttributes { + size_t structure_size; + InfiniOpsTensorDescriptor descriptor; + void *data; + size_t byte_size; + uint64_t reserved[8]; +} InfiniOpsTensorAttributes; + +typedef struct InfiniOpsHandleAttributes { + size_t structure_size; + InfiniOpsStream stream; + void *workspace; + size_t workspace_byte_size; + uint64_t reserved[8]; +} InfiniOpsHandleAttributes; + +typedef struct InfiniOpsConfigAttributes { + size_t structure_size; + size_t implementation_index; + uint64_t reserved[8]; +} InfiniOpsConfigAttributes; + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateTensorDescriptor( + const InfiniOpsTensorDescriptorAttributes *attributes, + InfiniOpsTensorDescriptor *descriptor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyTensorDescriptor( + InfiniOpsTensorDescriptor descriptor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateTensor( + const InfiniOpsTensorAttributes *attributes, InfiniOpsTensor *tensor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyTensor(InfiniOpsTensor tensor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetLastError(char *buffer, + size_t capacity, + size_t *required_size); + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateHandle( + const InfiniOpsHandleAttributes *attributes, InfiniOpsHandle *handle); + +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyHandle(InfiniOpsHandle handle); + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateConfig( + const InfiniOpsConfigAttributes *attributes, InfiniOpsConfig *config); + +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyConfig(InfiniOpsConfig config); + +INFINI_OPS_API InfiniOpsStatus infiniOpsAdd( + InfiniOpsHandle handle, InfiniOpsConfig config, InfiniOpsTensor input, + InfiniOpsTensor other, InfiniOpsTensor output); + +#ifdef __cplusplus +} +#endif + +#endif // INFINI_OPS_H_ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 762b9d48..27dbf0e1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,35 @@ add_library(infiniops SHARED) file(GLOB BASE_SRCS CONFIGURE_DEPENDS "*.cc") target_sources(infiniops PRIVATE ${BASE_SRCS}) +target_sources(infiniops PRIVATE infini/ops.cc) +target_compile_definitions(infiniops PRIVATE INFINI_OPS_BUILD_SHARED=1) + +if(WITH_NVIDIA OR WITH_ILUVATAR) + set_source_files_properties(infini/ops.cc PROPERTIES LANGUAGE CUDA) +endif() + +target_include_directories(infiniops + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +set_target_properties(infiniops PROPERTIES + VERSION 1.0.0 + SOVERSION 1 +) + +if(UNIX AND NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(infiniops PRIVATE -fvisibility=hidden) + target_link_options(infiniops PRIVATE + "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/infini/ops.map" + ) + set_target_properties(infiniops PROPERTIES + LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/infini/ops.map" + ) +endif() set(DEVICE_LIST "") @@ -446,8 +475,6 @@ if(WITH_TORCH) endif() endif() -target_include_directories(infiniops PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - if(GENERATE_PYTHON_BINDINGS) find_package(Python COMPONENTS Interpreter REQUIRED) # Always regenerate bindings so the included kernel headers match the @@ -596,3 +623,49 @@ if(GENERATE_PYTHON_BINDINGS) DESTINATION .) endif() endif() + +include(CMakePackageConfigHelpers) + +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/infiniops.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/infiniops.pc + @ONLY +) + +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/cmake/InfiniOpsConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/InfiniOpsConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/InfiniOps +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/InfiniOpsConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +install(TARGETS infiniops + EXPORT InfiniOpsTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(FILES ${PROJECT_SOURCE_DIR}/include/infini/ops.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/infini +) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/infiniops.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig +) + +install(EXPORT InfiniOpsTargets + NAMESPACE InfiniOps:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/InfiniOps +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/InfiniOpsConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/InfiniOpsConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/InfiniOps +) diff --git a/src/infini/ops.cc b/src/infini/ops.cc new file mode 100644 index 00000000..2d09b73c --- /dev/null +++ b/src/infini/ops.cc @@ -0,0 +1,599 @@ +#include "infini/ops.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "base/add.h" +#include "config.h" +#include "data_type.h" +#include "device.h" +#include "handle.h" +#include "tensor.h" + +#if defined(WITH_CPU) +#include "native/cpu/ops/add/add.h" +#endif +#if defined(WITH_NVIDIA) +#include "native/cuda/nvidia/ops/add/kernel.h" +#endif +#if defined(WITH_ILUVATAR) +#include "native/cuda/iluvatar/ops/add/kernel.h" +#endif +#if defined(WITH_METAX) +#include "native/cuda/metax/ops/add/kernel.h" +#endif +#if defined(WITH_MOORE) +#include "native/cuda/moore/ops/add/kernel.h" +#endif +#if defined(WITH_CAMBRICON) +#include "native/cambricon/ops/add/kernel.h" +#endif +#if defined(WITH_ASCEND) +#include "native/ascend/ops/add/kernel.h" +#endif + +struct InfiniOpsTensorDescriptorPrivate { + InfiniOpsDataType data_type = INFINI_OPS_DATA_TYPE_INVALID; + InfiniOpsDeviceType device_type = INFINI_OPS_DEVICE_TYPE_INVALID; + std::vector shape; + std::vector stride; +}; + +struct InfiniOpsTensorPrivate { + InfiniOpsTensorDescriptorPrivate descriptor; + void *data = nullptr; + size_t byte_size = 0; +}; + +struct InfiniOpsHandlePrivate { + infini::ops::Handle handle; +}; + +struct InfiniOpsConfigPrivate { + infini::ops::Config config; +}; + +namespace { + +thread_local std::string last_error; + +void SetLastError(const char *message) { last_error = message; } + +InfiniOpsStatus InvalidArgument(const char *message) { + SetLastError(message); + return INFINI_OPS_STATUS_INVALID_ARGUMENT; +} + +bool IsValidDataType(InfiniOpsDataType data_type) { + switch (data_type) { + case INFINI_OPS_DATA_TYPE_FLOAT16: + case INFINI_OPS_DATA_TYPE_BFLOAT16: + case INFINI_OPS_DATA_TYPE_FLOAT32: + case INFINI_OPS_DATA_TYPE_FLOAT64: + case INFINI_OPS_DATA_TYPE_INT8: + case INFINI_OPS_DATA_TYPE_INT16: + case INFINI_OPS_DATA_TYPE_INT32: + case INFINI_OPS_DATA_TYPE_INT64: + case INFINI_OPS_DATA_TYPE_UINT8: + case INFINI_OPS_DATA_TYPE_UINT16: + case INFINI_OPS_DATA_TYPE_UINT32: + case INFINI_OPS_DATA_TYPE_UINT64: + return true; + case INFINI_OPS_DATA_TYPE_INVALID: + return false; + } + return false; +} + +bool IsValidDeviceType(InfiniOpsDeviceType device_type) { + switch (device_type) { + case INFINI_OPS_DEVICE_TYPE_CPU: + case INFINI_OPS_DEVICE_TYPE_CUDA: + case INFINI_OPS_DEVICE_TYPE_CAMBRICON: + case INFINI_OPS_DEVICE_TYPE_ASCEND: + case INFINI_OPS_DEVICE_TYPE_METAX: + case INFINI_OPS_DEVICE_TYPE_MOORE: + case INFINI_OPS_DEVICE_TYPE_ILUVATAR: + return true; + case INFINI_OPS_DEVICE_TYPE_INVALID: + return false; + } + return false; +} + +bool ConvertDataType(InfiniOpsDataType input, infini::ops::DataType *output) { + assert(output != nullptr); + switch (input) { + case INFINI_OPS_DATA_TYPE_FLOAT16: + *output = infini::ops::DataType::kFloat16; + return true; + case INFINI_OPS_DATA_TYPE_FLOAT32: + *output = infini::ops::DataType::kFloat32; + return true; + case INFINI_OPS_DATA_TYPE_FLOAT64: + *output = infini::ops::DataType::kFloat64; + return true; + case INFINI_OPS_DATA_TYPE_BFLOAT16: + *output = infini::ops::DataType::kBFloat16; + return true; + case INFINI_OPS_DATA_TYPE_INT8: + *output = infini::ops::DataType::kInt8; + return true; + case INFINI_OPS_DATA_TYPE_INT16: + *output = infini::ops::DataType::kInt16; + return true; + case INFINI_OPS_DATA_TYPE_INT32: + *output = infini::ops::DataType::kInt32; + return true; + case INFINI_OPS_DATA_TYPE_INT64: + *output = infini::ops::DataType::kInt64; + return true; + case INFINI_OPS_DATA_TYPE_UINT8: + *output = infini::ops::DataType::kUInt8; + return true; + case INFINI_OPS_DATA_TYPE_UINT16: + *output = infini::ops::DataType::kUInt16; + return true; + case INFINI_OPS_DATA_TYPE_UINT32: + *output = infini::ops::DataType::kUInt32; + return true; + case INFINI_OPS_DATA_TYPE_UINT64: + *output = infini::ops::DataType::kUInt64; + return true; + case INFINI_OPS_DATA_TYPE_INVALID: + return false; + } + return false; +} + +bool ConvertDeviceType(InfiniOpsDeviceType input, + infini::ops::Device::Type *output) { + assert(output != nullptr); + switch (input) { + case INFINI_OPS_DEVICE_TYPE_CPU: + *output = infini::ops::Device::Type::kCpu; + return true; + case INFINI_OPS_DEVICE_TYPE_CUDA: + *output = infini::ops::Device::Type::kNvidia; + return true; + case INFINI_OPS_DEVICE_TYPE_CAMBRICON: + *output = infini::ops::Device::Type::kCambricon; + return true; + case INFINI_OPS_DEVICE_TYPE_ASCEND: + *output = infini::ops::Device::Type::kAscend; + return true; + case INFINI_OPS_DEVICE_TYPE_METAX: + *output = infini::ops::Device::Type::kMetax; + return true; + case INFINI_OPS_DEVICE_TYPE_MOORE: + *output = infini::ops::Device::Type::kMoore; + return true; + case INFINI_OPS_DEVICE_TYPE_ILUVATAR: + *output = infini::ops::Device::Type::kIluvatar; + return true; + case INFINI_OPS_DEVICE_TYPE_INVALID: + return false; + } + return false; +} + +bool DataTypeSize(InfiniOpsDataType data_type, size_t *size) { + assert(size != nullptr); + switch (data_type) { + case INFINI_OPS_DATA_TYPE_FLOAT16: + case INFINI_OPS_DATA_TYPE_BFLOAT16: + case INFINI_OPS_DATA_TYPE_INT16: + case INFINI_OPS_DATA_TYPE_UINT16: + *size = 2; + return true; + case INFINI_OPS_DATA_TYPE_FLOAT32: + case INFINI_OPS_DATA_TYPE_INT32: + case INFINI_OPS_DATA_TYPE_UINT32: + *size = 4; + return true; + case INFINI_OPS_DATA_TYPE_FLOAT64: + case INFINI_OPS_DATA_TYPE_INT64: + case INFINI_OPS_DATA_TYPE_UINT64: + *size = 8; + return true; + case INFINI_OPS_DATA_TYPE_INT8: + case INFINI_OPS_DATA_TYPE_UINT8: + *size = 1; + return true; + case INFINI_OPS_DATA_TYPE_INVALID: + return false; + } + return false; +} + +bool CheckedMultiply(size_t left, size_t right, size_t *result) { + assert(result != nullptr); + if (right != 0 && left > SIZE_MAX / right) { + return false; + } + *result = left * right; + return true; +} + +bool ComputeRequiredByteSize(const InfiniOpsTensorDescriptorPrivate &descriptor, + size_t *required_byte_size) { + assert(required_byte_size != nullptr); + + size_t element_size = 0; + if (!DataTypeSize(descriptor.data_type, &element_size)) { + return false; + } + + if (descriptor.shape.empty()) { + *required_byte_size = element_size; + return true; + } + + size_t element_count = 1; + if (descriptor.stride.empty()) { + for (int64_t dimension : descriptor.shape) { + if (!CheckedMultiply(element_count, static_cast(dimension), + &element_count)) { + return false; + } + } + return CheckedMultiply(element_count, element_size, required_byte_size); + } + + size_t max_offset = 0; + for (size_t i = 0; i < descriptor.shape.size(); ++i) { + if (descriptor.shape[i] == 0) { + *required_byte_size = 0; + return true; + } + if (descriptor.stride[i] < 0) { + return false; + } + size_t dimension_extent = 0; + if (!CheckedMultiply(static_cast(descriptor.shape[i] - 1), + static_cast(descriptor.stride[i]), + &dimension_extent)) { + return false; + } + if (SIZE_MAX - max_offset < dimension_extent) { + return false; + } + max_offset += dimension_extent; + } + + if (max_offset == SIZE_MAX) { + return false; + } + return CheckedMultiply(max_offset + 1, element_size, required_byte_size); +} + +std::vector ConvertShape( + const std::vector &shape) { + std::vector result; + result.reserve(shape.size()); + for (int64_t dimension : shape) { + result.push_back(static_cast(dimension)); + } + return result; +} + +std::vector ConvertStrides( + const std::vector &strides) { + std::vector result; + result.reserve(strides.size()); + for (int64_t stride : strides) { + result.push_back(static_cast(stride)); + } + return result; +} + +infini::ops::Tensor ToInternalTensor(const InfiniOpsTensorPrivate &tensor) { + infini::ops::DataType data_type; + const bool data_type_valid = + ConvertDataType(tensor.descriptor.data_type, &data_type); + assert(data_type_valid); + + infini::ops::Device::Type device_type; + const bool device_type_valid = + ConvertDeviceType(tensor.descriptor.device_type, &device_type); + assert(device_type_valid); + + const auto shape = ConvertShape(tensor.descriptor.shape); + const infini::ops::Device device(device_type); + if (tensor.descriptor.stride.empty()) { + return infini::ops::Tensor(tensor.data, shape, data_type, device); + } + return infini::ops::Tensor(tensor.data, shape, data_type, device, + ConvertStrides(tensor.descriptor.stride)); +} + +InfiniOpsStatus ValidateTensor(const char *name, InfiniOpsTensor tensor) { + if (tensor == nullptr) { + SetLastError(name); + last_error += " tensor must not be null"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + return INFINI_OPS_STATUS_SUCCESS; +} + +} // namespace + +extern "C" { + +InfiniOpsStatus infiniOpsCreateTensorDescriptor( + const InfiniOpsTensorDescriptorAttributes *attributes, + InfiniOpsTensorDescriptor *descriptor) { + try { + if (descriptor == nullptr) { + return InvalidArgument("descriptor output must not be null"); + } + *descriptor = nullptr; + if (attributes == nullptr) { + return InvalidArgument("tensor descriptor attributes must not be null"); + } + if (attributes->structure_size < + offsetof(InfiniOpsTensorDescriptorAttributes, reserved)) { + return InvalidArgument("tensor descriptor attributes size is invalid"); + } + if (!IsValidDataType(attributes->data_type)) { + return InvalidArgument("tensor data type is invalid"); + } + if (!IsValidDeviceType(attributes->device_type)) { + return InvalidArgument("tensor device type is invalid"); + } + if (attributes->rank < 0) { + return InvalidArgument("tensor rank must not be negative"); + } + if (attributes->rank > 0 && attributes->shape == nullptr) { + return InvalidArgument("tensor shape must not be null for non-scalar"); + } + for (int32_t i = 0; i < attributes->rank; ++i) { + if (attributes->shape[i] < 0) { + return InvalidArgument("tensor shape must not contain negative values"); + } + } + + InfiniOpsTensorDescriptorPrivate *created = + new InfiniOpsTensorDescriptorPrivate; + created->data_type = attributes->data_type; + created->device_type = attributes->device_type; + if (attributes->rank > 0) { + created->shape.assign(attributes->shape, + attributes->shape + attributes->rank); + } + if (attributes->stride != nullptr) { + for (int32_t i = 0; i < attributes->rank; ++i) { + if (attributes->stride[i] < 0) { + return InvalidArgument( + "tensor stride must not contain negative values"); + } + } + if (attributes->rank > 0) { + created->stride.assign(attributes->stride, + attributes->stride + attributes->rank); + } + } + *descriptor = created; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc &) { + SetLastError("out of memory while creating tensor descriptor"); + return INFINI_OPS_STATUS_OUT_OF_MEMORY; + } catch (const std::exception &error) { + SetLastError(error.what()); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } catch (...) { + SetLastError("unknown error while creating tensor descriptor"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +InfiniOpsStatus infiniOpsDestroyTensorDescriptor( + InfiniOpsTensorDescriptor descriptor) { + delete descriptor; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +InfiniOpsStatus infiniOpsCreateTensor( + const InfiniOpsTensorAttributes *attributes, InfiniOpsTensor *tensor) { + try { + if (tensor == nullptr) { + return InvalidArgument("tensor output must not be null"); + } + *tensor = nullptr; + if (attributes == nullptr) { + return InvalidArgument("tensor attributes must not be null"); + } + if (attributes->structure_size < + offsetof(InfiniOpsTensorAttributes, reserved)) { + return InvalidArgument("tensor attributes size is invalid"); + } + if (attributes->descriptor == nullptr) { + return InvalidArgument("tensor descriptor must not be null"); + } + + size_t required_byte_size = 0; + if (!ComputeRequiredByteSize(*attributes->descriptor, &required_byte_size)) { + return InvalidArgument("tensor descriptor byte size is invalid"); + } + if (required_byte_size > 0 && attributes->data == nullptr) { + return InvalidArgument("tensor data must not be null"); + } + if (attributes->byte_size < required_byte_size) { + return InvalidArgument("tensor byte size is smaller than descriptor size"); + } + + InfiniOpsTensorPrivate *created = new InfiniOpsTensorPrivate; + created->descriptor = *attributes->descriptor; + created->data = attributes->data; + created->byte_size = attributes->byte_size; + *tensor = created; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc &) { + SetLastError("out of memory while creating tensor"); + return INFINI_OPS_STATUS_OUT_OF_MEMORY; + } catch (const std::exception &error) { + SetLastError(error.what()); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } catch (...) { + SetLastError("unknown error while creating tensor"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +InfiniOpsStatus infiniOpsDestroyTensor(InfiniOpsTensor tensor) { + delete tensor; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +InfiniOpsStatus infiniOpsGetLastError(char *buffer, size_t capacity, + size_t *required_size) { + const size_t required = last_error.size() + 1; + if (required_size != nullptr) { + *required_size = required; + } + if (buffer == nullptr || capacity == 0) { + return last_error.empty() ? INFINI_OPS_STATUS_SUCCESS + : INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (capacity < required) { + if (capacity > 0) { + buffer[0] = '\0'; + } + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + std::memcpy(buffer, last_error.c_str(), required); + return INFINI_OPS_STATUS_SUCCESS; +} + +InfiniOpsStatus infiniOpsCreateHandle( + const InfiniOpsHandleAttributes *attributes, InfiniOpsHandle *handle) { + try { + if (handle == nullptr) { + return InvalidArgument("handle output must not be null"); + } + *handle = nullptr; + if (attributes == nullptr) { + return InvalidArgument("handle attributes must not be null"); + } + if (attributes->structure_size < + offsetof(InfiniOpsHandleAttributes, reserved)) { + return InvalidArgument("handle attributes size is invalid"); + } + if (attributes->workspace_byte_size > 0 && + attributes->workspace == nullptr) { + return InvalidArgument("handle workspace must not be null"); + } + + InfiniOpsHandlePrivate *created = new InfiniOpsHandlePrivate; + created->handle.set_stream(reinterpret_cast(attributes->stream)); + created->handle.set_workspace(attributes->workspace); + created->handle.set_workspace_size_in_bytes( + attributes->workspace_byte_size); + *handle = created; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc &) { + SetLastError("out of memory while creating handle"); + return INFINI_OPS_STATUS_OUT_OF_MEMORY; + } catch (const std::exception &error) { + SetLastError(error.what()); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } catch (...) { + SetLastError("unknown error while creating handle"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +InfiniOpsStatus infiniOpsDestroyHandle(InfiniOpsHandle handle) { + delete handle; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +InfiniOpsStatus infiniOpsCreateConfig( + const InfiniOpsConfigAttributes *attributes, InfiniOpsConfig *config) { + try { + if (config == nullptr) { + return InvalidArgument("config output must not be null"); + } + *config = nullptr; + if (attributes == nullptr) { + return InvalidArgument("config attributes must not be null"); + } + if (attributes->structure_size < + offsetof(InfiniOpsConfigAttributes, reserved)) { + return InvalidArgument("config attributes size is invalid"); + } + + InfiniOpsConfigPrivate *created = new InfiniOpsConfigPrivate; + created->config.set_implementation_index(attributes->implementation_index); + *config = created; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc &) { + SetLastError("out of memory while creating config"); + return INFINI_OPS_STATUS_OUT_OF_MEMORY; + } catch (const std::exception &error) { + SetLastError(error.what()); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } catch (...) { + SetLastError("unknown error while creating config"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +InfiniOpsStatus infiniOpsDestroyConfig(InfiniOpsConfig config) { + delete config; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +InfiniOpsStatus infiniOpsAdd(InfiniOpsHandle handle, InfiniOpsConfig config, + InfiniOpsTensor input, InfiniOpsTensor other, + InfiniOpsTensor output) { + try { + InfiniOpsStatus status = ValidateTensor("input", input); + if (status != INFINI_OPS_STATUS_SUCCESS) { + return status; + } + status = ValidateTensor("other", other); + if (status != INFINI_OPS_STATUS_SUCCESS) { + return status; + } + status = ValidateTensor("output", output); + if (status != INFINI_OPS_STATUS_SUCCESS) { + return status; + } + + const infini::ops::Handle default_handle; + const infini::ops::Config default_config; + infini::ops::Operator::Call( + handle == nullptr ? default_handle : handle->handle, + config == nullptr ? default_config : config->config, + ToInternalTensor(*input), ToInternalTensor(*other), + ToInternalTensor(*output)); + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc &) { + SetLastError("out of memory while running `infiniOpsAdd`"); + return INFINI_OPS_STATUS_OUT_OF_MEMORY; + } catch (const std::exception &error) { + SetLastError(error.what()); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } catch (...) { + SetLastError("unknown error while running `infiniOpsAdd`"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +} // extern "C" diff --git a/src/infini/ops.map b/src/infini/ops.map new file mode 100644 index 00000000..ea6695ed --- /dev/null +++ b/src/infini/ops.map @@ -0,0 +1,6 @@ +INFINIOPS_1 { + global: + infiniOps*; + local: + *; +}; diff --git a/tests/test_c_api.py b/tests/test_c_api.py new file mode 100644 index 00000000..369045c1 --- /dev/null +++ b/tests/test_c_api.py @@ -0,0 +1,219 @@ +import os +import subprocess +import textwrap +from pathlib import Path + +import pytest + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +INCLUDE_DIR = PROJECT_ROOT / "include" + + +def test_c_api_header_compiles_with_c(tmp_path): + source = tmp_path / "header_smoke.c" + source.write_text( + '#include \n' + "int main(void) { return INFINI_OPS_STATUS_SUCCESS; }\n" + ) + output = tmp_path / "header_smoke.o" + + _run( + [ + _compiler("CC", "cc"), + "-std=c11", + "-Werror", + f"-I{INCLUDE_DIR}", + "-c", + str(source), + "-o", + str(output), + ] + ) + + +def test_c_api_header_compiles_with_cpp(tmp_path): + source = tmp_path / "header_smoke.cc" + source.write_text( + '#include \n' + "int main() { return INFINI_OPS_STATUS_SUCCESS; }\n" + ) + output = tmp_path / "header_smoke.o" + + _run( + [ + _compiler("CXX", "c++"), + "-std=c++17", + "-Werror", + f"-I{INCLUDE_DIR}", + "-c", + str(source), + "-o", + str(output), + ] + ) + + +def test_c_api_add_smoke(tmp_path): + library_dir = _installed_library_dir() + source = tmp_path / "add_smoke.c" + binary = tmp_path / "add_smoke" + source.write_text(_ADD_SMOKE_SOURCE) + + _run( + [ + _compiler("CC", "cc"), + "-std=c11", + "-Werror", + f"-I{INCLUDE_DIR}", + str(source), + f"-L{library_dir}", + "-linfiniops", + f"-Wl,-rpath,{library_dir}", + "-o", + str(binary), + ] + ) + _run([str(binary)]) + + +def _compiler(env_name, default): + compiler = os.environ.get(env_name, default) + + if not compiler: + pytest.skip(f"`{env_name}` is not configured.") + + return compiler + + +def _installed_library_dir(): + library_dir = os.environ.get("INFINIOPS_LIBRARY_DIR") + + if library_dir: + return Path(library_dir) + + try: + import infini.ops + except ImportError as error: + pytest.skip( + "`infini.ops` is not installed and `INFINIOPS_LIBRARY_DIR` is not set: " + f"{error}" + ) + + return Path(infini.ops.__file__).resolve().parent + + +def _run(command): + try: + subprocess.run(command, check=True, text=True, capture_output=True) + except FileNotFoundError as error: + pytest.skip(f"`{command[0]}` is not available: {error}") + except subprocess.CalledProcessError as error: + output = "\n".join((error.stdout, error.stderr)).strip() + raise AssertionError(output) from error + + +_ADD_SMOKE_SOURCE = textwrap.dedent( + r""" + #include + + #include + #include + + int main(void) { + int64_t shape[1] = {3}; + InfiniOpsTensorDescriptorAttributes descriptor_attributes = {0}; + descriptor_attributes.structure_size = sizeof(descriptor_attributes); + descriptor_attributes.data_type = INFINI_OPS_DATA_TYPE_FLOAT32; + descriptor_attributes.device_type = INFINI_OPS_DEVICE_TYPE_CPU; + descriptor_attributes.rank = 1; + descriptor_attributes.shape = shape; + + InfiniOpsTensorDescriptor descriptor = NULL; + if (infiniOpsCreateTensorDescriptor(&descriptor_attributes, &descriptor) != + INFINI_OPS_STATUS_SUCCESS) { + return 1; + } + + float input_data[3] = {1.0f, 2.0f, 3.0f}; + float other_data[3] = {4.0f, 5.0f, 6.0f}; + float output_data[3] = {0.0f, 0.0f, 0.0f}; + + InfiniOpsTensorAttributes input_attributes = {0}; + input_attributes.structure_size = sizeof(input_attributes); + input_attributes.descriptor = descriptor; + input_attributes.data = input_data; + input_attributes.byte_size = sizeof(input_data); + + InfiniOpsTensorAttributes other_attributes = input_attributes; + other_attributes.data = other_data; + other_attributes.byte_size = sizeof(other_data); + + InfiniOpsTensorAttributes output_attributes = input_attributes; + output_attributes.data = output_data; + output_attributes.byte_size = sizeof(output_data); + + InfiniOpsTensor input = NULL; + InfiniOpsTensor other = NULL; + InfiniOpsTensor output = NULL; + InfiniOpsHandleAttributes handle_attributes = {0}; + handle_attributes.structure_size = sizeof(handle_attributes); + InfiniOpsHandle handle = NULL; + if (infiniOpsCreateHandle(&handle_attributes, &handle) != + INFINI_OPS_STATUS_SUCCESS) { + return 11; + } + + InfiniOpsConfigAttributes config_attributes = {0}; + config_attributes.structure_size = sizeof(config_attributes); + InfiniOpsConfig config = NULL; + if (infiniOpsCreateConfig(&config_attributes, &config) != + INFINI_OPS_STATUS_SUCCESS) { + return 12; + } + + if (infiniOpsCreateTensor(&input_attributes, &input) != + INFINI_OPS_STATUS_SUCCESS) { + return 2; + } + if (infiniOpsCreateTensor(&other_attributes, &other) != + INFINI_OPS_STATUS_SUCCESS) { + return 3; + } + if (infiniOpsCreateTensor(&output_attributes, &output) != + INFINI_OPS_STATUS_SUCCESS) { + return 4; + } + if (infiniOpsAdd(handle, config, input, other, output) != + INFINI_OPS_STATUS_SUCCESS) { + return 5; + } + + if (output_data[0] != 5.0f || output_data[1] != 7.0f || + output_data[2] != 9.0f) { + return 6; + } + + if (infiniOpsDestroyTensor(input) != INFINI_OPS_STATUS_SUCCESS) { + return 7; + } + if (infiniOpsDestroyTensor(other) != INFINI_OPS_STATUS_SUCCESS) { + return 8; + } + if (infiniOpsDestroyTensor(output) != INFINI_OPS_STATUS_SUCCESS) { + return 9; + } + if (infiniOpsDestroyTensorDescriptor(descriptor) != + INFINI_OPS_STATUS_SUCCESS) { + return 10; + } + if (infiniOpsDestroyConfig(config) != INFINI_OPS_STATUS_SUCCESS) { + return 13; + } + if (infiniOpsDestroyHandle(handle) != INFINI_OPS_STATUS_SUCCESS) { + return 14; + } + return 0; + } + """ +).lstrip()