diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..336e972 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0b78ab3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,157 @@ +name: test + +on: + workflow_dispatch: + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + cargo-fmt: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Ubuntu build dependencies + run: sudo apt update && sudo apt install -y clang cmake + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check Style + run: cargo fmt --check + + build-no-features: + needs: cargo-fmt + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Ubuntu build dependencies + if: matrix.platform == 'ubuntu-latest' + run: sudo apt update && sudo apt install -y clang cmake + - uses: dtolnay/rust-toolchain@stable + - name: Build + run: cargo build + + build-vulkan: + needs: build-no-features + strategy: + fail-fast: false + matrix: + platform: [macos-latest, ubuntu-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-latest' + run: sudo apt update && sudo apt install -y clang cmake + - name: Install vulkan sdk + uses: humbletim/install-vulkan-sdk@c2aa128094d42ba02959a660f03e0a4e012192f9 + with: + version: 1.3.250.1 + cache: true + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Build + run: cargo build --features vulkan + + + build-metal: + needs: build-no-features + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - uses: dtolnay/rust-toolchain@stable + - name: Build + run: cargo build --features metal + + build-cuda: + needs: build-no-features + strategy: + fail-fast: false + matrix: + platform: [ubuntu-22.04, windows-2022] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Install cuda toolkit + uses: Jimver/cuda-toolkit@v0.2.18 + - name: Install Windows dependencies + if: matrix.platform == 'windows-2022' + uses: ilammy/msvc-dev-cmd@v1 + - name: Ubuntu build dependencies + if: matrix.platform == 'ubuntu-22.04' + run: sudo apt update && sudo apt install -y clang cmake + - uses: dtolnay/rust-toolchain@stable + - name: Build + env: + CUDA_COMPUTE_CAP: "75" + run: cargo build --features cublas + + build-rocm: + needs: build-no-features + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Free Disk Space (Ubuntu) for Rocm + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + - name: Ubuntu build dependencies + run: sudo apt update && sudo apt install -y clang cmake ninja-build + - name: Install Rocm Toolkit + run: | + wget https://repo.radeon.com/amdgpu-install/6.1.2/ubuntu/jammy/amdgpu-install_6.1.60102-1_all.deb + sudo apt install ./amdgpu-install_6.1.60102-1_all.deb + sudo apt update + sudo apt install -y rocm + - uses: dtolnay/rust-toolchain@stable + - name: Build + env: + AMDGPU_TARGETS: "gfx1100" + run: cargo build --features hipblas + + build-sycl: + needs: build-no-features + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Ubuntu build dependencies + run: sudo apt update && sudo apt install -y clang cmake + - name: Install oneAPI Toolkit + run: | + wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ + | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \ + | sudo tee /etc/apt/sources.list.d/oneAPI.list + sudo apt update && sudo apt install -y intel-basekit intel-hpckit + - uses: dtolnay/rust-toolchain@stable + - name: Build + run: | + source /opt/intel/oneapi/setvars.sh + cargo build --features sycl + \ No newline at end of file diff --git a/.gitignore b/.gitignore index d01bd1a..d102392 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ +bin/act diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c6865ca --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sys/stable-diffusion.cpp"] + path = sys/stable-diffusion.cpp + url = https://github.com/leejet/stable-diffusion.cpp diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e24bf57 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +members = ["sys"] + +[package] +name = "diffusion-rs" +version = "0.1.0" +edition = "2021" +description = "Rust bindings for stable-diffusion.cpp" +license = "MIT" +documentation = "https://docs.rs/diffusion-rs" +repository = "https://github.com/newfla/diffusion-rs" + +[dependencies] +diffusion-rs-sys = { path = "sys", version = "0.1.0" } + +[features] +cublas = ["diffusion-rs-sys/cublas"] +hipblas = ["diffusion-rs-sys/hipblas"] +metal = ["diffusion-rs-sys/metal"] +vulkan = ["diffusion-rs-sys/vulkan"] +sycl = ["diffusion-rs-sys/sycl"] +flashattn = ["diffusion-rs-sys/flashattn"] \ No newline at end of file diff --git a/README.md b/README.md index 4376b8a..58e766e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ # diffusion-rs Rust bindings to https://github.com/leejet/stable-diffusion.cpp + +## Features Matrix +| | Windows | Mac | Linux | +| --- | :---: | :---: | :---: | +|vulkan| ✅️ | ✅️ | ✅️ | +|metal| ❌️ | ✅️ | ❌️ | +|cuda| ✅️ | ❌️ | ✅️ | +|rocm| ❔️ | ❌️ | ✅️ | +|sycl| ❔️ | ❌️ | ✅️ | + +❔️: Not tested, should be supported + +## Roadmap +1. ~~Ensure that the underline cpp library compiles on supported platforms~~ +2. Build an easy to use library with models download and async interface +3. Automatic library publishing on crates.io by gh actions +4. _Maybe_ prebuilt CLI app binaries \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ + diff --git a/sys/Cargo.toml b/sys/Cargo.toml new file mode 100644 index 0000000..d7f5c7c --- /dev/null +++ b/sys/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "diffusion-rs-sys" +version = "0.1.0" +edition = "2021" +description = "Rust bindings for stable-diffusion.cpp (FFI bindings)" +license = "MIT" +documentation = "https://docs.rs/diffusion-rs-sys" +repository = "https://github.com/newfla/diffusion-rs" +links = "stable-diffusion" +include = [ + "stable-diffusion.cpp/LICENSE", + "stable-diffusion.cpp/CMakeLists.txt", + "stable-diffusion.cpp/stable-diffusion.cpp", + "stable-diffusion.cpp/stable-diffusion.h", + "stable-diffusion.cpp/ggml/src/ggml.c", + "stable-diffusion.cpp/ggml/src/ggml-alloc.c", + "stable-diffusion.cpp/ggml/src/ggml-backend.c", + "stable-diffusion.cpp/ggml/src/ggml-cuda.cu", + "stable-diffusion.cpp/ggml/src/ggml-impl.h", + "stable-diffusion.cpp/ggml/src/ggml-metal.m", + "stable-diffusion.cpp/ggml/src/ggml-metal.metal", + "stable-diffusion.cpp/ggml/src/ggml-quants.h", + "stable-diffusion.cpp/ggml/src/ggml-quants.c", + "stable-diffusion.cpp/ggml/include/ggml.h", + "stable-diffusion.cpp/ggml/include/ggml-alloc.h", + "stable-diffusion.cpp/ggml/include/ggml-backend.h", + "stable-diffusion.cpp/ggml/include/ggml-backend-impl.h", + "stable-diffusion.cpp/ggml/include/ggml-cuda.h", + "stable-diffusion.cpp/ggml/include/ggml-metal.h", + "src/*.rs", + "build.rs", + "wrapper.h", +] + +[dependencies] + +[features] +cublas = [] +hipblas = [] +metal = [] +vulkan = [] +sycl = [] +flashattn = [] + +[build-dependencies] +cmake = "0.1.51" +bindgen = "0.70.1" +fs_extra = "1.3.0" \ No newline at end of file diff --git a/sys/build.rs b/sys/build.rs new file mode 100644 index 0000000..615b16f --- /dev/null +++ b/sys/build.rs @@ -0,0 +1,195 @@ +use std::{ + env, + fs::{create_dir_all, read_dir}, + path::PathBuf, +}; + +use cmake::Config; +use fs_extra::dir; + +// Heavily ispired by https://github.com/tazz4843/whisper-rs/blob/master/sys/build.rs + +fn main() { + // Link C++ standard library + let target = env::var("TARGET").unwrap(); + if let Some(cpp_stdlib) = get_cpp_link_stdlib(&target) { + println!("cargo:rustc-link-lib=dylib={}", cpp_stdlib); + } + + println!("cargo:rerun-if-changed=wrapper.h"); + + // Copy stable-diffusion code into the build script directory + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + let diffusion_root = out.join("stable-diffusion.cpp/"); + + if !diffusion_root.exists() { + create_dir_all(&diffusion_root).unwrap(); + dir::copy("./stable-diffusion.cpp", &out, &Default::default()).unwrap_or_else(|e| { + panic!( + "Failed to copy stable-diffusion sources into {}: {}", + diffusion_root.display(), + e + ) + }); + } + + // Bindgen + let bindings = bindgen::Builder::default().header("wrapper.h"); + + bindings + .clang_arg("-I./stable-diffusion.cpp") + .clang_arg("-I./stable-diffusion.cpp/ggml/include") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .unwrap() + .write_to_file(out.join("bindings.rs")) + .expect("Couldn't write bindings!"); + + // stop if we're on docs.rs + if env::var("DOCS_RS").is_ok() { + return; + } + + // Configure cmake for building + let mut config = Config::new(&diffusion_root); + + //Enable cmake feature flags + #[cfg(feature = "cublas")] + { + println!("cargo:rerun-if-env-changed=CUDA_PATH"); + println!("cargo:rustc-link-lib=cublas"); + println!("cargo:rustc-link-lib=cudart"); + println!("cargo:rustc-link-lib=cublasLt"); + println!("cargo:rustc-link-lib=cuda"); + + if target.contains("msvc") { + let cuda_path = PathBuf::from(env::var("CUDA_PATH").unwrap()).join("lib/x64"); + println!("cargo:rustc-link-search={}", cuda_path.display()); + } else { + println!("cargo:rustc-link-lib=culibos"); + println!("cargo:rustc-link-search=/usr/local/cuda/lib64"); + println!("cargo:rustc-link-search=/usr/local/cuda/lib64/stubs"); + println!("cargo:rustc-link-search=/opt/cuda/lib64"); + println!("cargo:rustc-link-search=/opt/cuda/lib64/stubs"); + } + + config.define("SD_CUBLAS", "ON"); + if let Ok(target) = env::var("CUDA_COMPUTE_CAP") { + config.define("CUDA_COMPUTE_CAP", target); + } + } + + #[cfg(feature = "hipblas")] + { + println!("cargo:rerun-if-env-changed=HIP_PATH"); + println!("cargo:rustc-link-lib=hipblas"); + println!("cargo:rustc-link-lib=rocblas"); + println!("cargo:rustc-link-lib=amdhip64"); + + config.generator("Ninja"); + config.define("CMAKE_C_COMPILER", "clang"); + config.define("CMAKE_CXX_COMPILER", "clang++"); + let hip_lib_path = if target.contains("msvc") { + let hip_path = env::var("HIP_PATH").expect("Missing HIP_PATH env variable"); + PathBuf::from(hip_path).join("lib") + } else { + let hip_path = match env::var("HIP_PATH") { + Ok(path) => PathBuf::from(path), + Err(_) => PathBuf::from("/opt/rocm"), + }; + hip_path.join("lib") + }; + println!("cargo:rustc-link-search={}", hip_lib_path.display()); + config.define("SD_HIPBLAS", "ON"); + if let Ok(target) = env::var("AMDGPU_TARGETS") { + config.define("AMDGPU_TARGETS", target); + } + } + + #[cfg(feature = "metal")] + { + println!("cargo:rustc-link-lib=framework=Foundation"); + println!("cargo:rustc-link-lib=framework=Metal"); + println!("cargo:rustc-link-lib=framework=MetalKit"); + config.define("SD_METAL", "ON"); + } + + #[cfg(feature = "vulkan")] + { + if target.contains("msvc") { + println!("cargo:rerun-if-env-changed=VULKAN_SDK"); + println!("cargo:rustc-link-lib=vulkan-1"); + let vulkan_path = match env::var("VULKAN_SDK") { + Ok(path) => PathBuf::from(path), + Err(_) => panic!( + "Please install Vulkan SDK and ensure that VULKAN_SDK env variable is set" + ), + }; + let vulkan_lib_path = vulkan_path.join("Lib"); + println!("cargo:rustc-link-search={}", vulkan_lib_path.display()); + } else { + println!("cargo:rustc-link-lib=vulkan"); + } + config.define("SD_VULKAN", "ON"); + } + + #[cfg(feature = "sycl")] + { + env::var("ONEAPI_ROOT").expect("Please load the oneAPi environment before building. See https://github.com/ggerganov/llama.cpp/blob/master/docs/backend/SYCL.md"); + + if target.contains("msvc") { + config.generator("Ninja"); + config.define("CMAKE_C_COMPILER", "cl"); + config.define("CMAKE_CXX_COMPILER", "icx"); + } else { + config.define("CMAKE_C_COMPILER", "icx"); + config.define("CMAKE_CXX_COMPILER", "icpx"); + } + config.define("SD_SYCL", "ON"); + } + + #[cfg(feature = "flashattn")] + { + config.define("SD_FLASH_ATTN", "ON"); + panic!("Broken in 2024/09/02 release!"); + } + + config + .profile("Release") + .define("SD_BUILD_SHARED_LIBS", "OFF") + .define("SD_BUILD_EXAMPLES", "OFF") + .very_verbose(true) + .pic(true); + + let destination = config.build(); + + add_link_search_path(&out.join("lib")).unwrap(); + add_link_search_path(&out.join("build")).unwrap(); + + println!("cargo:rustc-link-search=native={}", destination.display()); + println!("cargo:rustc-link-lib=static=stable-diffusion"); + println!("cargo:rustc-link-lib=static=ggml"); +} + +fn add_link_search_path(dir: &std::path::Path) -> std::io::Result<()> { + if dir.is_dir() { + println!("cargo:rustc-link-search={}", dir.display()); + for entry in read_dir(dir)? { + add_link_search_path(&entry?.path())?; + } + } + Ok(()) +} + +// From https://github.com/alexcrichton/cc-rs/blob/fba7feded71ee4f63cfe885673ead6d7b4f2f454/src/lib.rs#L2462 +fn get_cpp_link_stdlib(target: &str) -> Option<&'static str> { + if target.contains("msvc") { + None + } else if target.contains("apple") || target.contains("freebsd") || target.contains("openbsd") { + Some("c++") + } else if target.contains("android") { + Some("c++_shared") + } else { + Some("stdc++") + } +} diff --git a/sys/src/lib.rs b/sys/src/lib.rs new file mode 100644 index 0000000..a38a13a --- /dev/null +++ b/sys/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/sys/stable-diffusion.cpp b/sys/stable-diffusion.cpp new file mode 160000 index 0000000..e410aeb --- /dev/null +++ b/sys/stable-diffusion.cpp @@ -0,0 +1 @@ +Subproject commit e410aeb534413ad22b1674974a66b44e1e492e22 diff --git a/sys/wrapper.h b/sys/wrapper.h new file mode 100644 index 0000000..f112757 --- /dev/null +++ b/sys/wrapper.h @@ -0,0 +1 @@ +#include \ No newline at end of file