From f325a9d8e32b1aec4441b374fa70c1d0c037663a Mon Sep 17 00:00:00 2001 From: jh-block Date: Thu, 7 May 2026 09:12:18 +0200 Subject: [PATCH] Add Linux Vulkan support for local inference (#9038) Signed-off-by: jh-block --- .github/workflows/build-cli.yml | 6 ++- .github/workflows/bundle-desktop-linux.yml | 7 ++- .github/workflows/bundle-goose2.yml | 7 ++- BUILDING_LINUX.md | 10 ++-- Cross.toml | 4 ++ Justfile | 3 +- crates/goose-cli/Cargo.toml | 1 + crates/goose-server/Cargo.toml | 1 + crates/goose/Cargo.toml | 1 + crates/goose/src/providers/local_inference.rs | 52 ++++++++++++++++--- ui/desktop/forge.config.ts | 2 + ui/goose-binary/README.md | 5 ++ ui/goose2/src-tauri/tauri.conf.json | 8 +++ ui/scripts/publish.sh | 10 ++-- ui/sdk/scripts/build-native.ts | 14 +++-- 15 files changed, 104 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 3b3923ff43..d29e2afaf4 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -117,7 +117,11 @@ jobs: cross --version export CC="${{ matrix.cc || ''}}" - cross build --release --target ${TARGET} -p goose-cli + FEATURE_ARGS=() + if [[ "${TARGET}" == *-unknown-linux-gnu ]]; then + FEATURE_ARGS=(--features vulkan) + fi + cross build --release --target ${TARGET} -p goose-cli "${FEATURE_ARGS[@]}" - name: Setup Rust (Windows) if: matrix.os == 'windows' diff --git a/.github/workflows/bundle-desktop-linux.yml b/.github/workflows/bundle-desktop-linux.yml index 6203de8ba6..1c4c66d5af 100644 --- a/.github/workflows/bundle-desktop-linux.yml +++ b/.github/workflows/bundle-desktop-linux.yml @@ -79,7 +79,10 @@ jobs: protobuf-compiler \ flatpak \ flatpak-builder \ - elfutils + elfutils \ + libvulkan-dev \ + libvulkan1 \ + glslc - name: Setup Flatpak Runtimes run: | @@ -110,7 +113,7 @@ jobs: source ./bin/activate-hermit export TARGET="x86_64-unknown-linux-gnu" rustup target add "${TARGET}" - cross build --release --target ${TARGET} -p goose-server + cross build --release --target ${TARGET} -p goose-server --features vulkan - name: Copy binaries into Electron folder run: | diff --git a/.github/workflows/bundle-goose2.yml b/.github/workflows/bundle-goose2.yml index 2cc37bc4f3..b363ebf1a2 100644 --- a/.github/workflows/bundle-goose2.yml +++ b/.github/workflows/bundle-goose2.yml @@ -444,7 +444,10 @@ jobs: libappindicator3-dev \ librsvg2-dev \ patchelf \ - protobuf-compiler + protobuf-compiler \ + libvulkan-dev \ + libvulkan1 \ + glslc - name: Update versions if: inputs.version != '' @@ -489,7 +492,7 @@ jobs: if: inputs.cli-run-id == '' run: | source ./bin/activate-hermit - cargo build --release -p goose-cli --bin goose + cargo build --release -p goose-cli --bin goose --features vulkan - name: Prepare goose binary with target triple if: inputs.cli-run-id == '' diff --git a/BUILDING_LINUX.md b/BUILDING_LINUX.md index 6fc0e210db..4e1b9c4114 100644 --- a/BUILDING_LINUX.md +++ b/BUILDING_LINUX.md @@ -9,22 +9,22 @@ This guide covers building the goose Desktop application from source on various **Debian/Ubuntu:** ```bash sudo apt update -sudo apt install -y dpkg fakeroot build-essential clang libxcb1-dev libxcb-util-dev protobuf-compiler +sudo apt install -y dpkg fakeroot build-essential clang libxcb1-dev libxcb-util-dev protobuf-compiler libvulkan-dev libvulkan1 glslc ``` **Arch/Manjaro:** ```bash -sudo pacman -S --needed dpkg fakeroot base-devel +sudo pacman -S --needed dpkg fakeroot base-devel vulkan-headers vulkan-icd-loader shaderc ``` **Fedora/RHEL/CentOS:** ```bash -sudo dnf install dpkg-dev fakeroot gcc gcc-c++ make libxcb-devel +sudo dnf install dpkg-dev fakeroot gcc gcc-c++ make libxcb-devel vulkan-headers vulkan-loader glslc ``` **openSUSE:** ```bash -sudo zypper install dpkg fakeroot gcc gcc-c++ make +sudo zypper install dpkg fakeroot gcc gcc-c++ make vulkan-headers vulkan-loader glslc ``` **android / termux:** @@ -125,7 +125,7 @@ sudo dpkg -i out/make/deb/x64/goose_*.deb ### Common Issues #### Missing System Dependencies -If you see errors about missing `dpkg` or `fakeroot`: +If you see errors about missing `dpkg`, `fakeroot`, Vulkan headers, or `glslc`: ```bash # Install the missing packages for your distribution (see Prerequisites above) ``` diff --git a/Cross.toml b/Cross.toml index eb9cc1906b..f7457d6510 100644 --- a/Cross.toml +++ b/Cross.toml @@ -15,6 +15,8 @@ pre-build = [ libssl-dev:arm64 \ libdbus-1-dev:arm64 \ libxcb1-dev:arm64 \ + libvulkan-dev:arm64 \ + glslc \ gcc-10 """ ] @@ -30,6 +32,8 @@ pre-build = [ libssl-dev \ libdbus-1-dev \ libxcb1-dev \ + libvulkan-dev \ + glslc \ gcc-10 """ ] diff --git a/Justfile b/Justfile index 3e7a220929..bb4996370c 100644 --- a/Justfile +++ b/Justfile @@ -394,6 +394,7 @@ release-notes old: ### s = file separator based on OS s := if os() == "windows" { "\\" } else { "/" } +linux_vulkan_features := if os() == "linux" { "--features vulkan" } else { "" } ### testing/debugging os: @@ -486,6 +487,6 @@ record-mcp-tests: build-test-tools git add crates/goose/tests/mcp_replays/ bundle-goose2: - cargo build --release --package goose-cli --bin goose + cargo build --release --package goose-cli --bin goose {{linux_vulkan_features}} cp target/release/goose target/release/goose-$(rustc --print host-tuple) @just goose2::bundle diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index c5576f817d..8e9a07b30d 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -74,6 +74,7 @@ code-mode = ["goose/code-mode"] local-inference = ["goose/local-inference"] aws-providers = ["goose/aws-providers"] cuda = ["goose/cuda", "local-inference"] +vulkan = ["goose/vulkan", "local-inference"] telemetry = ["goose/telemetry"] otel = ["goose/otel"] # disables the update command diff --git a/crates/goose-server/Cargo.toml b/crates/goose-server/Cargo.toml index 17b5c10e6a..e55474052e 100644 --- a/crates/goose-server/Cargo.toml +++ b/crates/goose-server/Cargo.toml @@ -17,6 +17,7 @@ code-mode = ["goose/code-mode"] local-inference = ["goose/local-inference"] aws-providers = ["goose/aws-providers"] cuda = ["goose/cuda", "local-inference"] +vulkan = ["goose/vulkan", "local-inference"] telemetry = ["goose/telemetry"] otel = ["goose/otel"] rustls-tls = [ diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml index 7dabd0f8f2..f26a262327 100644 --- a/crates/goose/Cargo.toml +++ b/crates/goose/Cargo.toml @@ -37,6 +37,7 @@ aws-providers = [ "dep:aws-sdk-sagemakerruntime", ] cuda = ["local-inference", "candle-core/cuda", "candle-nn/cuda", "llama-cpp-2/cuda"] +vulkan = ["local-inference", "llama-cpp-2/vulkan"] rustls-tls = [ "reqwest/rustls", "rmcp/reqwest", diff --git a/crates/goose/src/providers/local_inference.rs b/crates/goose/src/providers/local_inference.rs index 2f72616d72..75540ef6a8 100644 --- a/crates/goose/src/providers/local_inference.rs +++ b/crates/goose/src/providers/local_inference.rs @@ -86,6 +86,7 @@ impl InferenceRuntime { } }; llama_cpp_2::send_logs_to_tracing(LogOptions::default()); + log_inference_backend_devices(); let runtime = Arc::new(Self { models: StdMutex::new(HashMap::new()), backend, @@ -156,20 +157,55 @@ pub fn resolve_model_path(model_id: &str) -> Option { None } +fn is_accelerator_device(device_type: LlamaBackendDeviceType) -> bool { + matches!( + device_type, + LlamaBackendDeviceType::Gpu + | LlamaBackendDeviceType::IntegratedGpu + | LlamaBackendDeviceType::Accelerator + ) +} + +fn is_non_cpu_device(device_type: LlamaBackendDeviceType) -> bool { + !matches!(device_type, LlamaBackendDeviceType::Cpu) +} + +fn log_inference_backend_devices() { + let devices = list_llama_ggml_backend_devices(); + let non_cpu_devices: Vec<_> = devices + .iter() + .filter(|device| is_non_cpu_device(device.device_type)) + .collect(); + + if non_cpu_devices.is_empty() { + tracing::info!( + device_count = devices.len(), + "No non-CPU llama.cpp backend devices detected for local inference" + ); + return; + } + + for device in non_cpu_devices { + tracing::info!( + index = device.index, + backend = %device.backend, + name = %device.name, + description = %device.description, + device_type = ?device.device_type, + memory_total_bytes = device.memory_total as u64, + memory_free_bytes = device.memory_free as u64, + "Non-CPU llama.cpp backend device detected for local inference" + ); + } +} + pub fn available_inference_memory_bytes(runtime: &InferenceRuntime) -> u64 { let _ = &runtime.backend; let devices = list_llama_ggml_backend_devices(); let accel_memory = devices .iter() - .filter(|d| { - matches!( - d.device_type, - LlamaBackendDeviceType::Gpu - | LlamaBackendDeviceType::IntegratedGpu - | LlamaBackendDeviceType::Accelerator - ) - }) + .filter(|d| is_accelerator_device(d.device_type)) .map(|d| d.memory_free as u64) .max() .unwrap_or(0); diff --git a/ui/desktop/forge.config.ts b/ui/desktop/forge.config.ts index fe71220454..d61ccd9b47 100644 --- a/ui/desktop/forge.config.ts +++ b/ui/desktop/forge.config.ts @@ -92,6 +92,7 @@ module.exports = { categories: ['Development'], desktopTemplate: './forge.deb.desktop', options: { + depends: ['libvulkan1'], icon: 'src/images/icon.png', prefix: '/opt', }, @@ -107,6 +108,7 @@ module.exports = { categories: ['Development'], desktopTemplate: './forge.rpm.desktop', options: { + requires: ['vulkan-loader'], icon: 'src/images/icon.png', prefix: '/opt', fpm: ['--rpm-rpmbuild-define', '_build_id_links none'], diff --git a/ui/goose-binary/README.md b/ui/goose-binary/README.md index 6a76d77dad..852fdad914 100644 --- a/ui/goose-binary/README.md +++ b/ui/goose-binary/README.md @@ -32,6 +32,11 @@ npx tsx scripts/build-native.ts darwin-arm64 linux-x64 The built binaries are placed into `ui/goose-binary/goose-binary-{platform}/bin/`. These directories are git-ignored. +Linux native binaries are built with local inference Vulkan support. Linux build +hosts need Vulkan headers and `glslc`; Linux runtime hosts need the Vulkan loader +package, such as `libvulkan1` on Debian/Ubuntu or `vulkan-loader` on RPM-based +distributions. + ## Publishing Publishing is handled by GitHub Actions. See `.github/workflows/publish-npm.yml`. diff --git a/ui/goose2/src-tauri/tauri.conf.json b/ui/goose2/src-tauri/tauri.conf.json index 7697524324..a5abbfd893 100644 --- a/ui/goose2/src-tauri/tauri.conf.json +++ b/ui/goose2/src-tauri/tauri.conf.json @@ -50,6 +50,14 @@ "../distro": "distro" }, "externalBin": ["../../../target/release/goose"], + "linux": { + "deb": { + "depends": ["libvulkan1"] + }, + "rpm": { + "depends": ["vulkan-loader"] + } + }, "macOS": { "entitlements": "entitlements.plist", "hardenedRuntime": true diff --git a/ui/scripts/publish.sh b/ui/scripts/publish.sh index 533e269843..4aa1637950 100755 --- a/ui/scripts/publish.sh +++ b/ui/scripts/publish.sh @@ -75,9 +75,10 @@ set -euo pipefail apt-get update -qq apt-get install -y -qq --no-install-recommends \ build-essential cmake pkg-config libssl-dev libdbus-1-dev \ - libclang-dev protobuf-compiler libprotobuf-dev ca-certificates >/dev/null 2>&1 + libclang-dev protobuf-compiler libprotobuf-dev ca-certificates \ + libvulkan-dev libvulkan1 glslc >/dev/null 2>&1 echo "==> Compiling goose (this takes a while)..." -cargo build --release --bin goose +cargo build --release --bin goose --features vulkan cp /build/target/release/goose /output/goose echo "==> Done" ' @@ -116,12 +117,13 @@ FROM rust:1.92-bookworm RUN apt-get update -qq && \ apt-get install -y -qq --no-install-recommends \ build-essential cmake pkg-config libssl-dev libdbus-1-dev \ - libclang-dev protobuf-compiler libprotobuf-dev ca-certificates >/dev/null 2>&1 && \ + libclang-dev protobuf-compiler libprotobuf-dev ca-certificates \ + libvulkan-dev libvulkan1 glslc >/dev/null 2>&1 && \ rm -rf /var/lib/apt/lists/* WORKDIR /build COPY . . RUN mkdir -p /output && \ - cargo build --release --bin goose && \ + cargo build --release --bin goose --features vulkan && \ cp target/release/goose /output/goose DEOF diff --git a/ui/sdk/scripts/build-native.ts b/ui/sdk/scripts/build-native.ts index e364f4c868..859298ef7f 100644 --- a/ui/sdk/scripts/build-native.ts +++ b/ui/sdk/scripts/build-native.ts @@ -57,10 +57,16 @@ function buildTarget(platform: string): void { console.log(`==> Building goose for ${platform} (${rustTarget})`); try { - execSync(`cargo build --release --target ${rustTarget} --bin goose`, { - cwd: ROOT, - stdio: "inherit", - }); + const featureArgs = platform.startsWith("linux-") + ? " --features vulkan" + : ""; + execSync( + `cargo build --release --target ${rustTarget} --bin goose${featureArgs}`, + { + cwd: ROOT, + stdio: "inherit", + }, + ); } catch (err) { console.error(`Failed to build for ${platform}`); throw err;