Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
114 changes: 99 additions & 15 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,75 @@ name: CI

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

env:
CARGO_TERM_COLOR: always
RUSTFLAGS: --deny warnings
RUSTDOCFLAGS: --deny warnings
SLANG_TAG: 2025.18.2

jobs:
# Check formatting.
format:
name: Format
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Run cargo fmt
run: cargo fmt --all -- --check
setup-slang:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
outputs:
slang-dir: ${{ steps.setup.outputs.slang-dir }} # Pass SLANG_DIR to dependent jobs
slang-cache-key: ${{ steps.setup.outputs.slang-cache-key }} # Pass SLANG_DIR to dependent jobs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using outputs is the wrong approach if we want to support matrices, as the output is overwritten for each matrix (see https://github.com/orgs/community/discussions/17245 for more in depth discussion).

different approaches:

  • preferred: we're not actually relying on a matrix here, as we only want to download slang, so drop the matrix, and download everything needed in 1 job.
  • OR have a different job (name) for each target, so we can use output (great to support compiling slang from CI)
  • OR do everything in the same job (not great for parallelization, but it's probably fine, and may end up saving total aggregated time.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a matrix to download slang, and another to test it.

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

- name: Cache Slang
id: cache-slang
uses: actions/cache/restore@v4 # Restore first
with:
path: |
~/.cache/slang # Matches script's default OUTPUT_DIR
key: slang-v$SLANG_TAG-${{ runner.os }}-${{ runner.arch }}

- name: Setup Slang
id: setup
run: |
echo "version=$SLANG_TAG" >> $GITHUB_OUTPUT # Output for cache key
SLANG_DIR=$(./download_slang.sh --version $SLANG_TAG | grep '^SLANG_DIR=' | cut -d'=' -f2-)
echo "slang-dir=$SLANG_DIR" >> $GITHUB_OUTPUT # Output for dependents
echo "slang-cache-key=slang-v$SLANG_TAG-${{ runner.os }}-${{ runner.arch }}" >> $GITHUB_OUTPUT
echo "SLANG_DIR=$SLANG_DIR" >> $GITHUB_ENV # For this job if needed

- name: Save Slang Cache
if: steps.cache-slang.outputs.cache-hit != 'true' # Only save on miss
uses: actions/cache/save@v4
with:
path: ~/.cache/slang
key: ${{ steps.setup.outputs.slang-cache-key }}
# Run clippy lints.
clippy:
needs: setup-slang # Depends on setup-slang
name: Clippy
runs-on: ubuntu-latest
env:
SLANG_DIR: ${{ needs.setup-slang.outputs.slang-dir }}
timeout-minutes: 30
steps:
- name: Checkout repository
Expand All @@ -29,36 +84,59 @@ jobs:
- name: Install dependencies
run: sudo apt-get update; sudo apt-get install --no-install-recommends libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev

- name: Retrieve Cache for Slang
uses: actions/cache/restore@v4
with:
path: ~/.cache/slang
key: ${{ needs.setup-slang.outputs.slang-cache-key }}

- name: Populate target directory from cache
uses: Leafwing-Studios/cargo-cache@v2
with:
sweep-cache: true

- name: Run clippy lints
run: cargo clippy --locked --workspace --all-targets --all-features -- --deny warnings
run: SLANG_DIR=$SLANG_DIR cargo clippy --locked --workspace --all-targets --features web,desktop,mobile,comptime_checks -- --deny warnings

# Check formatting.
format:
name: Format
# Check documentation.
doc:
needs: setup-slang # Depends on setup-slang
name: Docs
runs-on: ubuntu-latest
timeout-minutes: 30
env:
SLANG_DIR: ${{ needs.setup-slang.outputs.slang-dir }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install dependencies
run: sudo apt-get update; sudo apt-get install --no-install-recommends libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev

- name: Retrieve Cache for Slang
uses: actions/cache/restore@v4
with:
components: rustfmt
path: ~/.cache/slang
key: ${{ needs.setup-slang.outputs.slang-cache-key }}

- name: Run cargo fmt
run: cargo fmt --all -- --check
- name: Populate target directory from cache
uses: Leafwing-Studios/cargo-cache@v2
with:
sweep-cache: true

# Check documentation.
doc:
name: Docs
- name: Check documentation
run: SLANG_DIR=$SLANG_DIR cargo doc --locked --workspace --features web,desktop,mobile,comptime_checks --document-private-items --no-deps
# Testing.
test:
needs: setup-slang # Depends on setup-slang
name: Tests
runs-on: ubuntu-latest
timeout-minutes: 30
env:
SLANG_DIR: ${{ needs.setup-slang.outputs.slang-dir }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -69,10 +147,16 @@ jobs:
- name: Install dependencies
run: sudo apt-get update; sudo apt-get install --no-install-recommends libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev

- name: Retrieve Cache for Slang
uses: actions/cache/restore@v4
with:
path: ~/.cache/slang
key: ${{ needs.setup-slang.outputs.slang-cache-key }}

- name: Populate target directory from cache
uses: Leafwing-Studios/cargo-cache@v2
with:
sweep-cache: true

- name: Check documentation
run: cargo doc --locked --workspace --all-features --document-private-items --no-deps
- name: Run Cargo Tests
run: |
SLANG_DIR=$SLANG_DIR cargo test --verbose
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,6 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

.DS_store
.DS_store

downloaded_slang
13 changes: 10 additions & 3 deletions crates/slai-chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ This is a basic chat interface for testing **slai**. Currently only supports:
- Qwen 2.
- Segment Anything.

In order to compile and run `slai-chat`, be sure to define the `SLANG_DIR` environment variable:
1. Download the Slang compiler libraries for your platform: https://github.com/shader-slang/slang/releases/tag/v2025.16
2. Unzip the downloaded directory, and use its path as value to the `SLANG_DIR` environment variable: `SLANG_DIR=/path/to/slang`.
In order to compile and run `slai-chat`, be sure to define the `SLANG_DIR` environment variable.

Either:

- Run the script at the root of slai repository: `SLANG_DIR=$(./download_slang.sh | grep '^SLANG_DIR=' | cut -d'=' -f2-)`
- Or manually:
1. Download the Slang compiler libraries for your platform: https://github.com/shader-slang/slang/releases/tag/v2025.16
2. Unzip the downloaded directory, and use its path as value to the `SLANG_DIR` environment variable: `SLANG_DIR=/path/to/slang`.
Note that the variable must point to the root of the slang installation (i.e. the directory that contains `bin` and `lib`).



To run the GUI natively:
```bash
dx run --release --features desktop
Expand Down
5 changes: 1 addition & 4 deletions crates/slai-chat/src/chat_llama2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,7 @@ impl<B: Backend> ChatLlama2<B> {
// Run the transformer.
let mut encoder = backend.begin_encoding();
backend.write_buffer(state.rope_config_mut().buffer_mut(), &[rope_config])?;
backend.write_buffer(
state.rms_norm_config_mut().buffer_mut(),
&[rms_norm_config],
)?;
backend.write_buffer(state.rms_norm_config_mut().buffer_mut(), &[rms_norm_config])?;
backend.write_buffer(state.attn_params_mut().buffer_mut(), &[attn_params])?;
state
.x
Expand Down
22 changes: 10 additions & 12 deletions crates/slai-chat/src/components/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,18 +437,16 @@ fn submit_prompt<B: Backend>(
let gpu = gpu.clone();
let llm = model.llm.clone();
execute(async move {
llm
.forward(
&gpu.backend,
prompt,
sampler,
chat_template,
next_pos,
|msg| {
Ok(out.unbounded_send(msg)?)
})
.await
.unwrap()
llm.forward(
&gpu.backend,
prompt,
sampler,
chat_template,
next_pos,
|msg| Ok(out.unbounded_send(msg)?),
)
.await
.unwrap()
});
}

Expand Down
1 change: 0 additions & 1 deletion crates/slai-chat/src/components/home.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::chat_template::ChatTemplate;
use crate::llm::ChatLlm;
use crate::{GgufMetadata, GpuInstanceCtx, LoadedModel, LoadedModelSignal, SelectedBackend};
use async_std::sync::RwLock;
use dioxus::prelude::*;
use dioxus_markdown::Markdown;
use rfd::FileHandle;
Expand Down
3 changes: 1 addition & 2 deletions crates/slai-chat/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::cli::Cli;
use crate::llm::ChatLlm;
use crate::prompt::Prompt;
use crate::sampler::SamplerParams;
use async_std::sync::RwLock;
use components::{Chat, Home};
use dioxus::prelude::*;
use slang_hal::backend::Backend;
Expand Down Expand Up @@ -161,7 +160,7 @@ fn App() -> Element {
match (&*gpu_wgpu.read_unchecked(), &*gpu_cuda.read_unchecked()) {
(Some(Ok(gpu_wgpu)), Some(Ok(gpu_cuda))) => {
use_context_provider(|| gpu_wgpu.clone());
use_context_provider(|| gpu_cuda.clone());
use_context_provider(|| *gpu_cuda);
use_context_provider(|| LoadedModelSignal::new(None));
use_context_provider(|| Signal::new(PromptState::default()));

Expand Down
2 changes: 1 addition & 1 deletion crates/slai/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn main() {
let out_dir = Path::new(&out_dir);
let autogen_target = out_dir.join("autogen");

for (target, ext) in targets {
for (target, _ext) in targets {
std::fs::create_dir_all(&autogen_target).unwrap();
slang.compile_all(target, "./shaders", &autogen_target, &[]);
}
Expand Down
109 changes: 109 additions & 0 deletions download_slang.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/bin/bash

# Default values
OS=""
OUTPUT_DIR="$HOME/.cache/slang"
SLANG_VERSION=""
SLANG_TAG=""
ASSET_SUFFIX=""
SLANG_URL_BASE="https://github.com/shader-slang/slang/releases/download"

# Help message
usage() {
echo "Usage: $0 [--os <linux|macos|macos-arm64|windows>] [--output-dir <path>] [--version <version>]"
echo " --os: Target OS (default: auto-detect from current platform)"
echo " --output-dir: Directory to extract Slang (default: ~/.cache/slang)"
echo " --version: Slang version (e.g., 2025.18.2, default: latest)"
echo "Example: $0 --os linux --output-dir /tmp/slang"
}

# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--os) OS="$2"; shift ;;
--output-dir) export OUTPUT_DIR="$2"; shift ;;
--version) export SLANG_VERSION="$2"; shift ;;
*) usage ; exit 1 ;;
esac
shift
done

# Detect OS if not specified
if [[ -z "$OS" ]]; then
case "$(uname -s)" in
Linux*) OS="linux" ;;
Darwin*)
if [[ "$(uname -m)" == "arm64" ]]; then
OS="macos-aarch64"
else
OS="macos"
fi
;;
CYGWIN*|MINGW*|MSYS*) OS="windows" ;;
*) echo "Error: Unable to detect OS. Specify --os (linux, macos, macos-arm64, windows)"; exit 1 ;;
esac
fi

# Determine asset suffix based on OS
case "$OS" in
linux) ASSET_SUFFIX="linux-x86_64.zip" ;;
macos) ASSET_SUFFIX="macos-x86_64.zip" ;;
macos-aarch64) ASSET_SUFFIX="macos-aarch64.zip" ;;
windows) ASSET_SUFFIX="windows-x86_64.zip" ;;
*) echo "Error: Unsupported OS: $OS"; exit 1 ;;
esac

# Get Slang version if not specified
if [[ -z "$SLANG_VERSION" ]]; then
export SLANG_TAG=$(curl -s https://api.github.com/repos/shader-slang/slang/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
export SLANG_VERSION=$(echo "$SLANG_TAG" | sed 's/v//') # e.g., v2025.18.2 -> 2025.18.2
else
export SLANG_TAG="v$SLANG_VERSION"
fi

if [[ -z "$SLANG_VERSION" ]]; then
echo "Error: Could not determine Slang version"
exit 1
fi

# Set up paths
SLANG_DIR="$OUTPUT_DIR/slang-v$SLANG_VERSION-$OS"
ZIP_URL="$SLANG_URL_BASE/$SLANG_TAG/slang-$SLANG_VERSION-$ASSET_SUFFIX"
TEMP_ZIP="/tmp/slang-$SLANG_VERSION.zip"

# Check if Slang is already extracted
if [[ -d "$SLANG_DIR" ]] && [[ -f "$SLANG_DIR/bin/slangc" || -f "$SLANG_DIR/bin/slangc.exe" ]]; then
echo "Using existing Slang at $SLANG_DIR"
echo "SLANG_DIR=$SLANG_DIR"
exit 0
fi

# Download Slang release
echo "Downloading Slang v$SLANG_VERSION for $OS from $ZIP_URL..."
mkdir -p "$OUTPUT_DIR"
curl -L -o "$TEMP_ZIP" "$ZIP_URL" || { echo "Error: Download failed for $ZIP_URL"; exit 1; }

# Extract based on OS
echo "Extracting to $SLANG_DIR..."
if [[ "$OS" == "windows" ]]; then
# Windows: Assume 7z is available (or adjust for PowerShell/Expand-Archive)
7z x "$TEMP_ZIP" -o"$SLANG_DIR" -y > /dev/null || { echo "Error: Extraction failed"; rm -f "$TEMP_ZIP"; exit 1; }
else
# Linux/macOS: Use unzip
unzip -q "$TEMP_ZIP" -d "$SLANG_DIR" || { echo "Error: Extraction failed"; rm -f "$TEMP_ZIP"; exit 1; }
fi

# Clean up
rm -f "$TEMP_ZIP"

# Verify extraction
if [[ ! -f "$SLANG_DIR/bin/slangc" && ! -f "$SLANG_DIR/bin/slangc.exe" ]]; then
echo "Error: Extraction incomplete, slangc not found in $SLANG_DIR/bin"
exit 1
fi

echo "Slang v$SLANG_VERSION extracted to $SLANG_DIR"
echo "SLANG_DIR=$SLANG_DIR"

# For use in calling script
export SLANG_DIR
Loading