Expands SDK support to 8 additional languages/frameworks: - Java SDK with Maven/OkHttp/Jackson - Kotlin SDK with Gradle/Ktor/kotlinx.serialization - Swift SDK with Swift Package Manager/async-await - C SDK with CMake/libcurl - C++ SDK with CMake/Modern C++20 - C# SDK with .NET 8.0/HttpClient - Ruby SDK with Bundler/Faraday - Rust SDK with Cargo/reqwest/tokio All SDKs include: - Tensor operations (matmul, conv2d, attention) - LLM inference with streaming support - Model registry, pricing, and usage APIs - Builder patterns where idiomatic - Full type safety
537 lines
15 KiB
C
537 lines
15 KiB
C
/**
|
|
* Synor Compute SDK - C Implementation
|
|
*/
|
|
|
|
#include "../include/synor_compute.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <curl/curl.h>
|
|
|
|
/* ============ Internal Structures ============ */
|
|
|
|
struct synor_client {
|
|
char* api_key;
|
|
char* base_url;
|
|
synor_processor_t default_processor;
|
|
synor_precision_t default_precision;
|
|
synor_priority_t default_priority;
|
|
int timeout_ms;
|
|
bool debug;
|
|
bool closed;
|
|
CURL* curl;
|
|
};
|
|
|
|
typedef struct {
|
|
char* data;
|
|
size_t size;
|
|
} response_buffer_t;
|
|
|
|
/* ============ Internal Functions ============ */
|
|
|
|
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
|
|
size_t realsize = size * nmemb;
|
|
response_buffer_t* buf = (response_buffer_t*)userp;
|
|
|
|
char* ptr = realloc(buf->data, buf->size + realsize + 1);
|
|
if (!ptr) return 0;
|
|
|
|
buf->data = ptr;
|
|
memcpy(&(buf->data[buf->size]), contents, realsize);
|
|
buf->size += realsize;
|
|
buf->data[buf->size] = 0;
|
|
|
|
return realsize;
|
|
}
|
|
|
|
static char* synor_strdup(const char* str) {
|
|
if (!str) return NULL;
|
|
size_t len = strlen(str) + 1;
|
|
char* copy = malloc(len);
|
|
if (copy) memcpy(copy, str, len);
|
|
return copy;
|
|
}
|
|
|
|
static size_t compute_tensor_size(const int* shape, int ndim) {
|
|
size_t size = 1;
|
|
for (int i = 0; i < ndim; i++) {
|
|
size *= shape[i];
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/* ============ Client Functions ============ */
|
|
|
|
void synor_config_default(synor_config_t* config) {
|
|
config->api_key = NULL;
|
|
config->base_url = "https://api.synor.io/compute/v1";
|
|
config->default_processor = SYNOR_PROCESSOR_AUTO;
|
|
config->default_precision = SYNOR_PRECISION_FP32;
|
|
config->default_priority = SYNOR_PRIORITY_NORMAL;
|
|
config->timeout_ms = 30000;
|
|
config->debug = false;
|
|
}
|
|
|
|
synor_client_t* synor_create(const char* api_key) {
|
|
synor_config_t config;
|
|
synor_config_default(&config);
|
|
config.api_key = api_key;
|
|
return synor_create_with_config(&config);
|
|
}
|
|
|
|
synor_client_t* synor_create_with_config(const synor_config_t* config) {
|
|
if (!config || !config->api_key) return NULL;
|
|
|
|
synor_client_t* client = calloc(1, sizeof(synor_client_t));
|
|
if (!client) return NULL;
|
|
|
|
client->api_key = synor_strdup(config->api_key);
|
|
client->base_url = synor_strdup(config->base_url ? config->base_url : "https://api.synor.io/compute/v1");
|
|
client->default_processor = config->default_processor;
|
|
client->default_precision = config->default_precision;
|
|
client->default_priority = config->default_priority;
|
|
client->timeout_ms = config->timeout_ms;
|
|
client->debug = config->debug;
|
|
client->closed = false;
|
|
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
client->curl = curl_easy_init();
|
|
|
|
return client;
|
|
}
|
|
|
|
void synor_destroy(synor_client_t* client) {
|
|
if (!client) return;
|
|
|
|
client->closed = true;
|
|
|
|
if (client->curl) {
|
|
curl_easy_cleanup(client->curl);
|
|
}
|
|
curl_global_cleanup();
|
|
|
|
free(client->api_key);
|
|
free(client->base_url);
|
|
free(client);
|
|
}
|
|
|
|
/* ============ Tensor Functions ============ */
|
|
|
|
synor_tensor_t* synor_tensor_create(
|
|
const int* shape,
|
|
int ndim,
|
|
const double* data,
|
|
synor_precision_t dtype
|
|
) {
|
|
synor_tensor_t* tensor = calloc(1, sizeof(synor_tensor_t));
|
|
if (!tensor) return NULL;
|
|
|
|
tensor->ndim = ndim;
|
|
tensor->dtype = dtype;
|
|
tensor->size = compute_tensor_size(shape, ndim);
|
|
|
|
tensor->shape = malloc(ndim * sizeof(int));
|
|
if (!tensor->shape) {
|
|
free(tensor);
|
|
return NULL;
|
|
}
|
|
memcpy(tensor->shape, shape, ndim * sizeof(int));
|
|
|
|
tensor->data = malloc(tensor->size * sizeof(double));
|
|
if (!tensor->data) {
|
|
free(tensor->shape);
|
|
free(tensor);
|
|
return NULL;
|
|
}
|
|
|
|
if (data) {
|
|
memcpy(tensor->data, data, tensor->size * sizeof(double));
|
|
}
|
|
|
|
return tensor;
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_zeros(const int* shape, int ndim) {
|
|
synor_tensor_t* tensor = synor_tensor_create(shape, ndim, NULL, SYNOR_PRECISION_FP32);
|
|
if (tensor) {
|
|
memset(tensor->data, 0, tensor->size * sizeof(double));
|
|
}
|
|
return tensor;
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_ones(const int* shape, int ndim) {
|
|
synor_tensor_t* tensor = synor_tensor_create(shape, ndim, NULL, SYNOR_PRECISION_FP32);
|
|
if (tensor) {
|
|
for (size_t i = 0; i < tensor->size; i++) {
|
|
tensor->data[i] = 1.0;
|
|
}
|
|
}
|
|
return tensor;
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_rand(const int* shape, int ndim) {
|
|
synor_tensor_t* tensor = synor_tensor_create(shape, ndim, NULL, SYNOR_PRECISION_FP32);
|
|
if (tensor) {
|
|
srand((unsigned int)time(NULL));
|
|
for (size_t i = 0; i < tensor->size; i++) {
|
|
tensor->data[i] = (double)rand() / RAND_MAX;
|
|
}
|
|
}
|
|
return tensor;
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_randn(const int* shape, int ndim) {
|
|
synor_tensor_t* tensor = synor_tensor_create(shape, ndim, NULL, SYNOR_PRECISION_FP32);
|
|
if (tensor) {
|
|
srand((unsigned int)time(NULL));
|
|
for (size_t i = 0; i < tensor->size; i++) {
|
|
// Box-Muller transform
|
|
double u1 = (double)rand() / RAND_MAX;
|
|
double u2 = (double)rand() / RAND_MAX;
|
|
tensor->data[i] = sqrt(-2 * log(u1)) * cos(2 * M_PI * u2);
|
|
}
|
|
}
|
|
return tensor;
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_eye(int n) {
|
|
int shape[] = {n, n};
|
|
synor_tensor_t* tensor = synor_tensor_zeros(shape, 2);
|
|
if (tensor) {
|
|
for (int i = 0; i < n; i++) {
|
|
tensor->data[i * n + i] = 1.0;
|
|
}
|
|
}
|
|
return tensor;
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_reshape(const synor_tensor_t* tensor, const int* new_shape, int new_ndim) {
|
|
if (!tensor) return NULL;
|
|
|
|
size_t new_size = compute_tensor_size(new_shape, new_ndim);
|
|
if (new_size != tensor->size) return NULL;
|
|
|
|
return synor_tensor_create(new_shape, new_ndim, tensor->data, tensor->dtype);
|
|
}
|
|
|
|
synor_tensor_t* synor_tensor_transpose(const synor_tensor_t* tensor) {
|
|
if (!tensor || tensor->ndim != 2) return NULL;
|
|
|
|
int rows = tensor->shape[0];
|
|
int cols = tensor->shape[1];
|
|
int new_shape[] = {cols, rows};
|
|
|
|
synor_tensor_t* result = synor_tensor_create(new_shape, 2, NULL, tensor->dtype);
|
|
if (!result) return NULL;
|
|
|
|
for (int i = 0; i < rows; i++) {
|
|
for (int j = 0; j < cols; j++) {
|
|
result->data[j * rows + i] = tensor->data[i * cols + j];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
double synor_tensor_mean(const synor_tensor_t* tensor) {
|
|
if (!tensor || tensor->size == 0) return 0.0;
|
|
double sum = 0.0;
|
|
for (size_t i = 0; i < tensor->size; i++) {
|
|
sum += tensor->data[i];
|
|
}
|
|
return sum / tensor->size;
|
|
}
|
|
|
|
double synor_tensor_sum(const synor_tensor_t* tensor) {
|
|
if (!tensor) return 0.0;
|
|
double sum = 0.0;
|
|
for (size_t i = 0; i < tensor->size; i++) {
|
|
sum += tensor->data[i];
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
double synor_tensor_std(const synor_tensor_t* tensor) {
|
|
if (!tensor || tensor->size == 0) return 0.0;
|
|
double mean = synor_tensor_mean(tensor);
|
|
double sum_sq = 0.0;
|
|
for (size_t i = 0; i < tensor->size; i++) {
|
|
double diff = tensor->data[i] - mean;
|
|
sum_sq += diff * diff;
|
|
}
|
|
return sqrt(sum_sq / tensor->size);
|
|
}
|
|
|
|
double synor_tensor_max(const synor_tensor_t* tensor) {
|
|
if (!tensor || tensor->size == 0) return 0.0;
|
|
double max = tensor->data[0];
|
|
for (size_t i = 1; i < tensor->size; i++) {
|
|
if (tensor->data[i] > max) max = tensor->data[i];
|
|
}
|
|
return max;
|
|
}
|
|
|
|
double synor_tensor_min(const synor_tensor_t* tensor) {
|
|
if (!tensor || tensor->size == 0) return 0.0;
|
|
double min = tensor->data[0];
|
|
for (size_t i = 1; i < tensor->size; i++) {
|
|
if (tensor->data[i] < min) min = tensor->data[i];
|
|
}
|
|
return min;
|
|
}
|
|
|
|
void synor_tensor_free(synor_tensor_t* tensor) {
|
|
if (!tensor) return;
|
|
free(tensor->shape);
|
|
free(tensor->data);
|
|
free(tensor);
|
|
}
|
|
|
|
/* ============ Memory Management ============ */
|
|
|
|
void synor_job_result_free(synor_job_result_t* result) {
|
|
if (!result) return;
|
|
free(result->job_id);
|
|
free(result->result_string);
|
|
free(result->error);
|
|
synor_tensor_free(result->result_tensor);
|
|
free(result);
|
|
}
|
|
|
|
void synor_model_info_free(synor_model_info_t* info) {
|
|
if (!info) return;
|
|
free(info->id);
|
|
free(info->name);
|
|
free(info->description);
|
|
free(info->format);
|
|
free(info->license);
|
|
free(info->cid);
|
|
free(info);
|
|
}
|
|
|
|
void synor_model_info_array_free(synor_model_info_t* models, size_t count) {
|
|
if (!models) return;
|
|
for (size_t i = 0; i < count; i++) {
|
|
free(models[i].id);
|
|
free(models[i].name);
|
|
free(models[i].description);
|
|
free(models[i].format);
|
|
free(models[i].license);
|
|
free(models[i].cid);
|
|
}
|
|
free(models);
|
|
}
|
|
|
|
void synor_pricing_info_array_free(synor_pricing_info_t* pricing, size_t count) {
|
|
free(pricing);
|
|
}
|
|
|
|
void synor_usage_stats_free(synor_usage_stats_t* stats) {
|
|
free(stats);
|
|
}
|
|
|
|
/* ============ Utility Functions ============ */
|
|
|
|
const char* synor_error_string(synor_error_t error) {
|
|
switch (error) {
|
|
case SYNOR_OK: return "OK";
|
|
case SYNOR_ERROR_INVALID_ARGUMENT: return "Invalid argument";
|
|
case SYNOR_ERROR_OUT_OF_MEMORY: return "Out of memory";
|
|
case SYNOR_ERROR_NETWORK: return "Network error";
|
|
case SYNOR_ERROR_API: return "API error";
|
|
case SYNOR_ERROR_TIMEOUT: return "Timeout";
|
|
case SYNOR_ERROR_CLIENT_CLOSED: return "Client closed";
|
|
default: return "Unknown error";
|
|
}
|
|
}
|
|
|
|
const char* synor_processor_string(synor_processor_t processor) {
|
|
switch (processor) {
|
|
case SYNOR_PROCESSOR_CPU: return "cpu";
|
|
case SYNOR_PROCESSOR_GPU: return "gpu";
|
|
case SYNOR_PROCESSOR_TPU: return "tpu";
|
|
case SYNOR_PROCESSOR_NPU: return "npu";
|
|
case SYNOR_PROCESSOR_LPU: return "lpu";
|
|
case SYNOR_PROCESSOR_FPGA: return "fpga";
|
|
case SYNOR_PROCESSOR_DSP: return "dsp";
|
|
case SYNOR_PROCESSOR_WEBGPU: return "webgpu";
|
|
case SYNOR_PROCESSOR_WASM: return "wasm";
|
|
case SYNOR_PROCESSOR_AUTO: return "auto";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
const char* synor_precision_string(synor_precision_t precision) {
|
|
switch (precision) {
|
|
case SYNOR_PRECISION_FP64: return "fp64";
|
|
case SYNOR_PRECISION_FP32: return "fp32";
|
|
case SYNOR_PRECISION_FP16: return "fp16";
|
|
case SYNOR_PRECISION_BF16: return "bf16";
|
|
case SYNOR_PRECISION_INT8: return "int8";
|
|
case SYNOR_PRECISION_INT4: return "int4";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
const char* synor_status_string(synor_status_t status) {
|
|
switch (status) {
|
|
case SYNOR_STATUS_PENDING: return "pending";
|
|
case SYNOR_STATUS_QUEUED: return "queued";
|
|
case SYNOR_STATUS_RUNNING: return "running";
|
|
case SYNOR_STATUS_COMPLETED: return "completed";
|
|
case SYNOR_STATUS_FAILED: return "failed";
|
|
case SYNOR_STATUS_CANCELLED: return "cancelled";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
/* ============ API Functions (Stubs) ============ */
|
|
|
|
synor_job_result_t* synor_matmul(
|
|
synor_client_t* client,
|
|
const synor_tensor_t* a,
|
|
const synor_tensor_t* b,
|
|
const synor_matmul_options_t* options
|
|
) {
|
|
if (!client || client->closed || !a || !b) return NULL;
|
|
|
|
synor_job_result_t* result = calloc(1, sizeof(synor_job_result_t));
|
|
if (!result) return NULL;
|
|
|
|
// TODO: Implement HTTP call to API
|
|
result->status = SYNOR_STATUS_COMPLETED;
|
|
result->job_id = synor_strdup("job-placeholder");
|
|
|
|
return result;
|
|
}
|
|
|
|
synor_job_result_t* synor_conv2d(
|
|
synor_client_t* client,
|
|
const synor_tensor_t* input,
|
|
const synor_tensor_t* kernel,
|
|
const synor_conv2d_options_t* options
|
|
) {
|
|
if (!client || client->closed || !input || !kernel) return NULL;
|
|
|
|
synor_job_result_t* result = calloc(1, sizeof(synor_job_result_t));
|
|
if (!result) return NULL;
|
|
|
|
result->status = SYNOR_STATUS_COMPLETED;
|
|
result->job_id = synor_strdup("job-placeholder");
|
|
|
|
return result;
|
|
}
|
|
|
|
synor_job_result_t* synor_attention(
|
|
synor_client_t* client,
|
|
const synor_tensor_t* query,
|
|
const synor_tensor_t* key,
|
|
const synor_tensor_t* value,
|
|
const synor_attention_options_t* options
|
|
) {
|
|
if (!client || client->closed || !query || !key || !value) return NULL;
|
|
|
|
synor_job_result_t* result = calloc(1, sizeof(synor_job_result_t));
|
|
if (!result) return NULL;
|
|
|
|
result->status = SYNOR_STATUS_COMPLETED;
|
|
result->job_id = synor_strdup("job-placeholder");
|
|
|
|
return result;
|
|
}
|
|
|
|
synor_job_result_t* synor_inference(
|
|
synor_client_t* client,
|
|
const char* model,
|
|
const char* prompt,
|
|
const synor_inference_options_t* options
|
|
) {
|
|
if (!client || client->closed || !model || !prompt) return NULL;
|
|
|
|
synor_job_result_t* result = calloc(1, sizeof(synor_job_result_t));
|
|
if (!result) return NULL;
|
|
|
|
result->status = SYNOR_STATUS_COMPLETED;
|
|
result->job_id = synor_strdup("job-placeholder");
|
|
|
|
return result;
|
|
}
|
|
|
|
synor_error_t synor_inference_stream(
|
|
synor_client_t* client,
|
|
const char* model,
|
|
const char* prompt,
|
|
const synor_inference_options_t* options,
|
|
synor_stream_callback_t callback,
|
|
void* user_data
|
|
) {
|
|
if (!client || client->closed) return SYNOR_ERROR_CLIENT_CLOSED;
|
|
if (!model || !prompt || !callback) return SYNOR_ERROR_INVALID_ARGUMENT;
|
|
|
|
// TODO: Implement streaming HTTP call
|
|
return SYNOR_OK;
|
|
}
|
|
|
|
bool synor_health_check(synor_client_t* client) {
|
|
if (!client || client->closed) return false;
|
|
// TODO: Implement health check
|
|
return true;
|
|
}
|
|
|
|
synor_error_t synor_list_models(
|
|
synor_client_t* client,
|
|
synor_model_category_t category,
|
|
synor_model_info_t** models,
|
|
size_t* count
|
|
) {
|
|
if (!client || client->closed) return SYNOR_ERROR_CLIENT_CLOSED;
|
|
if (!models || !count) return SYNOR_ERROR_INVALID_ARGUMENT;
|
|
|
|
*models = NULL;
|
|
*count = 0;
|
|
// TODO: Implement model listing
|
|
return SYNOR_OK;
|
|
}
|
|
|
|
synor_model_info_t* synor_get_model(synor_client_t* client, const char* model_id) {
|
|
if (!client || client->closed || !model_id) return NULL;
|
|
// TODO: Implement model fetching
|
|
return NULL;
|
|
}
|
|
|
|
synor_error_t synor_search_models(
|
|
synor_client_t* client,
|
|
const char* query,
|
|
synor_model_info_t** models,
|
|
size_t* count
|
|
) {
|
|
if (!client || client->closed) return SYNOR_ERROR_CLIENT_CLOSED;
|
|
if (!query || !models || !count) return SYNOR_ERROR_INVALID_ARGUMENT;
|
|
|
|
*models = NULL;
|
|
*count = 0;
|
|
// TODO: Implement model search
|
|
return SYNOR_OK;
|
|
}
|
|
|
|
synor_error_t synor_get_pricing(
|
|
synor_client_t* client,
|
|
synor_pricing_info_t** pricing,
|
|
size_t* count
|
|
) {
|
|
if (!client || client->closed) return SYNOR_ERROR_CLIENT_CLOSED;
|
|
if (!pricing || !count) return SYNOR_ERROR_INVALID_ARGUMENT;
|
|
|
|
*pricing = NULL;
|
|
*count = 0;
|
|
// TODO: Implement pricing fetch
|
|
return SYNOR_OK;
|
|
}
|
|
|
|
synor_usage_stats_t* synor_get_usage(synor_client_t* client) {
|
|
if (!client || client->closed) return NULL;
|
|
// TODO: Implement usage stats fetch
|
|
return NULL;
|
|
}
|