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
39 changes: 39 additions & 0 deletions .github/scripts/cpu-separation.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Separate a short generated mix on a machine without a GPU: the server starts,
# runs on the CPU and returns stems.
#
# cpu-separation.ps1 -Server stemd-server.exe -Cli stemd-cli.exe
param(
[Parameter(Mandatory)] [string]$Server,
[Parameter(Mandatory)] [string]$Cli
)
$ErrorActionPreference = 'Stop'

$work = Join-Path $env:RUNNER_TEMP 'cpu-separation'
New-Item -ItemType Directory -Force -Path $work | Out-Null
python (Join-Path $PSScriptRoot 'test-mix.py') "$work\mix.wav"
if ($LASTEXITCODE -ne 0) { throw 'test-mix.py failed' }

$out = "$work\server.out"
$err = "$work\server.err"
$proc = Start-Process $Server -PassThru -NoNewWindow `
-ArgumentList '--headless', '--no-mdns', '--bind', '127.0.0.1:7979', '--cache-dir', "$work\cache" `
-RedirectStandardOutput $out -RedirectStandardError $err
function Get-Log { (Get-Content $out, $err -ErrorAction SilentlyContinue) -join "`n" }
function Stop-WithLog([string]$why) {
Get-Log | Write-Host
if (-not $proc.HasExited) { Stop-Process $proc -Force }
throw $why
}

$deadline = (Get-Date).AddMinutes(10)
while ((Get-Log) -notmatch 'listening') {
if ($proc.HasExited) { Stop-WithLog "the server exited with 0x$('{0:X8}' -f $proc.ExitCode)" }
if ((Get-Date) -gt $deadline) { Stop-WithLog 'the server is not listening after 10 minutes' }
Start-Sleep -Seconds 1
}
if ((Get-Log) -notmatch 'via mlx on cpu') { Stop-WithLog 'the server is not on the CPU' }

& $Cli --host 127.0.0.1:7979 --out "$work\stems" "$work\mix.wav"
if ($LASTEXITCODE -ne 0) { Stop-WithLog 'the separation failed' }
Stop-Process $proc -Force
Get-ChildItem "$work\stems"
28 changes: 28 additions & 0 deletions .github/scripts/cpu-separation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Separate a short generated mix on a machine without a GPU: the server starts,
# runs on the CPU and returns stems.
#
# cpu-separation.sh SERVER CLI
set -euo pipefail
server="$1"
cli="$2"
work="$(mktemp -d)"
python3 "$(dirname "$0")/test-mix.py" "$work/mix.wav"

"$server" --headless --no-mdns --bind 127.0.0.1:7979 --cache-dir "$work/cache" >"$work/server.log" 2>&1 &
pid=$!
trap 'kill "$pid" 2>/dev/null || true' EXIT
fail() {
cat "$work/server.log"
echo "ERROR: $1" >&2
exit 1
}
for _ in $(seq 1 600); do
grep -q listening "$work/server.log" && break
kill -0 "$pid" 2>/dev/null || fail "the server exited"
sleep 1
done
grep -q listening "$work/server.log" || fail "the server is not listening after 10 minutes"
grep -q "via mlx on cpu" "$work/server.log" || fail "the server is not on the CPU"
"$cli" --host 127.0.0.1:7979 --out "$work/stems" "$work/mix.wav" || fail "the separation failed"
ls -l "$work/stems"
30 changes: 30 additions & 0 deletions .github/scripts/test-mix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Write a 10 s stereo test mix at 44.1 kHz: a chord, a kick and some noise.

