Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 8 additions & 18 deletions .claude/rules/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,10 @@ paths:

# Rust Standards

- Do not inline import paths unless necessary. Prefer to use `use` statements in Rust files instead of inline paths to imported modules. The exception would be `error.rs` type modules that handle lib-level error structs.
- Always use explicit lifetime variable names (do not use `'a` and such, use descriptive names like `'message` or similar)
- Always use explicit generic parameter names (never use single letter names like `T` for generics, prefix all of them with `T`, however). For example, use `TMessage` instead of `T`, etc.
- Do not use `pub(crate)` in Rust; in case of doubt, just make things public.
- In Rust, never ignore errors with `Err(_)`; always make sure you are matching an expected error variant instead.
- Never use `.expect`, or `.unwrap`. In Rust, if a function can fail, use a matching Result (can be from the anyhow crate) instead. In case of doubt on this, ask. Allow `.expect` in mutex lock poison checks, or when integrating CPP libraries into Rust.
- Always make sure mutex locks are held for the shortest possible time.
- Always specify Rust dependencies in root Cargo.toml, then use workspace versions of packages in workspace members.
- In Rust, when implementing a `new` method in a struct, prefer to use a struct with a parameter list instead of multiple function arguments. It should be easier to maintain.
- Always check the project with Clippy.
- Always format the code with `cargo fmt`.
- Each file must contain at most a single struct, or single enum. For readability split those into multiple modules. You can still keep multiple private function helpers.
- Never use Result<> as a function argument.
- Never forward Result in enums if you can instead create a targeted error enum. It is always better to signal the specific issue, so it can be handled downstream.
- Always use descriptive parameter names (never use single letter names for generics)
- Each file must contain at most a single struct, or single enum, or a single public function (at most one of any of those).
Comment thread
mcharytoniuk marked this conversation as resolved.
- Each file must contain at most a single public item. You can still keep multiple private function helpers. Files need to be named after their public item.
- Always destructure structs in arguments if possible.

