Current section
Files
Jump to
Current section
Files
c_src/faiss_ex_nif.c
#include <erl_nif.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "c_api/Index_c.h"
#include "c_api/index_factory_c.h"
#include "c_api/index_io_c.h"
#include "c_api/clone_index_c.h"
#include "c_api/Clustering_c.h"
#include "c_api/error_c.h"
#include "c_api/faiss_c.h"
#ifdef FAISS_GPU_ENABLED
#include "c_api/gpu/StandardGpuResources_c.h"
#include "c_api/gpu/GpuAutoTune_c.h"
#endif
/* ========== Resource Types ========== */
static ErlNifResourceType *INDEX_RESOURCE_TYPE;
static ErlNifResourceType *CLUSTERING_RESOURCE_TYPE;
#ifdef FAISS_GPU_ENABLED
static ErlNifResourceType *GPU_RESOURCES_RESOURCE_TYPE;
#endif
typedef struct {
FaissIndex *index;
} IndexResource;
typedef struct {
FaissClustering *clustering;
} ClusteringResource;
#ifdef FAISS_GPU_ENABLED
typedef struct {
FaissStandardGpuResources *resources;
} GpuResourcesResource;
#endif
/* ========== Destructors ========== */
static void index_resource_destructor(ErlNifEnv *env, void *obj) {
(void)env;
IndexResource *res = (IndexResource *)obj;
if (res->index) {
faiss_Index_free(res->index);
res->index = NULL;
}
}
static void clustering_resource_destructor(ErlNifEnv *env, void *obj) {
(void)env;
ClusteringResource *res = (ClusteringResource *)obj;
if (res->clustering) {
faiss_Clustering_free(res->clustering);
res->clustering = NULL;
}
}
#ifdef FAISS_GPU_ENABLED
static void gpu_resources_destructor(ErlNifEnv *env, void *obj) {
(void)env;
GpuResourcesResource *res = (GpuResourcesResource *)obj;
if (res->resources) {
faiss_StandardGpuResources_free(res->resources);
res->resources = NULL;
}
}
#endif
/* ========== Helpers ========== */
static ERL_NIF_TERM make_atom(ErlNifEnv *env, const char *name) {
ERL_NIF_TERM atom;
if (enif_make_existing_atom(env, name, &atom, ERL_NIF_LATIN1)) {
return atom;
}
return enif_make_atom(env, name);
}
static ERL_NIF_TERM make_ok(ErlNifEnv *env, ERL_NIF_TERM term) {
return enif_make_tuple2(env, make_atom(env, "ok"), term);
}
static ERL_NIF_TERM make_ok_atom(ErlNifEnv *env) {
return make_atom(env, "ok");
}
static ERL_NIF_TERM make_error_msg(ErlNifEnv *env, const char *msg) {
ERL_NIF_TERM bin;
size_t len = strlen(msg);
unsigned char *buf = enif_make_new_binary(env, len, &bin);
memcpy(buf, msg, len);
return enif_make_tuple2(env, make_atom(env, "error"), bin);
}
static ERL_NIF_TERM make_faiss_error(ErlNifEnv *env, const char *fallback) {
const char *err = faiss_get_last_error();
if (err && strlen(err) > 0) {
return make_error_msg(env, err);
}
return make_error_msg(env, fallback);
}
/* Overflow-safe multiplication: returns 0 on overflow, 1 on success */
static int safe_mul(size_t a, size_t b, size_t *result) {
if (a != 0 && b > SIZE_MAX / a) return 0;
*result = a * b;
return 1;
}
/* ========== NIF: Index ========== */
/* nif_new_index(dim, description_binary, metric_int) */
static ERL_NIF_TERM nif_new_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
int dim;
ErlNifBinary desc_bin;
int metric;
if (!enif_get_int(env, argv[0], &dim) ||
!enif_inspect_binary(env, argv[1], &desc_bin) ||
!enif_get_int(env, argv[2], &metric)) {
return make_error_msg(env, "invalid arguments");
}
/* Null-terminate the description string */
char *desc = (char *)enif_alloc(desc_bin.size + 1);
if (!desc) return make_error_msg(env, "out of memory");
memcpy(desc, desc_bin.data, desc_bin.size);
desc[desc_bin.size] = '\0';
FaissIndex *index = NULL;
int ret = faiss_index_factory(&index, dim, desc, (FaissMetricType)metric);
enif_free(desc);
if (ret != 0) {
return make_faiss_error(env, "failed to create index");
}
IndexResource *res = enif_alloc_resource(INDEX_RESOURCE_TYPE, sizeof(IndexResource));
if (!res) {
faiss_Index_free(index);
return make_error_msg(env, "failed to allocate resource");
}
res->index = index;
ERL_NIF_TERM ref = enif_make_resource(env, res);
enif_release_resource(res);
return make_ok(env, ref);
}
/* nif_clone_index(ref) */
static ERL_NIF_TERM nif_clone_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid index reference");
}
FaissIndex *cloned = NULL;
int ret = faiss_clone_index(res->index, &cloned);
if (ret != 0) {
return make_faiss_error(env, "failed to clone index");
}
IndexResource *new_res = enif_alloc_resource(INDEX_RESOURCE_TYPE, sizeof(IndexResource));
if (!new_res) {
faiss_Index_free(cloned);
return make_error_msg(env, "failed to allocate resource");
}
new_res->index = cloned;
ERL_NIF_TERM ref = enif_make_resource(env, new_res);
enif_release_resource(new_res);
return make_ok(env, ref);
}
/* nif_add_to_index(ref, n, data_binary) */
static ERL_NIF_TERM nif_add_to_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifSInt64 n;
ErlNifBinary data_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &data_bin)) {
return make_error_msg(env, "invalid arguments");
}
if (n < 0) return make_error_msg(env, "n must be non-negative");
if (n == 0) return make_ok_atom(env);
int dim = faiss_Index_d(res->index);
size_t nd, expected;
if (!safe_mul((size_t)n, (size_t)dim, &nd) || !safe_mul(nd, sizeof(float), &expected)) {
return make_error_msg(env, "size overflow");
}
if (data_bin.size != expected) {
return make_error_msg(env, "data binary size mismatch");
}
int ret = faiss_Index_add(res->index, (idx_t)n, (const float *)data_bin.data);
if (ret != 0) {
return make_faiss_error(env, "failed to add vectors");
}
return make_ok_atom(env);
}
/* nif_add_with_ids_to_index(ref, n, data_binary, ids_binary) */
static ERL_NIF_TERM nif_add_with_ids_to_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifSInt64 n;
ErlNifBinary data_bin, ids_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &data_bin) ||
!enif_inspect_binary(env, argv[3], &ids_bin)) {
return make_error_msg(env, "invalid arguments");
}
if (n < 0) return make_error_msg(env, "n must be non-negative");
if (n == 0) return make_ok_atom(env);
int dim = faiss_Index_d(res->index);
size_t nd, expected_data, expected_ids;
if (!safe_mul((size_t)n, (size_t)dim, &nd) || !safe_mul(nd, sizeof(float), &expected_data)) {
return make_error_msg(env, "size overflow");
}
if (!safe_mul((size_t)n, sizeof(int64_t), &expected_ids)) {
return make_error_msg(env, "size overflow");
}
if (data_bin.size != expected_data || ids_bin.size != expected_ids) {
return make_error_msg(env, "binary size mismatch");
}
int ret = faiss_Index_add_with_ids(res->index, (idx_t)n,
(const float *)data_bin.data,
(const idx_t *)ids_bin.data);
if (ret != 0) {
return make_faiss_error(env, "failed to add vectors with ids");
}
return make_ok_atom(env);
}
/* nif_search_index(ref, n, data_binary, k) */
static ERL_NIF_TERM nif_search_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifSInt64 n, k;
ErlNifBinary data_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &data_bin) ||
!enif_get_int64(env, argv[3], &k)) {
return make_error_msg(env, "invalid arguments");
}
if (n < 0) return make_error_msg(env, "n must be non-negative");
if (k <= 0) return make_error_msg(env, "k must be positive");
if (n == 0) {
ErlNifBinary empty_d, empty_l;
if (!enif_alloc_binary(0, &empty_d))
return make_error_msg(env, "failed to allocate result binaries");
if (!enif_alloc_binary(0, &empty_l)) {
enif_release_binary(&empty_d);
return make_error_msg(env, "failed to allocate result binaries");
}
return make_ok(env, enif_make_tuple2(env,
enif_make_binary(env, &empty_d),
enif_make_binary(env, &empty_l)));
}
int dim = faiss_Index_d(res->index);
size_t nd, expected;
if (!safe_mul((size_t)n, (size_t)dim, &nd) || !safe_mul(nd, sizeof(float), &expected)) {
return make_error_msg(env, "size overflow");
}
if (data_bin.size != expected) {
return make_error_msg(env, "data binary size mismatch");
}
size_t nk, dist_size, label_size;
if (!safe_mul((size_t)n, (size_t)k, &nk) ||
!safe_mul(nk, sizeof(float), &dist_size) ||
!safe_mul(nk, sizeof(int64_t), &label_size)) {
return make_error_msg(env, "size overflow");
}
ErlNifBinary distances_bin, labels_bin;
if (!enif_alloc_binary(dist_size, &distances_bin)) {
return make_error_msg(env, "failed to allocate result binaries");
}
if (!enif_alloc_binary(label_size, &labels_bin)) {
enif_release_binary(&distances_bin);
return make_error_msg(env, "failed to allocate result binaries");
}
int ret = faiss_Index_search(res->index, (idx_t)n,
(const float *)data_bin.data, (idx_t)k,
(float *)distances_bin.data,
(idx_t *)labels_bin.data);
if (ret != 0) {
enif_release_binary(&distances_bin);
enif_release_binary(&labels_bin);
return make_faiss_error(env, "search failed");
}
ERL_NIF_TERM distances_term = enif_make_binary(env, &distances_bin);
ERL_NIF_TERM labels_term = enif_make_binary(env, &labels_bin);
return make_ok(env, enif_make_tuple2(env, distances_term, labels_term));
}
/* nif_train_index(ref, n, data_binary) */
static ERL_NIF_TERM nif_train_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifSInt64 n;
ErlNifBinary data_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &data_bin)) {
return make_error_msg(env, "invalid arguments");
}
if (n < 0) return make_error_msg(env, "n must be non-negative");
if (n == 0) return make_ok_atom(env);
int dim = faiss_Index_d(res->index);
size_t nd, expected;
if (!safe_mul((size_t)n, (size_t)dim, &nd) || !safe_mul(nd, sizeof(float), &expected)) {
return make_error_msg(env, "size overflow");
}
if (data_bin.size != expected) {
return make_error_msg(env, "data binary size mismatch");
}
int ret = faiss_Index_train(res->index, (idx_t)n, (const float *)data_bin.data);
if (ret != 0) {
return make_faiss_error(env, "training failed");
}
return make_ok_atom(env);
}
/* nif_reset_index(ref) */
static ERL_NIF_TERM nif_reset_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid index reference");
}
int ret = faiss_Index_reset(res->index);
if (ret != 0) {
return make_faiss_error(env, "reset failed");
}
return make_ok_atom(env);
}
/* nif_reconstruct_batch(ref, n, keys_binary) - loops over individual keys */
static ERL_NIF_TERM nif_reconstruct_batch(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifSInt64 n;
ErlNifBinary keys_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &keys_bin)) {
return make_error_msg(env, "invalid arguments");
}
if (n < 0) return make_error_msg(env, "n must be non-negative");
size_t expected_keys;
if (!safe_mul((size_t)n, sizeof(int64_t), &expected_keys)) {
return make_error_msg(env, "size overflow");
}
if (keys_bin.size != expected_keys) {
return make_error_msg(env, "keys binary size mismatch");
}
if (n == 0) {
ErlNifBinary empty;
if (!enif_alloc_binary(0, &empty)) {
return make_error_msg(env, "failed to allocate result binary");
}
return make_ok(env, enif_make_binary(env, &empty));
}
int dim = faiss_Index_d(res->index);
size_t nd, result_size;
if (!safe_mul((size_t)n, (size_t)dim, &nd) || !safe_mul(nd, sizeof(float), &result_size)) {
return make_error_msg(env, "size overflow");
}
ErlNifBinary result_bin;
if (!enif_alloc_binary(result_size, &result_bin)) {
return make_error_msg(env, "failed to allocate result binary");
}
const idx_t *keys = (const idx_t *)keys_bin.data;
float *result = (float *)result_bin.data;
for (ErlNifSInt64 i = 0; i < n; i++) {
int ret = faiss_Index_reconstruct(res->index, keys[i], result + i * dim);
if (ret != 0) {
enif_release_binary(&result_bin);
return make_faiss_error(env, "reconstruct failed");
}
}
return make_ok(env, enif_make_binary(env, &result_bin));
}
/* nif_compute_residuals(ref, n, data_binary, keys_binary) */
static ERL_NIF_TERM nif_compute_residuals(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifSInt64 n;
ErlNifBinary data_bin, keys_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &data_bin) ||
!enif_inspect_binary(env, argv[3], &keys_bin)) {
return make_error_msg(env, "invalid arguments");
}
if (n < 0) return make_error_msg(env, "n must be non-negative");
int dim = faiss_Index_d(res->index);
size_t nd, expected_data, expected_keys;
if (!safe_mul((size_t)n, (size_t)dim, &nd) || !safe_mul(nd, sizeof(float), &expected_data)) {
return make_error_msg(env, "size overflow");
}
if (!safe_mul((size_t)n, sizeof(int64_t), &expected_keys)) {
return make_error_msg(env, "size overflow");
}
if (data_bin.size != expected_data || keys_bin.size != expected_keys) {
return make_error_msg(env, "binary size mismatch");
}
if (n == 0) {
ErlNifBinary empty;
if (!enif_alloc_binary(0, &empty)) {
return make_error_msg(env, "failed to allocate result binary");
}
return make_ok(env, enif_make_binary(env, &empty));
}
ErlNifBinary result_bin;
if (!enif_alloc_binary(expected_data, &result_bin)) {
return make_error_msg(env, "failed to allocate result binary");
}
const float *data = (const float *)data_bin.data;
const idx_t *keys = (const idx_t *)keys_bin.data;
float *result = (float *)result_bin.data;
int ret = faiss_Index_compute_residual_n(res->index, (idx_t)n, data, result, keys);
if (ret != 0) {
enif_release_binary(&result_bin);
return make_faiss_error(env, "compute_residual failed");
}
return make_ok(env, enif_make_binary(env, &result_bin));
}
/* nif_write_index(ref, path_binary) */
static ERL_NIF_TERM nif_write_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
ErlNifBinary path_bin;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_inspect_binary(env, argv[1], &path_bin)) {
return make_error_msg(env, "invalid arguments");
}
char *path = (char *)enif_alloc(path_bin.size + 1);
if (!path) return make_error_msg(env, "out of memory");
memcpy(path, path_bin.data, path_bin.size);
path[path_bin.size] = '\0';
int ret = faiss_write_index_fname(res->index, path);
enif_free(path);
if (ret != 0) {
return make_faiss_error(env, "failed to write index");
}
return make_ok_atom(env);
}
/* nif_read_index(path_binary, io_flags) */
static ERL_NIF_TERM nif_read_index(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
ErlNifBinary path_bin;
int io_flags;
if (!enif_inspect_binary(env, argv[0], &path_bin) ||
!enif_get_int(env, argv[1], &io_flags)) {
return make_error_msg(env, "invalid arguments");
}
char *path = (char *)enif_alloc(path_bin.size + 1);
if (!path) return make_error_msg(env, "out of memory");
memcpy(path, path_bin.data, path_bin.size);
path[path_bin.size] = '\0';
FaissIndex *index = NULL;
int ret = faiss_read_index_fname(path, io_flags, &index);
enif_free(path);
if (ret != 0) {
return make_faiss_error(env, "failed to read index");
}
IndexResource *res = enif_alloc_resource(INDEX_RESOURCE_TYPE, sizeof(IndexResource));
if (!res) {
faiss_Index_free(index);
return make_error_msg(env, "failed to allocate resource");
}
res->index = index;
ERL_NIF_TERM ref = enif_make_resource(env, res);
enif_release_resource(res);
return make_ok(env, ref);
}
/* nif_get_index_dim(ref) */
static ERL_NIF_TERM nif_get_index_dim(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid index reference");
}
return make_ok(env, enif_make_int(env, faiss_Index_d(res->index)));
}
/* nif_get_index_ntotal(ref) */
static ERL_NIF_TERM nif_get_index_ntotal(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid index reference");
}
return make_ok(env, enif_make_int64(env, (int64_t)faiss_Index_ntotal(res->index)));
}
/* nif_get_index_is_trained(ref) */
static ERL_NIF_TERM nif_get_index_is_trained(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid index reference");
}
int trained = faiss_Index_is_trained(res->index);
return make_ok(env, trained ? make_atom(env, "true") : make_atom(env, "false"));
}
/* ========== NIF: GPU ========== */
#ifdef FAISS_GPU_ENABLED
/* nif_index_cpu_to_gpu(ref, device) - returns {gpu_resources_ref, gpu_index_ref} */
static ERL_NIF_TERM nif_index_cpu_to_gpu(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
int device;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res) ||
!enif_get_int(env, argv[1], &device)) {
return make_error_msg(env, "invalid arguments");
}
FaissStandardGpuResources *gpu_res = NULL;
int ret = faiss_StandardGpuResources_new(&gpu_res);
if (ret != 0) {
return make_faiss_error(env, "failed to create GPU resources");
}
FaissIndex *gpu_index = NULL;
ret = faiss_index_cpu_to_gpu((FaissGpuResourcesProvider *)gpu_res, device, res->index, &gpu_index);
if (ret != 0) {
faiss_StandardGpuResources_free(gpu_res);
return make_faiss_error(env, "failed to move index to GPU");
}
GpuResourcesResource *gpu_res_resource = enif_alloc_resource(
GPU_RESOURCES_RESOURCE_TYPE, sizeof(GpuResourcesResource));
if (!gpu_res_resource) {
faiss_Index_free(gpu_index);
faiss_StandardGpuResources_free(gpu_res);
return make_error_msg(env, "failed to allocate resource");
}
gpu_res_resource->resources = gpu_res;
ERL_NIF_TERM gpu_res_ref = enif_make_resource(env, gpu_res_resource);
enif_release_resource(gpu_res_resource);
IndexResource *gpu_idx_resource = enif_alloc_resource(INDEX_RESOURCE_TYPE, sizeof(IndexResource));
if (!gpu_idx_resource) {
faiss_Index_free(gpu_index);
/* gpu_res is owned by gpu_res_resource; env cleanup will free it */
return make_error_msg(env, "failed to allocate resource");
}
gpu_idx_resource->index = gpu_index;
ERL_NIF_TERM gpu_idx_ref = enif_make_resource(env, gpu_idx_resource);
enif_release_resource(gpu_idx_resource);
return make_ok(env, enif_make_tuple2(env, gpu_res_ref, gpu_idx_ref));
}
/* nif_index_gpu_to_cpu(ref) */
static ERL_NIF_TERM nif_index_gpu_to_cpu(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
IndexResource *res;
if (!enif_get_resource(env, argv[0], INDEX_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid index reference");
}
FaissIndex *cpu_index = NULL;
int ret = faiss_index_gpu_to_cpu(res->index, &cpu_index);
if (ret != 0) {
return make_faiss_error(env, "failed to move index to CPU");
}
IndexResource *new_res = enif_alloc_resource(INDEX_RESOURCE_TYPE, sizeof(IndexResource));
if (!new_res) {
faiss_Index_free(cpu_index);
return make_error_msg(env, "failed to allocate resource");
}
new_res->index = cpu_index;
ERL_NIF_TERM ref = enif_make_resource(env, new_res);
enif_release_resource(new_res);
return make_ok(env, ref);
}
/* nif_get_num_gpus() */
static ERL_NIF_TERM nif_get_num_gpus(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
(void)argv;
return make_ok(env, enif_make_int(env, faiss_get_num_gpus()));
}
#else /* !FAISS_GPU_ENABLED */
static ERL_NIF_TERM nif_index_cpu_to_gpu(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
(void)argv;
return make_error_msg(env, "GPU support not compiled");
}
static ERL_NIF_TERM nif_index_gpu_to_cpu(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
(void)argv;
return make_error_msg(env, "GPU support not compiled");
}
static ERL_NIF_TERM nif_get_num_gpus(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
(void)argv;
return make_ok(env, enif_make_int(env, 0));
}
#endif /* FAISS_GPU_ENABLED */
/* ========== NIF: Clustering ========== */
/* nif_new_clustering(d, k) */
static ERL_NIF_TERM nif_new_clustering(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
int d, k;
if (!enif_get_int(env, argv[0], &d) ||
!enif_get_int(env, argv[1], &k)) {
return make_error_msg(env, "invalid arguments");
}
FaissClustering *clustering = NULL;
int ret = faiss_Clustering_new(&clustering, d, k);
if (ret != 0) {
return make_faiss_error(env, "failed to create clustering");
}
ClusteringResource *res = enif_alloc_resource(CLUSTERING_RESOURCE_TYPE, sizeof(ClusteringResource));
if (!res) {
faiss_Clustering_free(clustering);
return make_error_msg(env, "failed to allocate resource");
}
res->clustering = clustering;
ERL_NIF_TERM ref = enif_make_resource(env, res);
enif_release_resource(res);
return make_ok(env, ref);
}
/* nif_train_clustering(clust_ref, n, data_binary, idx_ref) */
static ERL_NIF_TERM nif_train_clustering(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
ClusteringResource *clust_res;
ErlNifSInt64 n;
ErlNifBinary data_bin;
IndexResource *idx_res;
if (!enif_get_resource(env, argv[0], CLUSTERING_RESOURCE_TYPE, (void **)&clust_res) ||
!enif_get_int64(env, argv[1], &n) ||
!enif_inspect_binary(env, argv[2], &data_bin) ||
!enif_get_resource(env, argv[3], INDEX_RESOURCE_TYPE, (void **)&idx_res)) {
return make_error_msg(env, "invalid arguments");
}
if (n <= 0) return make_error_msg(env, "n must be positive");
int d = faiss_Clustering_d(clust_res->clustering);
size_t nd, expected;
if (!safe_mul((size_t)n, (size_t)d, &nd) || !safe_mul(nd, sizeof(float), &expected)) {
return make_error_msg(env, "size overflow");
}
if (data_bin.size != expected) {
return make_error_msg(env, "data binary size mismatch");
}
int ret = faiss_Clustering_train(clust_res->clustering, (idx_t)n,
(const float *)data_bin.data, idx_res->index);
if (ret != 0) {
return make_faiss_error(env, "clustering training failed");
}
return make_ok_atom(env);
}
/* nif_get_clustering_centroids(clust_ref) - returns {k, d, centroids_binary} */
static ERL_NIF_TERM nif_get_clustering_centroids(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
(void)argc;
ClusteringResource *res;
if (!enif_get_resource(env, argv[0], CLUSTERING_RESOURCE_TYPE, (void **)&res)) {
return make_error_msg(env, "invalid clustering reference");
}
size_t k = faiss_Clustering_k(res->clustering);
size_t d = faiss_Clustering_d(res->clustering);
float *centroids = NULL;
size_t centroids_size = 0;
faiss_Clustering_centroids(res->clustering, ¢roids, ¢roids_size);
if (!centroids || centroids_size == 0) {
return make_error_msg(env, "no centroids available");
}
size_t bin_size;
if (!safe_mul(centroids_size, sizeof(float), &bin_size)) {
return make_error_msg(env, "size overflow");
}
ErlNifBinary bin;
if (!enif_alloc_binary(bin_size, &bin)) {
return make_error_msg(env, "failed to allocate centroids binary");
}
memcpy(bin.data, centroids, bin_size);
ERL_NIF_TERM result = enif_make_tuple3(env,
enif_make_int64(env, (int64_t)k),
enif_make_int64(env, (int64_t)d),
enif_make_binary(env, &bin));
return make_ok(env, result);
}
/* ========== NIF Init ========== */
static int register_resource_types(ErlNifEnv *env, ErlNifResourceFlags flags) {
INDEX_RESOURCE_TYPE = enif_open_resource_type(
env, NULL, "FaissIndex", index_resource_destructor, flags, NULL);
if (!INDEX_RESOURCE_TYPE) return -1;
CLUSTERING_RESOURCE_TYPE = enif_open_resource_type(
env, NULL, "FaissClustering", clustering_resource_destructor, flags, NULL);
if (!CLUSTERING_RESOURCE_TYPE) return -1;
#ifdef FAISS_GPU_ENABLED
GPU_RESOURCES_RESOURCE_TYPE = enif_open_resource_type(
env, NULL, "FaissGpuResources", gpu_resources_destructor, flags, NULL);
if (!GPU_RESOURCES_RESOURCE_TYPE) return -1;
#endif
return 0;
}
static int on_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
(void)priv_data;
(void)load_info;
return register_resource_types(env, ERL_NIF_RT_CREATE);
}
static int on_upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info) {
(void)priv_data;
(void)old_priv_data;
(void)load_info;
return register_resource_types(env, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
}
static ErlNifFunc nif_funcs[] = {
/* Index */
{"nif_new_index", 3, nif_new_index, 0},
{"nif_clone_index", 1, nif_clone_index, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_add_to_index", 3, nif_add_to_index, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_add_with_ids_to_index", 4, nif_add_with_ids_to_index, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_search_index", 4, nif_search_index, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_train_index", 3, nif_train_index, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_reset_index", 1, nif_reset_index, 0},
{"nif_reconstruct_batch", 3, nif_reconstruct_batch, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_compute_residuals", 4, nif_compute_residuals, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_write_index", 2, nif_write_index, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"nif_read_index", 2, nif_read_index, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"nif_get_index_dim", 1, nif_get_index_dim, 0},
{"nif_get_index_ntotal", 1, nif_get_index_ntotal, 0},
{"nif_get_index_is_trained", 1, nif_get_index_is_trained, 0},
/* GPU */
{"nif_index_cpu_to_gpu", 2, nif_index_cpu_to_gpu, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"nif_index_gpu_to_cpu", 1, nif_index_gpu_to_cpu, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"nif_get_num_gpus", 0, nif_get_num_gpus, 0},
/* Clustering */
{"nif_new_clustering", 2, nif_new_clustering, 0},
{"nif_train_clustering", 4, nif_train_clustering, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"nif_get_clustering_centroids", 1, nif_get_clustering_centroids, 0},
};
ERL_NIF_INIT(Elixir.FaissEx.NIF, nif_funcs, on_load, NULL, on_upgrade, NULL)