Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
220 changes: 220 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
name: Build and Test

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:

env:
CARGO_TERM_COLOR: always

jobs:
build:
strategy:
fail-fast: false
matrix:
platform:
- os: ubuntu-22.04
rust_target: x86_64-unknown-linux-gnu
- os: macos-latest
rust_target: aarch64-apple-darwin
# Windows disabled temporarily - needs import library generation work
# - os: windows-latest
# rust_target: x86_64-pc-windows-msvc

runs-on: ${{ matrix.platform.os }}

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install dependencies (Ubuntu)
if: matrix.platform.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform.rust_target }}

- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri -> target

- name: Download llama.cpp libraries (Linux)
if: matrix.platform.os == 'ubuntu-22.04'
run: |
wget https://github.com/ggml-org/llama.cpp/releases/download/b7423/llama-b7423-bin-ubuntu-x64.tar.gz
tar -xzf llama-b7423-bin-ubuntu-x64.tar.gz
mkdir -p src-tauri/libs/linux-x64
cp -P llama-b7423/*.so* src-tauri/libs/linux-x64/ 2>/dev/null || true
ls -la src-tauri/libs/linux-x64/

- name: Download llama.cpp libraries (macOS)
if: matrix.platform.os == 'macos-latest'
run: |
wget https://github.com/ggml-org/llama.cpp/releases/download/b7423/llama-b7423-bin-macos-arm64.tar.gz
tar -xzf llama-b7423-bin-macos-arm64.tar.gz
mkdir -p src-tauri/libs/darwin-arm64
cp -P llama-b7423/*.dylib src-tauri/libs/darwin-arm64/
ls -la src-tauri/libs/darwin-arm64/

- name: Download llama.cpp libraries (Windows)
if: matrix.platform.os == 'windows-latest'
shell: powershell
run: |
Invoke-WebRequest -Uri "https://github.com/ggml-org/llama.cpp/releases/download/b7423/llama-b7423-bin-win-vulkan-x64.zip" -OutFile "llama-libs.zip"
Expand-Archive -Path "llama-libs.zip" -DestinationPath "llama-extracted"
Get-ChildItem -Recurse llama-extracted | Select-Object FullName
New-Item -ItemType Directory -Force -Path src-tauri/libs/windows-x64-vulkan
Get-ChildItem -Path "llama-extracted" -Recurse -Filter "*.dll" | Copy-Item -Destination src-tauri/libs/windows-x64-vulkan/
Get-ChildItem -Path "llama-extracted" -Recurse -Filter "*.lib" | Copy-Item -Destination src-tauri/libs/windows-x64-vulkan/
Get-ChildItem src-tauri/libs/windows-x64-vulkan/

- name: Generate import libraries from DLLs (Windows)
if: matrix.platform.os == 'windows-latest'
shell: powershell
run: |
$libDir = "src-tauri\libs\windows-x64-vulkan"

# Find Visual Studio installation and setup environment
$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$vsPath = & $vsWhere -latest -property installationPath
$vcvarsall = Join-Path $vsPath "VC\Auxiliary\Build\vcvars64.bat"

foreach ($name in @("llama", "ggml", "mtmd")) {
$dllPath = Join-Path $libDir "$name.dll"
if (Test-Path $dllPath) {
Write-Host "Generating import library for $name.dll"

# Run dumpbin and lib in a VS Developer environment
$defFile = Join-Path $libDir "$name.def"
$libFile = Join-Path $libDir "$name.lib"

# Use cmd to run vcvars and then our commands
cmd /c "`"$vcvarsall`" && dumpbin /exports `"$dllPath`" > `"$libDir\${name}_exports.txt`""

# Parse exports and create .def file
$exports = Get-Content "$libDir\${name}_exports.txt"
$inExports = $false
$defContent = "LIBRARY $name`r`nEXPORTS`r`n"

foreach ($line in $exports) {
if ($line -match "^\s+ordinal\s+hint\s+RVA\s+name") {
$inExports = $true
continue
}
if ($inExports -and $line -match "^\s+\d+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+(\S+)") {
$defContent += " $($Matches[1])`r`n"
}
if ($inExports -and $line -match "^\s*$") {
break
}
}

Set-Content -Path $defFile -Value $defContent

# Generate .lib from .def
cmd /c "`"$vcvarsall`" && lib /def:`"$defFile`" /machine:x64 /out:`"$libFile`""
}
}

Get-ChildItem "$libDir\*.lib"

- name: Set Windows backend environment
if: matrix.platform.os == 'windows-latest'
shell: powershell
run: echo "LLAMA_BACKEND=vulkan" >> $env:GITHUB_ENV

- name: Install frontend dependencies
run: npm ci

- name: Run Rust tests (Linux)
if: matrix.platform.os == 'ubuntu-22.04'
working-directory: src-tauri
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/src-tauri/libs/linux-x64
run: cargo test --verbose

- name: Run Rust tests (macOS)
if: matrix.platform.os == 'macos-latest'
working-directory: src-tauri
env:
DYLD_LIBRARY_PATH: ${{ github.workspace }}/src-tauri/libs/darwin-arm64
run: cargo test --verbose

- name: Run Rust tests (Windows)
if: matrix.platform.os == 'windows-latest'
working-directory: src-tauri
shell: powershell
run: |
$env:PATH = "${{ github.workspace }}\src-tauri\libs\windows-x64-vulkan;$env:PATH"
cargo test --verbose

- name: Build frontend
run: npm run build

- name: Copy libraries for bundling (Linux)
if: matrix.platform.os == 'ubuntu-22.04'
run: |
mkdir -p src-tauri/resources
cp -P src-tauri/libs/linux-x64/* src-tauri/resources/

- name: Copy libraries for bundling (Windows)
if: matrix.platform.os == 'windows-latest'
shell: powershell
run: |
New-Item -ItemType Directory -Force -Path src-tauri/resources
Copy-Item -Path src-tauri/libs/windows-x64-vulkan/* -Destination src-tauri/resources/

- name: Build Tauri application
run: npm run tauri build

- name: Upload artifacts (Linux)
if: matrix.platform.os == 'ubuntu-22.04'
uses: actions/upload-artifact@v4
with:
name: linux-build
path: |
src-tauri/target/release/bundle/deb/*.deb
src-tauri/target/release/bundle/appimage/*.AppImage
if-no-files-found: warn

- name: Upload artifacts (macOS)
if: matrix.platform.os == 'macos-latest'
uses: actions/upload-artifact@v4
with:
name: macos-build
path: |
src-tauri/target/release/bundle/dmg/*.dmg
src-tauri/target/release/bundle/macos/*.app
if-no-files-found: warn

- name: Upload artifacts (Windows)
if: matrix.platform.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: windows-build
path: |
src-tauri/target/release/bundle/msi/*.msi
src-tauri/target/release/bundle/nsis/*.exe
if-no-files-found: warn
7 changes: 6 additions & 1 deletion src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ fn main() {
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");

#[cfg(target_os = "linux")]
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir.display());
{
// Use $ORIGIN to reference the directory containing the executable
// Libraries will be bundled in the same directory as the binary
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir.display());
}

// Re-run build script if libraries change
println!("cargo:rerun-if-changed=libs/{}", lib_subdir);
Expand Down
45 changes: 44 additions & 1 deletion src-tauri/src/llama_inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,69 @@ pub struct MtmdInputText {

#[repr(C)]
pub struct LlamaModelParams {
// Simplified - add fields as needed
// Pointers come first
devices: *mut c_void,
tensor_buft_overrides: *const c_void,
// Then the fields we care about
n_gpu_layers: c_int,
split_mode: c_int,
main_gpu: c_int,
tensor_split: *const c_float,
progress_callback: *const c_void,
progress_callback_user_data: *mut c_void,
kv_overrides: *const c_void,
// Booleans at the end
vocab_only: bool,
use_mmap: bool,
use_mlock: bool,
check_tensors: bool,
// Padding to reach 72 bytes
_padding: [u8; 4],
}

#[repr(C)]
pub struct LlamaContextParams {
n_ctx: u32,
n_batch: u32,
n_ubatch: u32,
n_seq_max: u32,
n_threads: c_int,
n_threads_batch: c_int,
rope_scaling_type: c_int,
pooling_type: c_int,
attention_type: c_int,
flash_attn_type: c_int,
rope_freq_base: c_float,
rope_freq_scale: c_float,
yarn_ext_factor: c_float,
yarn_attn_factor: c_float,
yarn_beta_fast: c_float,
yarn_beta_slow: c_float,
yarn_orig_ctx: u32,
defrag_thold: c_float,
cb_eval: *const c_void,
cb_eval_user_data: *mut c_void,
type_k: c_int,
type_v: c_int,
logits_all: bool,
embeddings: bool,
offload_kqv: bool,
flash_attn: bool,
no_perf: bool,
abort_callback: *const c_void,
abort_callback_data: *mut c_void,
}

#[repr(C)]
pub struct MtmdContextParams {
use_gpu: bool,
print_timings: bool,
n_threads: c_int,
image_marker: *const c_char,
media_marker: *const c_char,
flash_attn_type: c_int,
image_min_tokens: c_int,
image_max_tokens: c_int,
}

pub type LlamaToken = i32;
Expand Down
5 changes: 3 additions & 2 deletions src-tauri/src/model_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,9 @@ mod tests {
let result = ModelManager::get_models_directory();
assert!(result.is_ok());
let path = result.unwrap();
assert!(path.to_string_lossy().contains("canvas"));
assert!(path.to_string_lossy().contains("models"));
let path_lower = path.to_string_lossy().to_lowercase();
assert!(path_lower.contains("canvas"), "Path should contain 'canvas': {}", path.display());
assert!(path_lower.contains("models"), "Path should contain 'models': {}", path.display());
}

#[tokio::test]
Expand Down
13 changes: 13 additions & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@
"libs/darwin-arm64/libmtmd.0.dylib",
"libs/darwin-arm64/libggml.0.dylib"
]
},
"linux": {
"deb": {
"depends": []
}
},
"windows": {
"wix": {
"language": "en-US"
},
"nsis": {
"languages": ["English"]
}
}
}
}
10 changes: 10 additions & 0 deletions src-tauri/tests/contextual_crop_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ fn create_solid_color_image(width: u32, height: u32, r: u8, g: u8, b: u8) -> Vec

#[test]
fn test_multi_turn_conversation_same_image() {
if std::env::var("CI").is_ok() {
println!("Skipping test in CI - requires SmolVLM model files");
return;
}

let (model_path, mmproj_path) = get_model_paths();

println!("Loading model...");
Expand Down Expand Up @@ -85,6 +90,11 @@ fn test_multi_turn_conversation_same_image() {

#[test]
fn test_contextual_crop_workflow() {
if std::env::var("CI").is_ok() {
println!("Skipping test in CI - requires SmolVLM model files");
return;
}

let (model_path, mmproj_path) = get_model_paths();

println!("Loading model for contextual crop test...");
Expand Down
10 changes: 10 additions & 0 deletions src-tauri/tests/inference_integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use baseweightcanvas_lib::inference_engine::InferenceEngine;

#[test]
fn test_model_loading() {
if std::env::var("CI").is_ok() {
println!("Skipping test in CI - requires SmolVLM model files");
return;
}

let model_path = "/home/bowserj/.local/share/canvas/models/smolvlm2-2.2b-instruct/SmolVLM2-2.2B-Instruct-Q4_K_M.gguf";
let mmproj_path = "/home/bowserj/.local/share/canvas/models/smolvlm2-2.2b-instruct/mmproj-SmolVLM2-2.2B-Instruct-f16.gguf";

Expand All @@ -14,6 +19,11 @@ fn test_model_loading() {

#[test]
fn test_inference_with_blue_image() {
if std::env::var("CI").is_ok() {
println!("Skipping test in CI - requires SmolVLM model files");
return;
}

let model_path = "/home/bowserj/.local/share/canvas/models/smolvlm2-2.2b-instruct/SmolVLM2-2.2B-Instruct-Q4_K_M.gguf";
let mmproj_path = "/home/bowserj/.local/share/canvas/models/smolvlm2-2.2b-instruct/mmproj-SmolVLM2-2.2B-Instruct-f16.gguf";

Expand Down
Loading