Usage: test-mix.py OUT.wav
"""

import math
import random
import struct
import sys
import wave

RATE = 44100
SECONDS = 10

random.seed(1)
frames = bytearray()
for i in range(RATE * SECONDS):
t = i / RATE
chord = 0.2 * sum(math.sin(2 * math.pi * f * t) for f in (220.0, 277.2, 329.6)) / 3
beat = t % 0.5
kick = 0.5 * math.sin(2 * math.pi * 60 * beat) * math.exp(-20 * beat)
left = chord + kick + 0.02 * random.uniform(-1, 1)
right = chord + kick + 0.02 * random.uniform(-1, 1)
frames += struct.pack("<hh", *(int(max(-1.0, min(1.0, v)) * 32767) for v in (left, right)))

with wave.open(sys.argv[1], "wb") as out:
out.setnchannels(2)
out.setsampwidth(2)
out.setframerate(RATE)
out.writeframes(bytes(frames))
12 changes: 12 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ jobs:
appimage=$(ls dist/*.AppImage)
(cd "$RUNNER_TEMP" && "$GITHUB_WORKSPACE/$appimage" --appimage-extract >/dev/null)
LD_LIBRARY_PATH="$PWD/dist/stub" "$RUNNER_TEMP/squashfs-root/AppRun" --version
# The runner has no GPU, and the stub libcuda finds no device.
- name: CPU separation
timeout-minutes: 30
run: |
root="$RUNNER_TEMP/squashfs-root"
LD_LIBRARY_PATH="$PWD/dist/stub" .github/scripts/cpu-separation.sh "$root/AppRun" "$root/opt/stemd/bin/stemd-cli"
- uses: actions/upload-artifact@v4
with:
name: linux-${{ matrix.arch }}
Expand Down Expand Up @@ -268,6 +274,12 @@ jobs:
$installed = Join-Path $env:LOCALAPPDATA 'Programs\stemd'
Test-Version (Join-Path $installed 'stemd-server.exe')
Test-Version (Join-Path $installed 'stemd-cli.exe')
# The runner has no NVIDIA driver.
- name: CPU separation
timeout-minutes: 30
run: |
$installed = Join-Path $env:LOCALAPPDATA 'Programs\stemd'
.github/scripts/cpu-separation.ps1 -Server (Join-Path $installed 'stemd-server.exe') -Cli (Join-Path $installed 'stemd-cli.exe')
- uses: actions/upload-artifact@v4
with:
name: windows-x86_64
Expand Down
16 changes: 11 additions & 5 deletions crates/stemd-server/src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,17 @@ pub fn prepare(args: &Args, logs: LogBuffer, addr: SocketAddr) -> Result<Server>
// loaded at CPU speed. A card whose runtime is missing looks exactly like
// no card in every later line, and only one of the two has a fix.
if stemd_core::Accelerator::gpu_refused() {
tracing::warn!(
"a GPU is installed but its runtime is not usable from here, so this \
runs on the CPU. Run with --install-cuda to fetch it (about 1.2 GB, \
once), or put the libraries beside the executable yourself."
);
if cfg!(windows) {
tracing::warn!(
"a GPU is installed but its runtime is not usable from here, so this \
runs on the CPU. Run with --install-cuda to fetch it (about 1.2 GB, \
once), or put the libraries beside the executable yourself."
);
} else {
tracing::warn!(
"a GPU is installed but failed a test computation, so this runs on the CPU."
);
}
}
let settings = Arc::new(open_settings(args)?);
let (stems, cache_summary) = open_stem_cache(args)?;
Expand Down
4 changes: 4 additions & 0 deletions docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ prebuilt copy instead.
On Windows the prebuilt also carries OpenBLAS (`libopenblas.lib` and `.dll`)
and `dl.lib`.

`mlx-sys` patches MLX's CUDA allocator so a CUDA build also runs on the CPU of a
machine without a CUDA device. CD checks this with a CPU separation on the Linux
and Windows runners, which have no GPU.

The manifest records the `mlx-sys` version, the MLX tag, the target, the C++
compiler (only `msvc` on Windows, whose toolsets link older ones), nvcc,
cuDNN, the GPU architectures and the RMSNorm patch, and `mlx-sys` refuses a
Expand Down
8 changes: 4 additions & 4 deletions packaging/mlx-prebuilt.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
# Changing an architecture list, the toolchain or the MLX pin means a new tag
# and a new build. 90a rather than 90: MLX builds its Hopper-only kernels for
# 90a alone. The SHA-256 lines are filled in from that workflow's summary.
MLX_PREBUILT_TAG=mlx-cuda-2
MLX_PREBUILT_TAG=mlx-cuda-3
CUDA_ARCHS_linux_x86_64="80;86;89;90a;120"
CUDA_ARCHS_linux_aarch64="90a;100;121"
CUDA_ARCHS_windows_x86_64="80;86;89;90a;120"
SHA256_linux_x86_64=2837bfdeecfa441c0c7d3bf9bd4974a3b04269e79bfbebecdb53806278d2722d
SHA256_linux_aarch64=703eacbef4b6d54532417dcb455a743a2199b901c67b3630c3deeeb249b1068e
SHA256_windows_x86_64=51cb5388c5abf5c128717a294d12fdf8b0530ac4cec4d19bbbce233642fae57f
SHA256_linux_x86_64=0970ff131deb42cf9b18c9600f6de7c8a47513fe9db37eca0f4a88edf0208eb8
SHA256_linux_aarch64=ead8f1a706cb1b0944f87289040bfbc6b9da1d8014bf5c5326d880d702fdf375
SHA256_windows_x86_64=d7664ce18c250ae74efeed3b6a77405884e452fa608ae271d36827bd29161f76
2 changes: 1 addition & 1 deletion vendor/mlx-rs-stemd
Submodule mlx-rs-stemd updated 1 files
+324 −1 mlx-sys/build.rs
Loading