From 2a1a0055728c98b8e6c5e2800815c7f6505a21e1 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Mon, 25 May 2026 16:59:59 +0800 Subject: [PATCH 1/3] feat: build C API on functional operator API --- include/infini/ops.h | 106 +++++++++ scripts/generate_wrappers.py | 145 ++++++++++++ src/CMakeLists.txt | 38 ++- src/infini/ops.cc | 446 +++++++++++++++++++++++++++++++++++ src/infini/ops.map | 10 + tests/test_c_api.py | 207 ++++++++++++++++ 6 files changed, 943 insertions(+), 9 deletions(-) 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/include/infini/ops.h b/include/infini/ops.h index db17bd335..6dae8c182 100644 --- a/include/infini/ops.h +++ b/include/infini/ops.h @@ -1,8 +1,114 @@ #ifndef INFINI_OPS_H_ #define INFINI_OPS_H_ +#include +#include + #ifdef __cplusplus #include + +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, + INFINI_OPS_STATUS_INVALID_ARGUMENT, + INFINI_OPS_STATUS_NOT_SUPPORTED, + INFINI_OPS_STATUS_OUT_OF_MEMORY, + INFINI_OPS_STATUS_INTERNAL_ERROR, +} InfiniOpsStatus; + +typedef enum InfiniOpsDataType { + INFINI_OPS_DATA_TYPE_INVALID, + INFINI_OPS_DATA_TYPE_INT8, + INFINI_OPS_DATA_TYPE_INT16, + INFINI_OPS_DATA_TYPE_INT32, + INFINI_OPS_DATA_TYPE_INT64, + INFINI_OPS_DATA_TYPE_UINT8, + INFINI_OPS_DATA_TYPE_UINT16, + INFINI_OPS_DATA_TYPE_UINT32, + INFINI_OPS_DATA_TYPE_UINT64, + INFINI_OPS_DATA_TYPE_FLOAT16, + INFINI_OPS_DATA_TYPE_BFLOAT16, + INFINI_OPS_DATA_TYPE_FLOAT32, + INFINI_OPS_DATA_TYPE_FLOAT64, +} InfiniOpsDataType; + +typedef enum InfiniOpsDeviceType { + INFINI_OPS_DEVICE_TYPE_INVALID, + INFINI_OPS_DEVICE_TYPE_CPU, + INFINI_OPS_DEVICE_TYPE_NVIDIA, + INFINI_OPS_DEVICE_TYPE_CAMBRICON, + INFINI_OPS_DEVICE_TYPE_ASCEND, + INFINI_OPS_DEVICE_TYPE_METAX, + INFINI_OPS_DEVICE_TYPE_MOORE, + INFINI_OPS_DEVICE_TYPE_ILUVATAR, +} InfiniOpsDeviceType; + +typedef struct InfiniOpsTensor { + size_t structure_size; + void* data; + size_t byte_size; + InfiniOpsDataType data_type; + InfiniOpsDeviceType device_type; + int32_t rank; + const int64_t* shape; + const int64_t* stride; + uint64_t reserved[8]; +} InfiniOpsTensor; + +typedef struct InfiniOpsStreamPrivate* InfiniOpsStream; +typedef struct InfiniOpsHandlePrivate* InfiniOpsHandle; +typedef struct InfiniOpsConfigPrivate* InfiniOpsConfig; + +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 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); + +#include + +#ifdef __cplusplus +} #endif #endif // INFINI_OPS_H_ diff --git a/scripts/generate_wrappers.py b/scripts/generate_wrappers.py index b612ac9c1..cfcc6dd73 100644 --- a/scripts/generate_wrappers.py +++ b/scripts/generate_wrappers.py @@ -37,6 +37,8 @@ _INDENTATION = " " +_C_API_OPERATOR_NAMES = frozenset({"add"}) + @functools.lru_cache(maxsize=1) def _get_system_include_flags(): @@ -852,6 +854,115 @@ def _append_optional_params(prefix, params): return declarations, definitions +def _generate_c_api_entries(operator): + pascal_case_op_name = _snake_to_pascal(operator.name) + declarations = [] + definitions = [] + + if operator.name not in _C_API_OPERATOR_NAMES: + return declarations, definitions + + for call in operator.calls: + params = _generate_c_api_params(call) + validations = _generate_c_api_validations(call) + args = _generate_c_api_arguments(call) + signature = _format_c_api_signature(f"infiniOps{pascal_case_op_name}", params) + declarations.append(f"INFINI_OPS_API {signature};") + definitions.append( + f"""{signature} {{ + try {{ +{validations} + const infini::ops::Handle default_handle; + const infini::ops::Config default_config; + infini::ops::functional::{pascal_case_op_name}( + handle == nullptr ? default_handle : handle->handle, + config == nullptr ? default_config : config->config{args}); + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + }} catch (const std::bad_alloc&) {{ + SetLastError("out of memory while running `infiniOps{pascal_case_op_name}`"); + 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 `infiniOps{pascal_case_op_name}`"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + }} +}}""" + ) + + return declarations, definitions + + +def _generate_c_api_params(node): + params = ["InfiniOpsHandle handle", "InfiniOpsConfig config"] + + for arg in node.get_arguments(): + if arg.spelling == "stream": + continue + + params.append(_c_api_param(arg)) + + return params + + +def _format_c_api_signature(name, params): + return ( + f"InfiniOpsStatus {name}(\n {', '.join(params[:2])},\n " + + ",\n ".join(params[2:]) + + ")" + ) + + +def _c_api_param(arg): + if arg.type.spelling == "const Tensor": + return f"const InfiniOpsTensor* {arg.spelling}" + + if arg.type.spelling == "Tensor": + return f"InfiniOpsTensor* {arg.spelling}" + + raise ValueError( + f"unsupported C API parameter {arg.spelling!r}: {arg.type.spelling!r}" + ) + + +def _generate_c_api_validations(node): + lines = [] + + for arg in node.get_arguments(): + if arg.spelling == "stream": + continue + + if arg.type.spelling not in {"const Tensor", "Tensor"}: + continue + + lines.extend( + ( + f" InfiniOpsStatus {arg.spelling}_status = " + f'ValidateTensor("{arg.spelling}", {arg.spelling});', + f" if ({arg.spelling}_status != INFINI_OPS_STATUS_SUCCESS) {{", + f" return {arg.spelling}_status;", + " }", + ) + ) + + return "\n".join(lines) + + +def _generate_c_api_arguments(node): + args = [ + f"ToInternalTensor(*{arg.spelling})" + for arg in node.get_arguments() + if arg.spelling != "stream" + ] + + if not args: + return "" + + return ",\n " + ",\n ".join(args) + + def _generate_generated_dispatch_header(op_names, devices, declarations): header_base_includes = "\n".join( f'#include "base/{op_name}.h"' for op_name in op_names @@ -954,6 +1065,23 @@ def _generate_functional_source(op_names, devices, impl_paths, definitions): """ +def _generate_c_api_header(declarations): + return f"""#ifndef INFINI_OPS_C_OPS_H_ +#define INFINI_OPS_C_OPS_H_ + +{chr(10).join(declarations)} + +#endif +""" + + +def _generate_c_api_source(definitions): + return f"""// Generated C ABI operator wrappers. + +{chr(10).join(definitions)} +""" + + def _device_marker_headers(devices): paths = { "cpu": "native/cpu/device_.h", @@ -1074,6 +1202,7 @@ def _generate_op_artifacts(item): functional_declarations, functional_definitions = _generate_functional_entries( operator ) + c_api_declarations, c_api_definitions = _generate_c_api_entries(operator) return { "op_name": op_name, @@ -1087,6 +1216,8 @@ def _generate_op_artifacts(item): "dispatch_definitions": dispatch_definitions, "functional_declarations": functional_declarations, "functional_definitions": functional_definitions, + "c_api_declarations": c_api_declarations, + "c_api_definitions": c_api_definitions, "impl_paths": impl_paths, } @@ -1182,6 +1313,16 @@ def _dispatch_gen_batch_size(): for artifact in artifacts for declaration in artifact["functional_declarations"] ] + c_api_declarations = [ + declaration + for artifact in artifacts + for declaration in artifact["c_api_declarations"] + ] + c_api_definitions = [ + definition + for artifact in artifacts + for definition in artifact["c_api_definitions"] + ] use_monolithic_bindings = _use_monolithic_bindings() op_includes = [] @@ -1213,6 +1354,10 @@ def _dispatch_gen_batch_size(): functional_header = _generate_functional_header(functional_declarations) (_PUBLIC_INCLUDE_DIR / "functional_ops.h").write_text(functional_header) + c_api_header = _generate_c_api_header(c_api_declarations) + (_PUBLIC_INCLUDE_DIR / "c_ops.h").write_text(c_api_header) + c_api_source = _generate_c_api_source(c_api_definitions) + (_GENERATED_SRC_DIR / "c_ops.inc").write_text(c_api_source) dispatch_batch_size = _dispatch_gen_batch_size() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7710f648c..7d9f602a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,32 @@ 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) + +target_include_directories(infiniops + PUBLIC + $ + $ + $ + $ + PRIVATE + ${PROJECT_SOURCE_DIR}/generated/src +) + +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_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 "") @@ -477,14 +503,6 @@ if(WITH_TORCH) endif() endif() -target_include_directories(infiniops - PUBLIC - $ - $ - $ - $ -) - if(GENERATE_CPP_OPERATOR_API OR GENERATE_PYTHON_BINDINGS) find_package(Python COMPONENTS Interpreter REQUIRED) # Always regenerate wrappers so the generated functional API and pybind11 @@ -809,7 +827,9 @@ install(FILES ${PROJECT_SOURCE_DIR}/include/infini/ops.h ) if(GENERATE_CPP_OPERATOR_API OR GENERATE_PYTHON_BINDINGS) - install(FILES ${PROJECT_SOURCE_DIR}/generated/include/infini/functional_ops.h + install(FILES + ${PROJECT_SOURCE_DIR}/generated/include/infini/functional_ops.h + ${PROJECT_SOURCE_DIR}/generated/include/infini/c_ops.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/infini ) endif() diff --git a/src/infini/ops.cc b/src/infini/ops.cc new file mode 100644 index 000000000..a65a3adf8 --- /dev/null +++ b/src/infini/ops.cc @@ -0,0 +1,446 @@ +#include "infini/ops.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +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_NVIDIA: + 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_NVIDIA: + *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 InfiniOpsTensor& tensor, + size_t* required_byte_size) { + assert(required_byte_size != nullptr); + + size_t element_size = 0; + if (!DataTypeSize(tensor.data_type, &element_size)) { + return false; + } + + if (tensor.rank == 0) { + *required_byte_size = element_size; + return true; + } + + size_t element_count = 1; + if (tensor.stride == nullptr) { + for (int32_t i = 0; i < tensor.rank; ++i) { + if (!CheckedMultiply(element_count, static_cast(tensor.shape[i]), + &element_count)) { + return false; + } + } + return CheckedMultiply(element_count, element_size, required_byte_size); + } + + size_t max_offset = 0; + for (int32_t i = 0; i < tensor.rank; ++i) { + if (tensor.shape[i] == 0) { + *required_byte_size = 0; + return true; + } + if (tensor.stride[i] < 0) { + return false; + } + size_t dimension_extent = 0; + if (!CheckedMultiply(static_cast(tensor.shape[i] - 1), + static_cast(tensor.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 InfiniOpsTensor& tensor) { + std::vector result; + result.reserve(tensor.rank); + for (int32_t i = 0; i < tensor.rank; ++i) { + result.push_back(static_cast(tensor.shape[i])); + } + return result; +} + +std::vector ConvertStrides( + const InfiniOpsTensor& tensor) { + std::vector result; + result.reserve(tensor.rank); + if (tensor.stride == nullptr) { + return result; + } + for (int32_t i = 0; i < tensor.rank; ++i) { + result.push_back( + static_cast(tensor.stride[i])); + } + return result; +} + +infini::ops::Tensor ToInternalTensor(const InfiniOpsTensor& tensor) { + infini::ops::DataType data_type; + const bool data_type_valid = ConvertDataType(tensor.data_type, &data_type); + assert(data_type_valid); + + infini::ops::Device::Type device_type; + const bool device_type_valid = + ConvertDeviceType(tensor.device_type, &device_type); + assert(device_type_valid); + + const auto shape = ConvertShape(tensor); + const infini::ops::Device device(device_type); + if (tensor.stride == nullptr) { + return infini::ops::Tensor(tensor.data, shape, data_type, device); + } + return infini::ops::Tensor(tensor.data, shape, data_type, device, + ConvertStrides(tensor)); +} + +InfiniOpsStatus ValidateTensor(const char* name, + const InfiniOpsTensor* tensor) { + if (tensor == nullptr) { + SetLastError(name); + last_error += " tensor must not be null"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (tensor->structure_size < offsetof(InfiniOpsTensor, reserved)) { + SetLastError(name); + last_error += " tensor structure size is invalid"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (!IsValidDataType(tensor->data_type)) { + SetLastError(name); + last_error += " tensor data type is invalid"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (!IsValidDeviceType(tensor->device_type)) { + SetLastError(name); + last_error += " tensor device type is invalid"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (tensor->rank < 0) { + SetLastError(name); + last_error += " tensor rank must not be negative"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (tensor->rank > 0 && tensor->shape == nullptr) { + SetLastError(name); + last_error += " tensor shape must not be null for non-scalar"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + for (int32_t i = 0; i < tensor->rank; ++i) { + if (tensor->shape[i] < 0) { + SetLastError(name); + last_error += " tensor shape must not contain negative values"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + } + + size_t required_byte_size = 0; + if (!ComputeRequiredByteSize(*tensor, &required_byte_size)) { + SetLastError(name); + last_error += " tensor byte size is invalid"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (required_byte_size > 0 && tensor->data == nullptr) { + SetLastError(name); + last_error += " tensor data must not be null"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + if (tensor->byte_size < required_byte_size) { + SetLastError(name); + last_error += " tensor byte size is smaller than shape requires"; + return INFINI_OPS_STATUS_INVALID_ARGUMENT; + } + return INFINI_OPS_STATUS_SUCCESS; +} + +} // namespace + +extern "C" { + +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; +} + +#include "c_ops.inc" + +} // extern "C" diff --git a/src/infini/ops.map b/src/infini/ops.map new file mode 100644 index 000000000..1c3c75111 --- /dev/null +++ b/src/infini/ops.map @@ -0,0 +1,10 @@ +INFINIOPS_1 { + global: + extern "C++" { + infini::ops::*; + infini::ops::functional::*; + }; + infiniOps*; + local: + *; +}; diff --git a/tests/test_c_api.py b/tests/test_c_api.py new file mode 100644 index 000000000..8e0b2c218 --- /dev/null +++ b/tests/test_c_api.py @@ -0,0 +1,207 @@ +import os +import subprocess +import textwrap +from pathlib import Path + +import pytest + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] + + +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", + *_include_flags(), + "-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 \nint main() { return INFINI_OPS_STATUS_SUCCESS; }\n" + ) + output = tmp_path / "header_smoke.o" + + _run( + [ + _compiler("CXX", "c++"), + "-std=c++17", + "-Werror", + *_include_flags(require_cpp_api=True), + "-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", + *_include_flags(), + 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 _include_flags(require_cpp_api=False): + install_prefix = os.environ.get("INFINIOPS_INSTALL_PREFIX") + + if install_prefix: + return [f"-I{Path(install_prefix) / 'include'}"] + + flags = [f"-I{PROJECT_ROOT / 'include'}"] + generated_include_dir = PROJECT_ROOT / "generated" / "include" + + if generated_include_dir.exists(): + flags.append(f"-I{generated_include_dir}") + elif require_cpp_api: + pytest.skip("generated C++ API headers are not available.") + + return flags + + +def _installed_library_dir(): + install_prefix = os.environ.get("INFINIOPS_INSTALL_PREFIX") + + if install_prefix: + for name in ("lib", "lib64"): + library_dir = Path(install_prefix) / name + if (library_dir / "libinfiniops.so").exists(): + return library_dir + pytest.skip(f"`libinfiniops.so` was not found under `{install_prefix}`.") + + 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 neither " + "`INFINIOPS_INSTALL_PREFIX` nor `INFINIOPS_LIBRARY_DIR` is 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}; + + 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}; + + InfiniOpsTensor input = {0}; + input.structure_size = sizeof(input); + input.data = input_data; + input.byte_size = sizeof(input_data); + input.data_type = INFINI_OPS_DATA_TYPE_FLOAT32; + input.device_type = INFINI_OPS_DEVICE_TYPE_CPU; + input.rank = 1; + input.shape = shape; + + InfiniOpsTensor other = input; + other.data = other_data; + other.byte_size = sizeof(other_data); + + InfiniOpsTensor output = input; + output.data = output_data; + output.byte_size = sizeof(output_data); + + InfiniOpsHandleAttributes handle_attributes = {0}; + handle_attributes.structure_size = sizeof(handle_attributes); + InfiniOpsHandle handle = NULL; + if (infiniOpsCreateHandle(&handle_attributes, &handle) != + INFINI_OPS_STATUS_SUCCESS) { + return 1; + } + + InfiniOpsConfigAttributes config_attributes = {0}; + config_attributes.structure_size = sizeof(config_attributes); + InfiniOpsConfig config = NULL; + if (infiniOpsCreateConfig(&config_attributes, &config) != + INFINI_OPS_STATUS_SUCCESS) { + return 2; + } + + if (infiniOpsAdd(handle, config, &input, &other, &output) != + INFINI_OPS_STATUS_SUCCESS) { + return 3; + } + + if (output_data[0] != 5.0f || output_data[1] != 7.0f || + output_data[2] != 9.0f) { + return 4; + } + + if (infiniOpsDestroyConfig(config) != INFINI_OPS_STATUS_SUCCESS) { + return 5; + } + if (infiniOpsDestroyHandle(handle) != INFINI_OPS_STATUS_SUCCESS) { + return 6; + } + return 0; + } + """ +).lstrip() From 9ea42a8b295be42afeb875c3f911df860f92009c Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Tue, 26 May 2026 02:25:16 +0800 Subject: [PATCH 2/3] fix: stabilize operator C API builds --- include/infini/c_ops.h | 10 +++++++ scripts/generate_wrappers.py | 2 +- src/CMakeLists.txt | 4 ++- src/infini/ops.cc | 13 ++++----- tests/test_c_api.py | 52 +++++++++++++++++++++++++++++++++--- 5 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 include/infini/c_ops.h diff --git a/include/infini/c_ops.h b/include/infini/c_ops.h new file mode 100644 index 000000000..ca946019c --- /dev/null +++ b/include/infini/c_ops.h @@ -0,0 +1,10 @@ +#ifndef INFINI_C_OPS_H_ +#define INFINI_C_OPS_H_ + +INFINI_OPS_API InfiniOpsStatus infiniOpsAdd(InfiniOpsHandle handle, + InfiniOpsConfig config, + const InfiniOpsTensor* input, + const InfiniOpsTensor* other, + InfiniOpsTensor* out); + +#endif // INFINI_C_OPS_H_ diff --git a/scripts/generate_wrappers.py b/scripts/generate_wrappers.py index cfcc6dd73..1e320072c 100644 --- a/scripts/generate_wrappers.py +++ b/scripts/generate_wrappers.py @@ -869,7 +869,7 @@ def _generate_c_api_entries(operator): signature = _format_c_api_signature(f"infiniOps{pascal_case_op_name}", params) declarations.append(f"INFINI_OPS_API {signature};") definitions.append( - f"""{signature} {{ + f"""INFINI_OPS_API {signature} {{ try {{ {validations} const infini::ops::Handle default_handle; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d9f602a4..315f5bb99 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -822,7 +822,9 @@ install(TARGETS infiniops RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -install(FILES ${PROJECT_SOURCE_DIR}/include/infini/ops.h +install(FILES + ${PROJECT_SOURCE_DIR}/include/infini/ops.h + ${PROJECT_SOURCE_DIR}/include/infini/c_ops.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/infini ) diff --git a/src/infini/ops.cc b/src/infini/ops.cc index a65a3adf8..a21421add 100644 --- a/src/infini/ops.cc +++ b/src/infini/ops.cc @@ -338,8 +338,9 @@ InfiniOpsStatus ValidateTensor(const char* name, extern "C" { -InfiniOpsStatus infiniOpsGetLastError(char* buffer, size_t capacity, - size_t* required_size) { +INFINI_OPS_API 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; @@ -358,7 +359,7 @@ InfiniOpsStatus infiniOpsGetLastError(char* buffer, size_t capacity, return INFINI_OPS_STATUS_SUCCESS; } -InfiniOpsStatus infiniOpsCreateHandle( +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateHandle( const InfiniOpsHandleAttributes* attributes, InfiniOpsHandle* handle) { try { if (handle == nullptr) { @@ -397,13 +398,13 @@ InfiniOpsStatus infiniOpsCreateHandle( } } -InfiniOpsStatus infiniOpsDestroyHandle(InfiniOpsHandle handle) { +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyHandle(InfiniOpsHandle handle) { delete handle; SetLastError(""); return INFINI_OPS_STATUS_SUCCESS; } -InfiniOpsStatus infiniOpsCreateConfig( +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateConfig( const InfiniOpsConfigAttributes* attributes, InfiniOpsConfig* config) { try { if (config == nullptr) { @@ -435,7 +436,7 @@ InfiniOpsStatus infiniOpsCreateConfig( } } -InfiniOpsStatus infiniOpsDestroyConfig(InfiniOpsConfig config) { +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyConfig(InfiniOpsConfig config) { delete config; SetLastError(""); return INFINI_OPS_STATUS_SUCCESS; diff --git a/tests/test_c_api.py b/tests/test_c_api.py index 8e0b2c218..b75e2bf3d 100644 --- a/tests/test_c_api.py +++ b/tests/test_c_api.py @@ -65,9 +65,7 @@ def test_c_api_add_smoke(tmp_path): "-Werror", *_include_flags(), str(source), - f"-L{library_dir}", - "-linfiniops", - f"-Wl,-rpath,{library_dir}", + *_library_link_flags(library_dir), "-o", str(binary), ] @@ -81,6 +79,9 @@ def _compiler(env_name, default): if not compiler: pytest.skip(f"`{env_name}` is not configured.") + if env_name == "CXX" and "cu-bridge" in compiler: + compiler = default + return compiler @@ -90,7 +91,7 @@ def _include_flags(require_cpp_api=False): if install_prefix: return [f"-I{Path(install_prefix) / 'include'}"] - flags = [f"-I{PROJECT_ROOT / 'include'}"] + flags = [f"-I{PROJECT_ROOT / 'include'}", f"-I{PROJECT_ROOT / 'src'}"] generated_include_dir = PROJECT_ROOT / "generated" / "include" if generated_include_dir.exists(): @@ -128,6 +129,49 @@ def _installed_library_dir(): return Path(infini.ops.__file__).resolve().parent +def _library_link_flags(library_dir): + flags = [f"-L{library_dir}", "-linfiniops", f"-Wl,-rpath,{library_dir}"] + + for runtime_dir in _python_runtime_library_dirs(): + flags.extend( + [ + f"-L{runtime_dir}", + f"-Wl,-rpath,{runtime_dir}", + f"-Wl,-rpath-link,{runtime_dir}", + ] + ) + + if (runtime_dir / "libiomp5.so").exists(): + flags.append("-liomp5") + elif (runtime_dir / "libomp.so").exists(): + flags.append("-lomp") + + return flags + + +def _python_runtime_library_dirs(): + runtime_dirs = [] + + try: + import torch + except ImportError: + return runtime_dirs + + site_packages = Path(torch.__file__).resolve().parents[1] + for name in ("torch/lib", "torch.libs"): + runtime_dir = site_packages / name + if runtime_dir.exists(): + runtime_dirs.append(runtime_dir) + + maca_path = os.environ.get("MACA_PATH") + if maca_path: + runtime_dir = Path(maca_path) / "mxgpu_llvm" / "lib" + if runtime_dir.exists(): + runtime_dirs.append(runtime_dir) + + return runtime_dirs + + def _run(command): try: subprocess.run(command, check=True, text=True, capture_output=True) From a8fa2760d1422bfffae5333a3d8254a1b761655e Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Thu, 28 May 2026 04:11:54 +0800 Subject: [PATCH 3/3] fix: make operator C API objects opaque --- include/infini/c_ops.h | 10 - include/infini/ops.h | 96 +++++--- scripts/generate_wrappers.py | 7 +- src/CMakeLists.txt | 1 - src/infini/ops.cc | 437 +++++++++++++++++++++++++++++------ tests/test_c_api.py | 102 +++++--- 6 files changed, 507 insertions(+), 146 deletions(-) delete mode 100644 include/infini/c_ops.h diff --git a/include/infini/c_ops.h b/include/infini/c_ops.h deleted file mode 100644 index ca946019c..000000000 --- a/include/infini/c_ops.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef INFINI_C_OPS_H_ -#define INFINI_C_OPS_H_ - -INFINI_OPS_API InfiniOpsStatus infiniOpsAdd(InfiniOpsHandle handle, - InfiniOpsConfig config, - const InfiniOpsTensor* input, - const InfiniOpsTensor* other, - InfiniOpsTensor* out); - -#endif // INFINI_C_OPS_H_ diff --git a/include/infini/ops.h b/include/infini/ops.h index 6dae8c182..01a807823 100644 --- a/include/infini/ops.h +++ b/include/infini/ops.h @@ -61,50 +61,86 @@ typedef enum InfiniOpsDeviceType { INFINI_OPS_DEVICE_TYPE_ILUVATAR, } InfiniOpsDeviceType; -typedef struct InfiniOpsTensor { - size_t structure_size; - void* data; - size_t byte_size; - InfiniOpsDataType data_type; - InfiniOpsDeviceType device_type; - int32_t rank; - const int64_t* shape; - const int64_t* stride; - uint64_t reserved[8]; -} InfiniOpsTensor; - typedef struct InfiniOpsStreamPrivate* InfiniOpsStream; +typedef struct InfiniOpsTensorPrivate* InfiniOpsTensor; typedef struct InfiniOpsHandlePrivate* InfiniOpsHandle; typedef struct InfiniOpsConfigPrivate* InfiniOpsConfig; -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 infiniOpsGetLastError(char* buffer, size_t capacity, size_t* required_size); -INFINI_OPS_API InfiniOpsStatus infiniOpsCreateHandle( - const InfiniOpsHandleAttributes* attributes, InfiniOpsHandle* handle); +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateTensor(InfiniOpsTensor* tensor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyTensor(InfiniOpsTensor tensor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorData(InfiniOpsTensor tensor, + void* data); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorData(InfiniOpsTensor tensor, + void** data); + +INFINI_OPS_API InfiniOpsStatus +infiniOpsSetTensorByteSize(InfiniOpsTensor tensor, size_t byte_size); + +INFINI_OPS_API InfiniOpsStatus +infiniOpsGetTensorByteSize(InfiniOpsTensor tensor, size_t* byte_size); + +INFINI_OPS_API InfiniOpsStatus +infiniOpsSetTensorDataType(InfiniOpsTensor tensor, InfiniOpsDataType data_type); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorDataType( + InfiniOpsTensor tensor, InfiniOpsDataType* data_type); + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorDeviceType( + InfiniOpsTensor tensor, InfiniOpsDeviceType device_type); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorDeviceType( + InfiniOpsTensor tensor, InfiniOpsDeviceType* device_type); + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorShape(InfiniOpsTensor tensor, + int32_t rank, + const int64_t* shape); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorShape(InfiniOpsTensor tensor, + int32_t* rank, + const int64_t** shape); + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorStride(InfiniOpsTensor tensor, + const int64_t* stride); + +INFINI_OPS_API InfiniOpsStatus +infiniOpsClearTensorStride(InfiniOpsTensor tensor); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorStride(InfiniOpsTensor tensor, + const int64_t** stride); + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateHandle(InfiniOpsHandle* handle); INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyHandle(InfiniOpsHandle handle); -INFINI_OPS_API InfiniOpsStatus infiniOpsCreateConfig( - const InfiniOpsConfigAttributes* attributes, InfiniOpsConfig* config); +INFINI_OPS_API InfiniOpsStatus infiniOpsSetHandleStream(InfiniOpsHandle handle, + InfiniOpsStream stream); + +INFINI_OPS_API InfiniOpsStatus +infiniOpsGetHandleStream(InfiniOpsHandle handle, InfiniOpsStream* stream); + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetHandleWorkspace( + InfiniOpsHandle handle, void* workspace, size_t byte_size); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetHandleWorkspace( + InfiniOpsHandle handle, void** workspace, size_t* byte_size); + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateConfig(InfiniOpsConfig* config); INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyConfig(InfiniOpsConfig config); +INFINI_OPS_API InfiniOpsStatus infiniOpsSetConfigImplementationIndex( + InfiniOpsConfig config, size_t implementation_index); + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetConfigImplementationIndex( + InfiniOpsConfig config, size_t* implementation_index); + #include #ifdef __cplusplus diff --git a/scripts/generate_wrappers.py b/scripts/generate_wrappers.py index 1e320072c..e06dc14b0 100644 --- a/scripts/generate_wrappers.py +++ b/scripts/generate_wrappers.py @@ -916,11 +916,8 @@ def _format_c_api_signature(name, params): def _c_api_param(arg): - if arg.type.spelling == "const Tensor": - return f"const InfiniOpsTensor* {arg.spelling}" - - if arg.type.spelling == "Tensor": - return f"InfiniOpsTensor* {arg.spelling}" + if arg.type.spelling in {"const Tensor", "Tensor"}: + return f"InfiniOpsTensor {arg.spelling}" raise ValueError( f"unsupported C API parameter {arg.spelling!r}: {arg.type.spelling!r}" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 315f5bb99..4fa67f185 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -824,7 +824,6 @@ install(TARGETS infiniops install(FILES ${PROJECT_SOURCE_DIR}/include/infini/ops.h - ${PROJECT_SOURCE_DIR}/include/infini/c_ops.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/infini ) diff --git a/src/infini/ops.cc b/src/infini/ops.cc index a21421add..319be045d 100644 --- a/src/infini/ops.cc +++ b/src/infini/ops.cc @@ -8,14 +8,29 @@ #include #include #include +#include #include +struct InfiniOpsTensorPrivate { + void* data = nullptr; + size_t byte_size = 0; + InfiniOpsDataType data_type = INFINI_OPS_DATA_TYPE_INVALID; + InfiniOpsDeviceType device_type = INFINI_OPS_DEVICE_TYPE_INVALID; + std::vector shape; + std::vector stride; + bool has_stride = false; +}; + struct InfiniOpsHandlePrivate { infini::ops::Handle handle; + InfiniOpsStream stream = nullptr; + void* workspace = nullptr; + size_t workspace_byte_size = 0; }; struct InfiniOpsConfigPrivate { infini::ops::Config config; + size_t implementation_index = 0; }; namespace { @@ -180,7 +195,7 @@ bool CheckedMultiply(size_t left, size_t right, size_t* result) { return true; } -bool ComputeRequiredByteSize(const InfiniOpsTensor& tensor, +bool ComputeRequiredByteSize(const InfiniOpsTensorPrivate& tensor, size_t* required_byte_size) { assert(required_byte_size != nullptr); @@ -189,15 +204,15 @@ bool ComputeRequiredByteSize(const InfiniOpsTensor& tensor, return false; } - if (tensor.rank == 0) { + if (tensor.shape.empty()) { *required_byte_size = element_size; return true; } size_t element_count = 1; - if (tensor.stride == nullptr) { - for (int32_t i = 0; i < tensor.rank; ++i) { - if (!CheckedMultiply(element_count, static_cast(tensor.shape[i]), + if (!tensor.has_stride) { + for (int64_t dimension : tensor.shape) { + if (!CheckedMultiply(element_count, static_cast(dimension), &element_count)) { return false; } @@ -206,7 +221,7 @@ bool ComputeRequiredByteSize(const InfiniOpsTensor& tensor, } size_t max_offset = 0; - for (int32_t i = 0; i < tensor.rank; ++i) { + for (size_t i = 0; i < tensor.shape.size(); ++i) { if (tensor.shape[i] == 0) { *required_byte_size = 0; return true; @@ -233,30 +248,29 @@ bool ComputeRequiredByteSize(const InfiniOpsTensor& tensor, } std::vector ConvertShape( - const InfiniOpsTensor& tensor) { + const InfiniOpsTensorPrivate& tensor) { std::vector result; - result.reserve(tensor.rank); - for (int32_t i = 0; i < tensor.rank; ++i) { - result.push_back(static_cast(tensor.shape[i])); + result.reserve(tensor.shape.size()); + for (int64_t dimension : tensor.shape) { + result.push_back(static_cast(dimension)); } return result; } std::vector ConvertStrides( - const InfiniOpsTensor& tensor) { + const InfiniOpsTensorPrivate& tensor) { std::vector result; - result.reserve(tensor.rank); - if (tensor.stride == nullptr) { + result.reserve(tensor.stride.size()); + if (!tensor.has_stride) { return result; } - for (int32_t i = 0; i < tensor.rank; ++i) { - result.push_back( - static_cast(tensor.stride[i])); + for (int64_t stride : tensor.stride) { + result.push_back(static_cast(stride)); } return result; } -infini::ops::Tensor ToInternalTensor(const InfiniOpsTensor& tensor) { +infini::ops::Tensor ToInternalTensor(const InfiniOpsTensorPrivate& tensor) { infini::ops::DataType data_type; const bool data_type_valid = ConvertDataType(tensor.data_type, &data_type); assert(data_type_valid); @@ -268,25 +282,19 @@ infini::ops::Tensor ToInternalTensor(const InfiniOpsTensor& tensor) { const auto shape = ConvertShape(tensor); const infini::ops::Device device(device_type); - if (tensor.stride == nullptr) { + if (!tensor.has_stride) { return infini::ops::Tensor(tensor.data, shape, data_type, device); } return infini::ops::Tensor(tensor.data, shape, data_type, device, ConvertStrides(tensor)); } -InfiniOpsStatus ValidateTensor(const char* name, - const InfiniOpsTensor* tensor) { +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; } - if (tensor->structure_size < offsetof(InfiniOpsTensor, reserved)) { - SetLastError(name); - last_error += " tensor structure size is invalid"; - return INFINI_OPS_STATUS_INVALID_ARGUMENT; - } if (!IsValidDataType(tensor->data_type)) { SetLastError(name); last_error += " tensor data type is invalid"; @@ -297,18 +305,8 @@ InfiniOpsStatus ValidateTensor(const char* name, last_error += " tensor device type is invalid"; return INFINI_OPS_STATUS_INVALID_ARGUMENT; } - if (tensor->rank < 0) { - SetLastError(name); - last_error += " tensor rank must not be negative"; - return INFINI_OPS_STATUS_INVALID_ARGUMENT; - } - if (tensor->rank > 0 && tensor->shape == nullptr) { - SetLastError(name); - last_error += " tensor shape must not be null for non-scalar"; - return INFINI_OPS_STATUS_INVALID_ARGUMENT; - } - for (int32_t i = 0; i < tensor->rank; ++i) { - if (tensor->shape[i] < 0) { + for (int64_t dimension : tensor->shape) { + if (dimension < 0) { SetLastError(name); last_error += " tensor shape must not contain negative values"; return INFINI_OPS_STATUS_INVALID_ARGUMENT; @@ -359,31 +357,260 @@ INFINI_OPS_API InfiniOpsStatus infiniOpsGetLastError(char* buffer, return INFINI_OPS_STATUS_SUCCESS; } -INFINI_OPS_API InfiniOpsStatus infiniOpsCreateHandle( - const InfiniOpsHandleAttributes* attributes, InfiniOpsHandle* handle) { +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateTensor(InfiniOpsTensor* tensor) { try { - if (handle == nullptr) { - return InvalidArgument("handle output must not be null"); + if (tensor == nullptr) { + return InvalidArgument("tensor output must not be null"); } - *handle = nullptr; - if (attributes == nullptr) { - return InvalidArgument("handle attributes must not be null"); + *tensor = nullptr; + *tensor = new InfiniOpsTensorPrivate; + 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; + } +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyTensor(InfiniOpsTensor tensor) { + delete tensor; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorData(InfiniOpsTensor tensor, + void* data) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + tensor->data = data; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorData(InfiniOpsTensor tensor, + void** data) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (data == nullptr) { + return InvalidArgument("tensor data output must not be null"); + } + *data = tensor->data; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus +infiniOpsSetTensorByteSize(InfiniOpsTensor tensor, size_t byte_size) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + tensor->byte_size = byte_size; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus +infiniOpsGetTensorByteSize(InfiniOpsTensor tensor, size_t* byte_size) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (byte_size == nullptr) { + return InvalidArgument("tensor byte size output must not be null"); + } + *byte_size = tensor->byte_size; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorDataType( + InfiniOpsTensor tensor, InfiniOpsDataType data_type) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (!IsValidDataType(data_type)) { + return InvalidArgument("tensor data type is invalid"); + } + tensor->data_type = data_type; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorDataType( + InfiniOpsTensor tensor, InfiniOpsDataType* data_type) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (data_type == nullptr) { + return InvalidArgument("tensor data type output must not be null"); + } + *data_type = tensor->data_type; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorDeviceType( + InfiniOpsTensor tensor, InfiniOpsDeviceType device_type) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (!IsValidDeviceType(device_type)) { + return InvalidArgument("tensor device type is invalid"); + } + tensor->device_type = device_type; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorDeviceType( + InfiniOpsTensor tensor, InfiniOpsDeviceType* device_type) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (device_type == nullptr) { + return InvalidArgument("tensor device type output must not be null"); + } + *device_type = tensor->device_type; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorShape(InfiniOpsTensor tensor, + int32_t rank, + const int64_t* shape) { + try { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (rank < 0) { + return InvalidArgument("tensor rank must not be negative"); + } + if (rank > 0 && shape == nullptr) { + return InvalidArgument("tensor shape must not be null for non-scalar"); + } + std::vector new_shape; + new_shape.reserve(static_cast(rank)); + for (int32_t i = 0; i < rank; ++i) { + if (shape[i] < 0) { + return InvalidArgument("tensor shape must not contain negative values"); + } + new_shape.push_back(shape[i]); + } + tensor->shape = std::move(new_shape); + tensor->stride.clear(); + tensor->has_stride = false; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc&) { + SetLastError("out of memory while setting tensor shape"); + 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 setting tensor shape"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetTensorShape(InfiniOpsTensor tensor, + int32_t* rank, + const int64_t** shape) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (rank == nullptr) { + return InvalidArgument("tensor rank output must not be null"); + } + if (shape == nullptr) { + return InvalidArgument("tensor shape output must not be null"); + } + *rank = static_cast(tensor->shape.size()); + *shape = tensor->shape.empty() ? nullptr : tensor->shape.data(); + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetTensorStride(InfiniOpsTensor tensor, + const int64_t* stride) { + try { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); } - if (attributes->structure_size < - offsetof(InfiniOpsHandleAttributes, reserved)) { - return InvalidArgument("handle attributes size is invalid"); + if (tensor->shape.empty()) { + tensor->stride.clear(); + tensor->has_stride = false; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; } - if (attributes->workspace_byte_size > 0 && - attributes->workspace == nullptr) { - return InvalidArgument("handle workspace must not be null"); + if (stride == nullptr) { + return InvalidArgument("tensor stride must not be null for non-scalar"); + } + std::vector new_stride; + new_stride.reserve(tensor->shape.size()); + for (size_t i = 0; i < tensor->shape.size(); ++i) { + if (stride[i] < 0) { + return InvalidArgument( + "tensor stride must not contain negative values"); + } + new_stride.push_back(stride[i]); } + tensor->stride = std::move(new_stride); + tensor->has_stride = true; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; + } catch (const std::bad_alloc&) { + SetLastError("out of memory while setting tensor stride"); + 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 setting tensor stride"); + return INFINI_OPS_STATUS_INTERNAL_ERROR; + } +} + +INFINI_OPS_API InfiniOpsStatus +infiniOpsClearTensorStride(InfiniOpsTensor tensor) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + tensor->stride.clear(); + tensor->has_stride = false; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus +infiniOpsGetTensorStride(InfiniOpsTensor tensor, const int64_t** stride) { + if (tensor == nullptr) { + return InvalidArgument("tensor must not be null"); + } + if (stride == nullptr) { + return InvalidArgument("tensor stride output must not be null"); + } + *stride = tensor->has_stride && !tensor->stride.empty() + ? tensor->stride.data() + : nullptr; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} - 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; +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateHandle(InfiniOpsHandle* handle) { + try { + if (handle == nullptr) { + return InvalidArgument("handle output must not be null"); + } + *handle = nullptr; + *handle = new InfiniOpsHandlePrivate; SetLastError(""); return INFINI_OPS_STATUS_SUCCESS; } catch (const std::bad_alloc&) { @@ -404,24 +631,71 @@ INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyHandle(InfiniOpsHandle handle) { return INFINI_OPS_STATUS_SUCCESS; } -INFINI_OPS_API InfiniOpsStatus infiniOpsCreateConfig( - const InfiniOpsConfigAttributes* attributes, InfiniOpsConfig* config) { +INFINI_OPS_API InfiniOpsStatus +infiniOpsSetHandleStream(InfiniOpsHandle handle, InfiniOpsStream stream) { + if (handle == nullptr) { + return InvalidArgument("handle must not be null"); + } + handle->stream = stream; + handle->handle.set_stream(reinterpret_cast(stream)); + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus +infiniOpsGetHandleStream(InfiniOpsHandle handle, InfiniOpsStream* stream) { + if (handle == nullptr) { + return InvalidArgument("handle must not be null"); + } + if (stream == nullptr) { + return InvalidArgument("handle stream output must not be null"); + } + *stream = handle->stream; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsSetHandleWorkspace( + InfiniOpsHandle handle, void* workspace, size_t byte_size) { + if (handle == nullptr) { + return InvalidArgument("handle must not be null"); + } + if (byte_size > 0 && workspace == nullptr) { + return InvalidArgument("handle workspace must not be null"); + } + handle->workspace = workspace; + handle->workspace_byte_size = byte_size; + handle->handle.set_workspace(workspace); + handle->handle.set_workspace_size_in_bytes(byte_size); + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetHandleWorkspace( + InfiniOpsHandle handle, void** workspace, size_t* byte_size) { + if (handle == nullptr) { + return InvalidArgument("handle must not be null"); + } + if (workspace == nullptr) { + return InvalidArgument("handle workspace output must not be null"); + } + if (byte_size == nullptr) { + return InvalidArgument( + "handle workspace byte size output must not be null"); + } + *workspace = handle->workspace; + *byte_size = handle->workspace_byte_size; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsCreateConfig(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; + *config = new InfiniOpsConfigPrivate; SetLastError(""); return INFINI_OPS_STATUS_SUCCESS; } catch (const std::bad_alloc&) { @@ -442,6 +716,31 @@ INFINI_OPS_API InfiniOpsStatus infiniOpsDestroyConfig(InfiniOpsConfig config) { return INFINI_OPS_STATUS_SUCCESS; } +INFINI_OPS_API InfiniOpsStatus infiniOpsSetConfigImplementationIndex( + InfiniOpsConfig config, size_t implementation_index) { + if (config == nullptr) { + return InvalidArgument("config must not be null"); + } + config->implementation_index = implementation_index; + config->config.set_implementation_index(implementation_index); + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + +INFINI_OPS_API InfiniOpsStatus infiniOpsGetConfigImplementationIndex( + InfiniOpsConfig config, size_t* implementation_index) { + if (config == nullptr) { + return InvalidArgument("config must not be null"); + } + if (implementation_index == nullptr) { + return InvalidArgument( + "config implementation index output must not be null"); + } + *implementation_index = config->implementation_index; + SetLastError(""); + return INFINI_OPS_STATUS_SUCCESS; +} + #include "c_ops.inc" } // extern "C" diff --git a/tests/test_c_api.py b/tests/test_c_api.py index b75e2bf3d..9c95733c1 100644 --- a/tests/test_c_api.py +++ b/tests/test_c_api.py @@ -189,6 +189,33 @@ def _run(command): #include #include + static int create_tensor(float* data, size_t byte_size, const int64_t* shape, + InfiniOpsTensor* tensor) { + if (infiniOpsCreateTensor(tensor) != INFINI_OPS_STATUS_SUCCESS) { + return 1; + } + if (infiniOpsSetTensorData(*tensor, data) != INFINI_OPS_STATUS_SUCCESS) { + return 2; + } + if (infiniOpsSetTensorByteSize(*tensor, byte_size) != + INFINI_OPS_STATUS_SUCCESS) { + return 3; + } + if (infiniOpsSetTensorDataType(*tensor, INFINI_OPS_DATA_TYPE_FLOAT32) != + INFINI_OPS_STATUS_SUCCESS) { + return 4; + } + if (infiniOpsSetTensorDeviceType(*tensor, INFINI_OPS_DEVICE_TYPE_CPU) != + INFINI_OPS_STATUS_SUCCESS) { + return 5; + } + if (infiniOpsSetTensorShape(*tensor, 1, shape) != + INFINI_OPS_STATUS_SUCCESS) { + return 6; + } + return 0; + } + int main(void) { int64_t shape[1] = {3}; @@ -196,54 +223,67 @@ def _run(command): float other_data[3] = {4.0f, 5.0f, 6.0f}; float output_data[3] = {0.0f, 0.0f, 0.0f}; - InfiniOpsTensor input = {0}; - input.structure_size = sizeof(input); - input.data = input_data; - input.byte_size = sizeof(input_data); - input.data_type = INFINI_OPS_DATA_TYPE_FLOAT32; - input.device_type = INFINI_OPS_DEVICE_TYPE_CPU; - input.rank = 1; - input.shape = shape; - - InfiniOpsTensor other = input; - other.data = other_data; - other.byte_size = sizeof(other_data); - - InfiniOpsTensor output = input; - output.data = output_data; - output.byte_size = sizeof(output_data); - - InfiniOpsHandleAttributes handle_attributes = {0}; - handle_attributes.structure_size = sizeof(handle_attributes); - InfiniOpsHandle handle = NULL; - if (infiniOpsCreateHandle(&handle_attributes, &handle) != - INFINI_OPS_STATUS_SUCCESS) { + InfiniOpsTensor input = NULL; + InfiniOpsTensor other = NULL; + InfiniOpsTensor output = NULL; + if (create_tensor(input_data, sizeof(input_data), shape, &input) != 0) { return 1; } + if (create_tensor(other_data, sizeof(other_data), shape, &other) != 0) { + return 2; + } + if (create_tensor(output_data, sizeof(output_data), shape, &output) != 0) { + return 3; + } + + int32_t rank = 0; + const int64_t* stored_shape = NULL; + if (infiniOpsGetTensorShape(input, &rank, &stored_shape) != + INFINI_OPS_STATUS_SUCCESS) { + return 4; + } + if (rank != 1 || stored_shape == NULL || stored_shape[0] != 3) { + return 5; + } + + InfiniOpsHandle handle = NULL; + if (infiniOpsCreateHandle(&handle) != INFINI_OPS_STATUS_SUCCESS) { + return 6; + } - InfiniOpsConfigAttributes config_attributes = {0}; - config_attributes.structure_size = sizeof(config_attributes); InfiniOpsConfig config = NULL; - if (infiniOpsCreateConfig(&config_attributes, &config) != + if (infiniOpsCreateConfig(&config) != INFINI_OPS_STATUS_SUCCESS) { + return 7; + } + if (infiniOpsSetConfigImplementationIndex(config, 0) != INFINI_OPS_STATUS_SUCCESS) { - return 2; + return 8; } - if (infiniOpsAdd(handle, config, &input, &other, &output) != + if (infiniOpsAdd(handle, config, input, other, output) != INFINI_OPS_STATUS_SUCCESS) { - return 3; + return 9; } if (output_data[0] != 5.0f || output_data[1] != 7.0f || output_data[2] != 9.0f) { - return 4; + return 10; } if (infiniOpsDestroyConfig(config) != INFINI_OPS_STATUS_SUCCESS) { - return 5; + return 11; } if (infiniOpsDestroyHandle(handle) != INFINI_OPS_STATUS_SUCCESS) { - return 6; + return 12; + } + if (infiniOpsDestroyTensor(output) != INFINI_OPS_STATUS_SUCCESS) { + return 13; + } + if (infiniOpsDestroyTensor(other) != INFINI_OPS_STATUS_SUCCESS) { + return 14; + } + if (infiniOpsDestroyTensor(input) != INFINI_OPS_STATUS_SUCCESS) { + return 15; } return 0; }