From 6e12b7ef2e88d2e10a8f53bef7c78c069358021d Mon Sep 17 00:00:00 2001 From: maxwellflitton Date: Tue, 21 Jan 2025 15:21:54 +0000 Subject: [PATCH] dynamic C lib now linking to onnxruntime --- modules/c-wrapper/.gitignore | 1 + modules/c-wrapper/Cargo.toml | 1 + modules/c-wrapper/Dockerfile | 4 +- modules/c-wrapper/build-context/Dockerfile | 4 +- .../build-context/c-wrapper/Cargo.toml | 11 +- .../c-wrapper/scripts/build-docker.sh | 11 +- .../build-context/c-wrapper/src/api/mod.rs | 1 + .../tests/test_utils/c_lib_loader.py | 12 +- .../c-wrapper/tests/test_utils/routes.py | 1 + .../c-wrapper/build-context/core/Cargo.toml | 2 +- modules/c-wrapper/build-context/core/build.rs | 111 +--------------- .../core/src/execution/session.rs | 31 ++++- modules/c-wrapper/build.rs | 121 ++++++++++++++++++ modules/c-wrapper/scripts/build-docker.sh | 11 +- modules/c-wrapper/scripts/copy_over_lib.sh | 38 ++++++ modules/c-wrapper/src/api/ml_sys/link_onnx.rs | 43 +++++++ modules/c-wrapper/src/api/ml_sys/mod.rs | 1 + modules/c-wrapper/src/api/mod.rs | 1 + .../tests/test_utils/c_lib_loader.py | 12 +- modules/c-wrapper/tests/test_utils/routes.py | 1 + modules/core/build.rs | 111 +--------------- modules/core/src/execution/session.rs | 6 +- 22 files changed, 296 insertions(+), 239 deletions(-) create mode 100644 modules/c-wrapper/.gitignore create mode 100644 modules/c-wrapper/build.rs create mode 100644 modules/c-wrapper/scripts/copy_over_lib.sh create mode 100644 modules/c-wrapper/src/api/ml_sys/link_onnx.rs create mode 100644 modules/c-wrapper/src/api/ml_sys/mod.rs diff --git a/modules/c-wrapper/.gitignore b/modules/c-wrapper/.gitignore new file mode 100644 index 0000000..e0d4e69 --- /dev/null +++ b/modules/c-wrapper/.gitignore @@ -0,0 +1 @@ +onnx_lib/ diff --git a/modules/c-wrapper/Cargo.toml b/modules/c-wrapper/Cargo.toml index 46ee50a..cfe851c 100644 --- a/modules/c-wrapper/Cargo.toml +++ b/modules/c-wrapper/Cargo.toml @@ -21,3 +21,4 @@ reqwest = { version = "0.12.12", features = ["blocking", "json"] } # tokio = { version = "1", features = ["full"] } # Required for reqwest tar = "0.4" # For extracting tar files flate2 = "1.0" # For handling gzip +zip = "2.2.2" diff --git a/modules/c-wrapper/Dockerfile b/modules/c-wrapper/Dockerfile index 584f533..95a60c7 100644 --- a/modules/c-wrapper/Dockerfile +++ b/modules/c-wrapper/Dockerfile @@ -37,7 +37,9 @@ COPY . . RUN apt-get update && apt-get install -y python3 python3-pip # Clean and build the Rust project -RUN cd c-wrapper/scripts && bash prep_tests.sh +# RUN cd c-wrapper/scripts && bash prep_tests.sh +# RUN cd c-wrapper && cargo build --verbose > build_log.txt 2>&1 +RUN cd c-wrapper && cargo build && bash scripts/copy_over_lib.sh # RUN rm /onnxruntime diff --git a/modules/c-wrapper/build-context/Dockerfile b/modules/c-wrapper/build-context/Dockerfile index 584f533..95a60c7 100644 --- a/modules/c-wrapper/build-context/Dockerfile +++ b/modules/c-wrapper/build-context/Dockerfile @@ -37,7 +37,9 @@ COPY . . RUN apt-get update && apt-get install -y python3 python3-pip # Clean and build the Rust project -RUN cd c-wrapper/scripts && bash prep_tests.sh +# RUN cd c-wrapper/scripts && bash prep_tests.sh +# RUN cd c-wrapper && cargo build --verbose > build_log.txt 2>&1 +RUN cd c-wrapper && cargo build && bash scripts/copy_over_lib.sh # RUN rm /onnxruntime diff --git a/modules/c-wrapper/build-context/c-wrapper/Cargo.toml b/modules/c-wrapper/build-context/c-wrapper/Cargo.toml index 1274cac..cfe851c 100644 --- a/modules/c-wrapper/build-context/c-wrapper/Cargo.toml +++ b/modules/c-wrapper/build-context/c-wrapper/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -surrealml-core = { path = "../core" } -uuid = { version = "1.4.1", features = ["v4"] } +surrealml-core = { path = "../core", features = ["dynamic"] } +uuid = { version = "1.11.1", features = ["v4"] } ndarray = "0.16.1" # for the uploading the model to the server @@ -15,3 +15,10 @@ base64 = "0.13" [lib] crate-type = ["cdylib"] + +[build-dependencies] +reqwest = { version = "0.12.12", features = ["blocking", "json"] } +# tokio = { version = "1", features = ["full"] } # Required for reqwest +tar = "0.4" # For extracting tar files +flate2 = "1.0" # For handling gzip +zip = "2.2.2" diff --git a/modules/c-wrapper/build-context/c-wrapper/scripts/build-docker.sh b/modules/c-wrapper/build-context/c-wrapper/scripts/build-docker.sh index 3ac8066..7ffdba6 100644 --- a/modules/c-wrapper/build-context/c-wrapper/scripts/build-docker.sh +++ b/modules/c-wrapper/build-context/c-wrapper/scripts/build-docker.sh @@ -6,26 +6,29 @@ cd $SCRIPTPATH cd .. +# wipe and build the build context BUILD_DIR="build-context" - if [ -d "$BUILD_DIR" ]; then echo "Cleaning up existing build directory..." rm -rf "$BUILD_DIR" fi - mkdir "$BUILD_DIR" mkdir "$BUILD_DIR"/c-wrapper + +# copy over the code to be built cp -r src "$BUILD_DIR"/c-wrapper/src cp -r tests "$BUILD_DIR"/c-wrapper/tests cp -r scripts "$BUILD_DIR"/c-wrapper/scripts cp Cargo.toml "$BUILD_DIR"/c-wrapper/Cargo.toml - +cp build.rs "$BUILD_DIR"/c-wrapper/build.rs cp -r ../core "$BUILD_DIR"/core +cp Dockerfile "$BUILD_DIR"/Dockerfile +# remove unnecessary files rm -rf "$BUILD_DIR"/core/.git rm -rf "$BUILD_DIR"/core/target/ -cp Dockerfile "$BUILD_DIR"/Dockerfile +# build the docker image cd "$BUILD_DIR" docker build --no-cache -t c-wrapper-tests . diff --git a/modules/c-wrapper/build-context/c-wrapper/src/api/mod.rs b/modules/c-wrapper/build-context/c-wrapper/src/api/mod.rs index a81ff7d..1f0b9fa 100644 --- a/modules/c-wrapper/build-context/c-wrapper/src/api/mod.rs +++ b/modules/c-wrapper/build-context/c-wrapper/src/api/mod.rs @@ -1,3 +1,4 @@ //! C API for interacting with the SurML file storage and executing models. pub mod execution; pub mod storage; +pub mod ml_sys; diff --git a/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/c_lib_loader.py b/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/c_lib_loader.py index 64908b2..00adfc6 100644 --- a/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/c_lib_loader.py +++ b/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/c_lib_loader.py @@ -2,6 +2,7 @@ import platform from pathlib import Path import os +from test_utils.return_structs import EmptyReturn def load_library(lib_name: str = "libc_wrapper") -> ctypes.CDLL: @@ -38,9 +39,18 @@ def load_library(lib_name: str = "libc_wrapper") -> ctypes.CDLL: # os.environ["LD_LIBRARY_PATH"] = f"{onnx_lib_path}:{current_ld_library_path}" # os.environ["ORT_LIB_LOCATION"] = str(onnx_lib_path) - ctypes.CDLL(str(onnx_path), mode=ctypes.RTLD_GLOBAL) + # ctypes.CDLL(str(onnx_path), mode=ctypes.RTLD_GLOBAL) + onnx_path = current_dir.joinpath("onnxruntime") if not lib_path.exists(): raise FileNotFoundError(f"Shared library not found at: {lib_path}") + + loaded_lib = ctypes.CDLL(str(lib_path)) + loaded_lib.link_onnx.argtypes = [ctypes.c_char_p] + loaded_lib.link_onnx.restype = EmptyReturn + c_string = str(onnx_path).encode('utf-8') + load_info = loaded_lib.link_onnx(c_string) + if load_info.error_message: + raise OSError(f"Failed to load onnxruntime: {load_info.error_message.decode('utf-8')}") return ctypes.CDLL(str(lib_path)) diff --git a/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/routes.py b/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/routes.py index bca182d..07e07e7 100644 --- a/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/routes.py +++ b/modules/c-wrapper/build-context/c-wrapper/tests/test_utils/routes.py @@ -9,3 +9,4 @@ TEST_SURML_PATH = ASSETS_PATH.joinpath("test.surml") SHOULD_BREAK_FILE = ASSETS_PATH.joinpath("should_break.txt") TEST_ONNX_FILE_PATH = ASSETS_PATH.joinpath("linear_test.onnx") +ONNX_LIB = UTILS_PATH.joinpath("..").joinpath("..").joinpath("onnx_lib").joinpath("onnxruntime") diff --git a/modules/c-wrapper/build-context/core/Cargo.toml b/modules/c-wrapper/build-context/core/Cargo.toml index 0a8d8b9..8d2dac1 100644 --- a/modules/c-wrapper/build-context/core/Cargo.toml +++ b/modules/c-wrapper/build-context/core/Cargo.toml @@ -18,10 +18,10 @@ onnx-tests = [] torch-tests = [] tensorflow-tests = [] gpu = [] +dynamic = ["ort/load-dynamic"] [dependencies] regex = "1.9.3" -# ort = { version = "1.16.2", features = ["load-dynamic"], default-features = false } ort = { version = "2.0.0-rc.9", features = [ "cuda", "ndarray" ]} ndarray = "0.16.1" once_cell = "1.18.0" diff --git a/modules/c-wrapper/build-context/core/build.rs b/modules/c-wrapper/build-context/core/build.rs index a48ef5d..a1fd6aa 100644 --- a/modules/c-wrapper/build-context/core/build.rs +++ b/modules/c-wrapper/build-context/core/build.rs @@ -1,113 +1,4 @@ -// use std::env; -// use std::fs; -// use std::path::Path; - -// /// works out where the `onnxruntime` library is in the build target and copies the library to the root -// /// of the crate so the core library can find it and load it into the binary using `include_bytes!()`. -// /// -// /// # Notes -// /// This is a workaround for the fact that `onnxruntime` doesn't support `cargo` yet. This build step -// /// is reliant on the `ort` crate downloading and building the `onnxruntime` library. This is -// /// why the following dependency is required in `Cargo.toml`: -// /// ```toml -// /// [build-dependencies] -// /// ort = { version = "1.16.2", default-features = true } -// /// ``` -// /// Here we can see that the `default-features` is set to `true`. This is because the `ort` crate will download -// /// the correct package and build it for the target platform by default. In the main part of our dependencies -// /// we have the following: -// /// ```toml -// /// [dependencies] -// /// ort = { version = "1.16.2", features = ["load-dynamic"], default-features = false } -// /// ``` -// /// Here we can see that the `default-features` is set to `false`. This is because we don't want the `ort` crate -// /// to download and build the `onnxruntime` library again. Instead we want to use the one that was built in the -// /// build step. We also set the `load-dynamic` feature to `true` so that the `ort` crate will load the `onnxruntime` -// /// library dynamically at runtime. This is because we don't want to statically link the `onnxruntime`. Our `onnxruntime` -// /// is embedded into the binary using `include_bytes!()` and we want to load it dynamically at runtime. This means that -// /// we do not need to move the `onnxruntime` library around with the binary, and there is no complicated setup required -// /// or linking. -// fn unpack_onnx() -> std::io::Result<()> { -// let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); -// let out_path = Path::new(&out_dir); -// let build_dir = out_path -// .ancestors() // This gives an iterator over all ancestors of the path -// .nth(3) // 'nth(3)' gets the fourth ancestor (counting from 0), which should be the debug directory -// .expect("Failed to find debug directory"); - -// match std::env::var("ONNXRUNTIME_LIB_PATH") { -// Ok(onnx_path) => { -// println!("Surrealml Core Debug: ONNXRUNTIME_LIB_PATH set at: {}", onnx_path); -// println!("cargo:rustc-cfg=onnx_runtime_env_var_set"); -// } -// Err(_) => { -// println!("Surrealml Core Debug: ONNXRUNTIME_LIB_PATH not set"); -// let target_lib = match env::var("CARGO_CFG_TARGET_OS").unwrap() { -// ref s if s.contains("linux") => "libonnxruntime.so", -// ref s if s.contains("macos") => "libonnxruntime.dylib", -// ref s if s.contains("windows") => "onnxruntime.dll", -// // ref s if s.contains("android") => "android", => not building for android -// _ => panic!("Unsupported target os"), -// }; - -// let lib_path = build_dir.join(target_lib); -// let lib_path = lib_path.to_str().unwrap(); -// println!("Surrealml Core Debug: lib_path={}", lib_path); - -// // Check if the path exists -// if fs::metadata(lib_path).is_ok() { -// println!("Surrealml Core Debug: lib_path exists"); -// } else { -// println!("Surrealml Core Debug: lib_path does not exist"); -// // Extract the directory path -// if let Some(parent) = std::path::Path::new(lib_path).parent() { -// // Print the contents of the directory -// match fs::read_dir(parent) { -// Ok(entries) => { -// println!("Surrealml Core Debug: content of directory {}", parent.display()); -// for entry in entries { -// if let Ok(entry) = entry { -// println!("{}", entry.path().display()); -// } -// } -// } -// Err(e) => { -// println!("Surrealml Core Debug: Failed to read directory {}: {}", parent.display(), e); -// } -// } -// } else { -// println!("Surrealml Core Debug: Could not determine the parent directory of the path."); -// } -// } - -// // put it next to the file of the embedding -// let destination = Path::new(target_lib); -// fs::copy(lib_path, destination)?; -// println!("Surrealml Core Debug: onnx lib copied from {} to {}", lib_path, destination.display()); -// } -// } -// Ok(()) -// } - -// fn main() -> std::io::Result<()> { -// if std::env::var("DOCS_RS").is_ok() { -// // we are not going to be anything here for docs.rs, because we are merely building the docs. When we are just building -// // the docs, the onnx environment variable will not look for the `onnxruntime` library, so we don't need to unpack it. -// return Ok(()); -// } - -// if env::var("ORT_STRATEGY").as_deref() == Ok("system") { -// // If the ORT crate is built with the `system` strategy, then the crate will take care of statically linking the library. -// // No need to do anything here. -// println!("cargo:rustc-cfg=onnx_statically_linked"); - -// return Ok(()); -// } - -// unpack_onnx()?; -// Ok(()) -// } fn main() { -} \ No newline at end of file +} diff --git a/modules/c-wrapper/build-context/core/src/execution/session.rs b/modules/c-wrapper/build-context/core/src/execution/session.rs index 1ebf097..69a847b 100644 --- a/modules/c-wrapper/build-context/core/src/execution/session.rs +++ b/modules/c-wrapper/build-context/core/src/execution/session.rs @@ -3,6 +3,15 @@ use ort::session::Session; use crate::errors::error::{SurrealError, SurrealErrorStatus}; use crate::safe_eject; +#[cfg(feature = "dynamic")] +use once_cell::sync::Lazy; +#[cfg(feature = "dynamic")] +use ort::environment::{EnvironmentBuilder, Environment}; +#[cfg(feature = "dynamic")] +use std::sync::{Arc, Mutex}; + +use std::sync::LazyLock; + /// Creates a session for a model. /// @@ -26,4 +35,24 @@ pub fn get_session(model_bytes: Vec) -> Result { let session: Session = safe_eject!(builder .commit_from_memory(&model_bytes), SurrealErrorStatus::Unknown); Ok(session) -} \ No newline at end of file +} + + +// #[cfg(feature = "dynamic")] +// pub static ORT_ENV: LazyLock>>>> = LazyLock::new(|| Arc::new(Mutex::new(None))); + + +#[cfg(feature = "dynamic")] +pub fn set_environment(dylib_path: String) -> Result<(), SurrealError> { + + let outcome: EnvironmentBuilder = ort::init_from(dylib_path); + match outcome.commit() { + Ok(env) => { + // ORT_ENV.lock().unwrap().replace(env); + }, + Err(e) => { + return Err(SurrealError::new(e.to_string(), SurrealErrorStatus::Unknown)); + } + } + Ok(()) +} diff --git a/modules/c-wrapper/build.rs b/modules/c-wrapper/build.rs new file mode 100644 index 0000000..bc0e78e --- /dev/null +++ b/modules/c-wrapper/build.rs @@ -0,0 +1,121 @@ +use std::env; +use std::fs; +use std::io::prelude::*; +use std::fs::File; +use std::io::Cursor; +use std::path::Path; +use reqwest::blocking::get; +use flate2::read::GzDecoder; +use tar::Archive; + +fn main() { + let version = "1.20.1"; + let root_dir_str = env::var("OUT_DIR").unwrap(); + let root_dir = Path::new(&root_dir_str); + let current_working_dir = std::env::current_dir().unwrap(); + let out_dir = std::env::current_dir().unwrap().join("onnx_lib"); + + // Create output directory + fs::create_dir_all(&out_dir).expect("Failed to create output directory"); + + // Detect OS and architecture + let target_os = env::var("CARGO_CFG_TARGET_OS").expect("Failed to get target OS"); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Failed to get target architecture"); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); // Optional: For specific environments like MSVC + + // Map to appropriate URL + let file_extension = match target_os.as_str() { + "windows" => "zip", + _ => "tgz", + }; + + // Construct the directory name + // linux aarch64 + let directory_name = match (target_os.as_str(), target_arch.as_str()) { + ("linux", "aarch64") => format!("onnxruntime-linux-aarch64-{version}"), + ("linux", "x86_64") => format!("onnxruntime-linux-x64-{version}"), + ("macos", "aarch64") => format!("onnxruntime-osx-arm64-{version}"), + ("macos", "x86_64") => format!("onnxruntime-osx-x86_64-{version}"), + ("windows", "aarch64") => format!("onnxruntime-win-arm64-{version}"), + ("windows", "x86_64") => format!("onnxruntime-win-x64-{version}"), + ("windows", "x86") => format!("onnxruntime-win-x86-{version}"), + _ => panic!("Unsupported OS/architecture combination"), + }; + println!("build directory defined: {}", directory_name); + + let filename = match (target_os.as_str(), target_arch.as_str(), target_env.as_str()) { + ("linux", "aarch64", _) => format!("onnxruntime-linux-aarch64-{version}.{file_extension}"), + ("linux", "x86_64", _) => { + if cfg!(feature = "gpu") { + format!("onnxruntime-linux-x64-gpu-{version}.{file_extension}") + } else { + format!("onnxruntime-linux-x64-{version}.{file_extension}") + } + } + ("macos", "aarch64", _) => format!("onnxruntime-osx-arm64-{version}.{file_extension}"), + ("macos", "x86_64", _) => format!("onnxruntime-osx-x86_64-{version}.{file_extension}"), + ("windows", "x86_64", _) => { + if cfg!(feature = "gpu") { + format!("onnxruntime-win-x64-gpu-{version}.{file_extension}") + } else { + format!("onnxruntime-win-x64-{version}.{file_extension}") + } + } + ("windows", "x86", _) => format!("onnxruntime-win-x86-{version}.{file_extension}"), + ("windows", "aarch64", _) => format!("onnxruntime-win-arm64-{version}.{file_extension}"), + _ => panic!("Unsupported OS/architecture combination"), + }; + println!("build filename defined: {}", filename); + + let url = format!( + "https://github.com/microsoft/onnxruntime/releases/download/v{version}/{filename}" + ); + + // Download and extract + println!("Downloading ONNX Runtime from {}", url); + let response = get(&url).expect("Failed to send request"); + if !response.status().is_success() { + panic!("Failed to download ONNX Runtime: HTTP {}", response.status()); + } + println!("Downloaded ONNX Runtime successfully"); + + if file_extension == "tgz" { + let tar_gz = GzDecoder::new(Cursor::new(response.bytes().expect("Failed to read response bytes"))); + let mut archive = Archive::new(tar_gz); + archive.unpack(&out_dir).expect("Failed to extract archive"); + } else if file_extension == "zip" { + let mut archive = zip::ZipArchive::new(Cursor::new( + response.bytes().expect("Failed to read response bytes"), + )) + .expect("Failed to open ZIP archive"); + archive.extract(&out_dir).expect("Failed to extract ZIP archive"); + } + println!("Extracted ONNX Runtime successfully"); + + let lib_filename = match target_os.as_str() { + "windows" => "onnxruntime.dll", + "macos" => "libonnxruntime.dylib", + _ => "libonnxruntime.so", + }; + println!("lib filename defined: {}", lib_filename); + + let output_dir = Path::new(&out_dir); + let lib_path = output_dir.join(directory_name.clone()).join("lib").join(lib_filename); + + // copy the library to the output directory + fs::copy(&lib_path, Path::new(&out_dir).join("onnxruntime")).expect("Failed to copy library"); + + let path_data = format!("Copied library to output directory {} -> {}", lib_path.display(), out_dir.display()); + + let mut file = File::create(current_working_dir.join("build_output.txt")).unwrap(); + file.write_all(path_data.as_bytes()).unwrap(); + + println!("{}", path_data); + // remove the out_dir + fs::remove_dir_all(&output_dir.join(directory_name)).expect("Failed to remove output directory"); + + let output_lib = Path::new(&out_dir).join("onnxruntime"); + + // link the library + println!("cargo:rustc-env=ORT_LIB_LOCATION={}", output_lib.display()); +} diff --git a/modules/c-wrapper/scripts/build-docker.sh b/modules/c-wrapper/scripts/build-docker.sh index 3ac8066..7ffdba6 100644 --- a/modules/c-wrapper/scripts/build-docker.sh +++ b/modules/c-wrapper/scripts/build-docker.sh @@ -6,26 +6,29 @@ cd $SCRIPTPATH cd .. +# wipe and build the build context BUILD_DIR="build-context" - if [ -d "$BUILD_DIR" ]; then echo "Cleaning up existing build directory..." rm -rf "$BUILD_DIR" fi - mkdir "$BUILD_DIR" mkdir "$BUILD_DIR"/c-wrapper + +# copy over the code to be built cp -r src "$BUILD_DIR"/c-wrapper/src cp -r tests "$BUILD_DIR"/c-wrapper/tests cp -r scripts "$BUILD_DIR"/c-wrapper/scripts cp Cargo.toml "$BUILD_DIR"/c-wrapper/Cargo.toml - +cp build.rs "$BUILD_DIR"/c-wrapper/build.rs cp -r ../core "$BUILD_DIR"/core +cp Dockerfile "$BUILD_DIR"/Dockerfile +# remove unnecessary files rm -rf "$BUILD_DIR"/core/.git rm -rf "$BUILD_DIR"/core/target/ -cp Dockerfile "$BUILD_DIR"/Dockerfile +# build the docker image cd "$BUILD_DIR" docker build --no-cache -t c-wrapper-tests . diff --git a/modules/c-wrapper/scripts/copy_over_lib.sh b/modules/c-wrapper/scripts/copy_over_lib.sh new file mode 100644 index 0000000..135212d --- /dev/null +++ b/modules/c-wrapper/scripts/copy_over_lib.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# navigate to directory +SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" +cd $SCRIPTPATH + +cd .. +OS=$(uname) + +# Set the library name and extension based on the OS +case "$OS" in + "Linux") + LIB_NAME="libc_wrapper.so" + ;; + "Darwin") + LIB_NAME="libc_wrapper.dylib" + ;; + "CYGWIN"*|"MINGW"*) + LIB_NAME="libc_wrapper.dll" + ;; + *) + echo "Unsupported operating system: $OS" + exit 1 + ;; +esac + +# Source directory (where Cargo outputs the compiled library) +SOURCE_DIR="target/debug" + +# Destination directory (tests directory) +DEST_DIR="tests/test_utils" + +# Destination directory (onnxruntime library) +LIB_PATH="onnx_lib/onnxruntime" + + +cp "$SOURCE_DIR/$LIB_NAME" "$DEST_DIR/" +cp "$LIB_PATH" "$DEST_DIR/" diff --git a/modules/c-wrapper/src/api/ml_sys/link_onnx.rs b/modules/c-wrapper/src/api/ml_sys/link_onnx.rs new file mode 100644 index 0000000..0760a12 --- /dev/null +++ b/modules/c-wrapper/src/api/ml_sys/link_onnx.rs @@ -0,0 +1,43 @@ +use surrealml_core::execution::session::set_environment; +use std::ffi::{c_float, CStr, CString, c_int, c_char}; +use crate::utils::EmptyReturn; + + +/// Links the onnx file to the environment. +/// +/// # Arguments +/// * `onnx_path` - The path to the onnx file. +/// +/// # Returns +/// An EmptyReturn object containing the outcome of the operation. +#[no_mangle] +pub extern "C" fn link_onnx(onnx_path: *const c_char) -> EmptyReturn { + if onnx_path.is_null() { + return EmptyReturn { + is_error: 1, + error_message: CString::new("Onnx path is null").unwrap().into_raw() + } + } + let onnx_path = match unsafe { CStr::from_ptr(onnx_path) }.to_str() { + Ok(onnx_path) => onnx_path.to_owned(), + Err(error) => return EmptyReturn { + is_error: 1, + error_message: CString::new(format!("Error getting onnx path: {}", error)).unwrap().into_raw() + } + }; + match set_environment(onnx_path) { + Ok(_) => { + EmptyReturn { + is_error: 0, + error_message: std::ptr::null_mut() + } + }, + Err(e) => { + println!("Error linking onnx file to environment: {}", e); + EmptyReturn { + is_error: 1, + error_message: CString::new(e.to_string()).unwrap().into_raw() + } + } + } +} diff --git a/modules/c-wrapper/src/api/ml_sys/mod.rs b/modules/c-wrapper/src/api/ml_sys/mod.rs new file mode 100644 index 0000000..1d6da62 --- /dev/null +++ b/modules/c-wrapper/src/api/ml_sys/mod.rs @@ -0,0 +1 @@ +pub mod link_onnx; diff --git a/modules/c-wrapper/src/api/mod.rs b/modules/c-wrapper/src/api/mod.rs index a81ff7d..1f0b9fa 100644 --- a/modules/c-wrapper/src/api/mod.rs +++ b/modules/c-wrapper/src/api/mod.rs @@ -1,3 +1,4 @@ //! C API for interacting with the SurML file storage and executing models. pub mod execution; pub mod storage; +pub mod ml_sys; diff --git a/modules/c-wrapper/tests/test_utils/c_lib_loader.py b/modules/c-wrapper/tests/test_utils/c_lib_loader.py index 64908b2..00adfc6 100644 --- a/modules/c-wrapper/tests/test_utils/c_lib_loader.py +++ b/modules/c-wrapper/tests/test_utils/c_lib_loader.py @@ -2,6 +2,7 @@ import platform from pathlib import Path import os +from test_utils.return_structs import EmptyReturn def load_library(lib_name: str = "libc_wrapper") -> ctypes.CDLL: @@ -38,9 +39,18 @@ def load_library(lib_name: str = "libc_wrapper") -> ctypes.CDLL: # os.environ["LD_LIBRARY_PATH"] = f"{onnx_lib_path}:{current_ld_library_path}" # os.environ["ORT_LIB_LOCATION"] = str(onnx_lib_path) - ctypes.CDLL(str(onnx_path), mode=ctypes.RTLD_GLOBAL) + # ctypes.CDLL(str(onnx_path), mode=ctypes.RTLD_GLOBAL) + onnx_path = current_dir.joinpath("onnxruntime") if not lib_path.exists(): raise FileNotFoundError(f"Shared library not found at: {lib_path}") + + loaded_lib = ctypes.CDLL(str(lib_path)) + loaded_lib.link_onnx.argtypes = [ctypes.c_char_p] + loaded_lib.link_onnx.restype = EmptyReturn + c_string = str(onnx_path).encode('utf-8') + load_info = loaded_lib.link_onnx(c_string) + if load_info.error_message: + raise OSError(f"Failed to load onnxruntime: {load_info.error_message.decode('utf-8')}") return ctypes.CDLL(str(lib_path)) diff --git a/modules/c-wrapper/tests/test_utils/routes.py b/modules/c-wrapper/tests/test_utils/routes.py index bca182d..07e07e7 100644 --- a/modules/c-wrapper/tests/test_utils/routes.py +++ b/modules/c-wrapper/tests/test_utils/routes.py @@ -9,3 +9,4 @@ TEST_SURML_PATH = ASSETS_PATH.joinpath("test.surml") SHOULD_BREAK_FILE = ASSETS_PATH.joinpath("should_break.txt") TEST_ONNX_FILE_PATH = ASSETS_PATH.joinpath("linear_test.onnx") +ONNX_LIB = UTILS_PATH.joinpath("..").joinpath("..").joinpath("onnx_lib").joinpath("onnxruntime") diff --git a/modules/core/build.rs b/modules/core/build.rs index a48ef5d..a1fd6aa 100644 --- a/modules/core/build.rs +++ b/modules/core/build.rs @@ -1,113 +1,4 @@ -// use std::env; -// use std::fs; -// use std::path::Path; - -// /// works out where the `onnxruntime` library is in the build target and copies the library to the root -// /// of the crate so the core library can find it and load it into the binary using `include_bytes!()`. -// /// -// /// # Notes -// /// This is a workaround for the fact that `onnxruntime` doesn't support `cargo` yet. This build step -// /// is reliant on the `ort` crate downloading and building the `onnxruntime` library. This is -// /// why the following dependency is required in `Cargo.toml`: -// /// ```toml -// /// [build-dependencies] -// /// ort = { version = "1.16.2", default-features = true } -// /// ``` -// /// Here we can see that the `default-features` is set to `true`. This is because the `ort` crate will download -// /// the correct package and build it for the target platform by default. In the main part of our dependencies -// /// we have the following: -// /// ```toml -// /// [dependencies] -// /// ort = { version = "1.16.2", features = ["load-dynamic"], default-features = false } -// /// ``` -// /// Here we can see that the `default-features` is set to `false`. This is because we don't want the `ort` crate -// /// to download and build the `onnxruntime` library again. Instead we want to use the one that was built in the -// /// build step. We also set the `load-dynamic` feature to `true` so that the `ort` crate will load the `onnxruntime` -// /// library dynamically at runtime. This is because we don't want to statically link the `onnxruntime`. Our `onnxruntime` -// /// is embedded into the binary using `include_bytes!()` and we want to load it dynamically at runtime. This means that -// /// we do not need to move the `onnxruntime` library around with the binary, and there is no complicated setup required -// /// or linking. -// fn unpack_onnx() -> std::io::Result<()> { -// let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); -// let out_path = Path::new(&out_dir); -// let build_dir = out_path -// .ancestors() // This gives an iterator over all ancestors of the path -// .nth(3) // 'nth(3)' gets the fourth ancestor (counting from 0), which should be the debug directory -// .expect("Failed to find debug directory"); - -// match std::env::var("ONNXRUNTIME_LIB_PATH") { -// Ok(onnx_path) => { -// println!("Surrealml Core Debug: ONNXRUNTIME_LIB_PATH set at: {}", onnx_path); -// println!("cargo:rustc-cfg=onnx_runtime_env_var_set"); -// } -// Err(_) => { -// println!("Surrealml Core Debug: ONNXRUNTIME_LIB_PATH not set"); -// let target_lib = match env::var("CARGO_CFG_TARGET_OS").unwrap() { -// ref s if s.contains("linux") => "libonnxruntime.so", -// ref s if s.contains("macos") => "libonnxruntime.dylib", -// ref s if s.contains("windows") => "onnxruntime.dll", -// // ref s if s.contains("android") => "android", => not building for android -// _ => panic!("Unsupported target os"), -// }; - -// let lib_path = build_dir.join(target_lib); -// let lib_path = lib_path.to_str().unwrap(); -// println!("Surrealml Core Debug: lib_path={}", lib_path); - -// // Check if the path exists -// if fs::metadata(lib_path).is_ok() { -// println!("Surrealml Core Debug: lib_path exists"); -// } else { -// println!("Surrealml Core Debug: lib_path does not exist"); -// // Extract the directory path -// if let Some(parent) = std::path::Path::new(lib_path).parent() { -// // Print the contents of the directory -// match fs::read_dir(parent) { -// Ok(entries) => { -// println!("Surrealml Core Debug: content of directory {}", parent.display()); -// for entry in entries { -// if let Ok(entry) = entry { -// println!("{}", entry.path().display()); -// } -// } -// } -// Err(e) => { -// println!("Surrealml Core Debug: Failed to read directory {}: {}", parent.display(), e); -// } -// } -// } else { -// println!("Surrealml Core Debug: Could not determine the parent directory of the path."); -// } -// } - -// // put it next to the file of the embedding -// let destination = Path::new(target_lib); -// fs::copy(lib_path, destination)?; -// println!("Surrealml Core Debug: onnx lib copied from {} to {}", lib_path, destination.display()); -// } -// } -// Ok(()) -// } - -// fn main() -> std::io::Result<()> { -// if std::env::var("DOCS_RS").is_ok() { -// // we are not going to be anything here for docs.rs, because we are merely building the docs. When we are just building -// // the docs, the onnx environment variable will not look for the `onnxruntime` library, so we don't need to unpack it. -// return Ok(()); -// } - -// if env::var("ORT_STRATEGY").as_deref() == Ok("system") { -// // If the ORT crate is built with the `system` strategy, then the crate will take care of statically linking the library. -// // No need to do anything here. -// println!("cargo:rustc-cfg=onnx_statically_linked"); - -// return Ok(()); -// } - -// unpack_onnx()?; -// Ok(()) -// } fn main() { -} \ No newline at end of file +} diff --git a/modules/core/src/execution/session.rs b/modules/core/src/execution/session.rs index ab78cfa..69a847b 100644 --- a/modules/core/src/execution/session.rs +++ b/modules/core/src/execution/session.rs @@ -38,8 +38,8 @@ pub fn get_session(model_bytes: Vec) -> Result { } -#[cfg(feature = "dynamic")] -pub static ORT_ENV: LazyLock>>>> = LazyLock::new(|| Arc::new(Mutex::new(None))); +// #[cfg(feature = "dynamic")] +// pub static ORT_ENV: LazyLock>>>> = LazyLock::new(|| Arc::new(Mutex::new(None))); #[cfg(feature = "dynamic")] @@ -48,7 +48,7 @@ pub fn set_environment(dylib_path: String) -> Result<(), SurrealError> { let outcome: EnvironmentBuilder = ort::init_from(dylib_path); match outcome.commit() { Ok(env) => { - ORT_ENV.lock().unwrap().replace(env); + // ORT_ENV.lock().unwrap().replace(env); }, Err(e) => { return Err(SurrealError::new(e.to_string(), SurrealErrorStatus::Unknown));