# Code Style
Expand All @@ -29,8 +19,8 @@ Imports/uses must not be mixed with other kinds of rust syntax.
Each file needs to follow this order:
1. `pub mod`/`mod` exports
2. vendor crate `use`
2. project crate `use`
3. local crate `use`
4. private function helpers
5. private struct helpers
6. single public export
3. project crate `use`
4. local crate `use`
5. private function helpers
6. private struct helpers
7. single public export
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ members = [
"llama-cpp-bindings",
"llama-cpp-bindings-tests",
"llama-cpp-error-recorder",
"llama-cpp-ggml-sys",
"llama-cpp-gbnf-sys",
"llama-cpp-gbnf",
"llama-cpp-log-decoder",
"llama-cpp-test-harness",
"llama-cpp-test-harness-macros",
Expand Down Expand Up @@ -35,6 +38,9 @@ llama-cpp-bindings-build = { path = "llama-cpp-bindings-build", version = "=0.9.
llama-cpp-bindings-sys = { path = "llama-cpp-bindings-sys", version = "=0.9.0" }
llama-cpp-bindings-types = { path = "llama-cpp-bindings-types", version = "=0.9.0" }
llama-cpp-error-recorder = { path = "llama-cpp-error-recorder", version = "=0.9.0" }
llama-cpp-gbnf = { path = "llama-cpp-gbnf", version = "=0.9.0" }
llama-cpp-gbnf-sys = { path = "llama-cpp-gbnf-sys", version = "=0.9.0" }
llama-cpp-ggml-sys = { path = "llama-cpp-ggml-sys", version = "=0.9.0" }
llama-cpp-log-decoder = { path = "llama-cpp-log-decoder", version = "=0.9.0" }
llama-cpp-test-harness = { path = "llama-cpp-test-harness", version = "=0.9.0" }
llama-cpp-test-harness-macros = { path = "llama-cpp-test-harness-macros", version = "=0.9.0" }
Expand Down
14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
TEST_DEVICE ?=

DEVICE_FEATURE = $(if $(TEST_DEVICE),--features $(TEST_DEVICE),)
FAULT_INJECTION_FEATURE = --features llama-cpp-gbnf/fault-injection

node_modules: package-lock.json
npm ci
Expand All @@ -9,9 +10,14 @@ node_modules: package-lock.json
package-lock.json: package.json
npm install --package-lock-only

.PHONY: clean
clean:
cargo clean
rm -rf node_modules

.PHONY: clean.cmake
clean.cmake:
rm -rf target/llama-cpp-cmake-build
rm -rf target/*/llama-cpp-cmake-build

.PHONY: clippy
clippy:
Expand All @@ -20,14 +26,16 @@ clippy:
.PHONY: coverage
coverage: node_modules
cargo llvm-cov clean --workspace
cargo llvm-cov --no-report --no-fail-fast --workspace $(DEVICE_FEATURE)
cargo llvm-cov --no-report --no-fail-fast --workspace $(FAULT_INJECTION_FEATURE) $(DEVICE_FEATURE)
cargo llvm-cov report --json --output-path target/llvm-cov.json
cargo llvm-cov report --lcov --output-path target/lcov.info
cargo llvm-cov report
npx rust-coverage-check target/llvm-cov.json \
--workspace-root $(CURDIR) \
--gated llama-cpp-bindings=98 \
--gated llama-cpp-error-recorder=100 \
--gated llama-cpp-gbnf=100 \
--gated llama-cpp-gbnf-sys=100 \
--gated llama-cpp-log-decoder=100 \
--gated llama-cpp-bindings-types=100 \
--gated llama-cpp-test-harness=99 \
Expand Down Expand Up @@ -83,4 +91,4 @@ test.llms: clippy test.harness test.unit

.PHONY: test.unit
test.unit: clippy
cargo test -p llama-cpp-log-decoder -p llama-cpp-bindings $(DEVICE_FEATURE)
cargo test -p llama-cpp-gbnf -p llama-cpp-gbnf-sys -p llama-cpp-log-decoder -p llama-cpp-bindings $(DEVICE_FEATURE)
86 changes: 86 additions & 0 deletions llama-cpp-bindings-build/src/build_gbnf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::env;
use std::path::Path;
use std::path::PathBuf;

use crate::target_os::TargetOs;

const LLAMA_BINDINGS_SYS_RELATIVE_PATH: &str = "../llama-cpp-bindings-sys";
const LLAMA_SUBMODULE_RELATIVE_PATH: &str = "../llama-cpp-bindings-sys/llama.cpp";

pub fn build_gbnf() {
let manifest_dir =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var is required");
let target_triple = env::var("TARGET").expect("TARGET env var is required in build scripts");
let target_os = TargetOs::from_target_triple(&target_triple)
.unwrap_or_else(|error| panic!("Failed to parse target OS: {error}"));

let manifest = PathBuf::from(&manifest_dir);
let llama_src = manifest.join(LLAMA_SUBMODULE_RELATIVE_PATH);
let bindings_sys = manifest.join(LLAMA_BINDINGS_SYS_RELATIVE_PATH);
let grammar_source_dir = llama_src.join("src");

assert!(
grammar_source_dir.join("llama-grammar.h").is_file(),
"grammar headers not found under {}; ensure the llama.cpp submodule is checked out",
grammar_source_dir.display()
);

register_rebuild_triggers(&manifest);

compile_wrapper(
&manifest,
&llama_src,
&bindings_sys,
&grammar_source_dir,
&target_os,
);
}

fn compile_wrapper(
manifest: &Path,
llama_src: &Path,
bindings_sys: &Path,
grammar_source_dir: &Path,
target_os: &TargetOs,
) {
let fault_injection = env::var_os("CARGO_FEATURE_FAULT_INJECTION").is_some();

let mut build = cc::Build::new();

build
.cpp(true)
.warnings(false)
.include(manifest)
.include(bindings_sys)
.include(llama_src.join("include"))
.include(grammar_source_dir)
.include(llama_src.join("ggml/include"))
.flag_if_supported("-std=c++17")
.pic(true)
.file(manifest.join("wrapper_gbnf.cpp"));

if fault_injection {
build.define("GBNF_FAULT_INJECTION", None);
build.file(manifest.join("alloc_fault.cpp"));
}

if target_os.is_msvc() {
build.flag("/std:c++17");
build.flag("/EHsc");
}

build.compile("llama_cpp_gbnf_wrapper");
}

fn register_rebuild_triggers(manifest: &Path) {
println!("cargo:rerun-if-changed=build.rs");

for source in [
"wrapper_gbnf.cpp",
"wrapper_gbnf.h",
"alloc_fault.cpp",
"alloc_fault.h",
] {
println!("cargo:rerun-if-changed={}", manifest.join(source).display());
}
}
118 changes: 118 additions & 0 deletions llama-cpp-bindings-build/src/build_ggml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::env;
use std::path::Path;
use std::path::PathBuf;

use cmake::Config;

use crate::cmake_common;
use crate::ggml_cmake_options;
use crate::target_os::TargetOs;

const GGML_SUBMODULE_RELATIVE_PATH: &str = "../llama-cpp-bindings-sys/llama.cpp/ggml";

pub fn build_ggml() {
let manifest_dir =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var is required");
let target_triple = env::var("TARGET").expect("TARGET env var is required in build scripts");
let target_os = TargetOs::from_target_triple(&target_triple)
.unwrap_or_else(|error| panic!("Failed to parse target OS: {error}"));
let profile = env::var("LLAMA_LIB_PROFILE").unwrap_or_else(|_| "Release".to_string());

let wrapper_cmake_dir = PathBuf::from(&manifest_dir);
let ggml_src = wrapper_cmake_dir.join(GGML_SUBMODULE_RELATIVE_PATH);

assert!(
ggml_src.join("CMakeLists.txt").is_file(),
"ggml source not found at {}; ensure the llama.cpp submodule is checked out",
ggml_src.display()
);

register_rebuild_triggers(&ggml_src);

let install_dir = configure_and_install_ggml(
&wrapper_cmake_dir,
&ggml_src,
&target_triple,
&target_os,
&profile,
);

emit_metadata(&install_dir);
}

fn configure_and_install_ggml(
wrapper_cmake_dir: &Path,
ggml_src: &Path,
target_triple: &str,
target_os: &TargetOs,
profile: &str,
) -> PathBuf {
let mut config = Config::new(wrapper_cmake_dir);

let ggml_source_dir = ggml_src
.to_str()
.expect("ggml source path must be valid UTF-8")
.replace('\\', "/");
config.define("GGML_SOURCE_DIR", &ggml_source_dir);
config.define("GGML_BUILD_TESTS", "OFF");
config.define("GGML_BUILD_EXAMPLES", "OFF");
config.cflag("-w");
config.cxxflag("-w");

cmake_common::pass_cmake_env_vars(&mut config);
cmake_common::configure_compiler_launchers(&mut config);
cmake_common::configure_shared_libs(&mut config, false);
Comment thread
mcharytoniuk marked this conversation as resolved.
ggml_cmake_options::configure_cpu_features(&mut config, target_triple);
ggml_cmake_options::configure_gpu_backends(&mut config, target_os);
ggml_cmake_options::configure_openmp(&mut config, target_os);

if let TargetOs::Apple(_) = target_os {
config.define("GGML_BLAS", "OFF");
cmake_common::override_archive_commands_for_apple_ar(&mut config);
}

config
.profile(profile)
.very_verbose(env::var("CMAKE_VERBOSE").is_ok())
.always_configure(false);

config.build()
}

fn emit_metadata(install_dir: &Path) {
let lib_dir = resolve_lib_dir(install_dir);

println!("cargo:root={}", install_dir.display());
println!("cargo:include={}", install_dir.join("include").display());
println!("cargo:lib={}", lib_dir.display());
println!(
"cargo:cmake={}",
lib_dir.join("cmake").join("ggml").display()
);
}

fn resolve_lib_dir(install_dir: &Path) -> PathBuf {
let lib64 = install_dir.join("lib64");

if lib64.is_dir() {
lib64
} else {
install_dir.join("lib")
}
}

fn register_rebuild_triggers(ggml_src: &Path) {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=LLAMA_LIB_PROFILE");
println!("cargo:rerun-if-env-changed=LLAMA_DISABLE_CCACHE");
println!("cargo:rerun-if-env-changed=CMAKE_VERBOSE");
println!(
"cargo:rerun-if-changed={}",
ggml_src.join("CMakeLists.txt").display()
);
println!("cargo:rerun-if-changed={}", ggml_src.join("src").display());
println!(
"cargo:rerun-if-changed={}",
ggml_src.join("include").display()
);
}
Loading
Loading