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
18 changes: 10 additions & 8 deletions .github/buildcheck/nativedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
import sys

# The four OS DLLs every Windows build imports, and the dynamic UCRT.
WINDOWS_OS = {"ws2_32.dll", "kernel32.dll", "shell32.dll", "advapi32.dll"}
# crypt32.dll: cpp-httplib loads the Windows certificate store for HTTPS clients
# (CertOpenSystemStoreW, `#pragma comment(lib, "crypt32.lib")` under CPPHTTPLIB_OPENSSL_SUPPORT); an
# OS component since Windows 2000. BoringSSL itself adds no import: its RNG loads bcryptprimitives
# at run time (LoadLibraryW), and everything else is in the static libraries.
WINDOWS_OS = {"ws2_32.dll", "kernel32.dll", "shell32.dll", "advapi32.dll", "crypt32.dll"}
# The Universal CRT forwarders. Present because the Windows builds use the HYBRID CRT (static STL +
# vcruntime, dynamic UCRT -- llama/CMakeLists.txt explains why), which is what keeps Microsoft's
# UCRT security updates reaching a shipped artifact instead of freezing a copy inside jllama.dll.
Expand Down Expand Up @@ -91,13 +95,11 @@ def ucrt_forwarder(name):
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
"/usr/lib/libobjc.A.dylib",
"/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
"/System/Library/Frameworks/Security.framework/Versions/A/Security",
# KNOWN DEFECT, allowed only so this check reports NEW dependencies: the macOS
# build picks up the runner's Homebrew OpenSSL, so the shipped dylib does not load
# on a Mac without `brew install openssl@3`. See TODO.md ("macOS dylib links
# Homebrew OpenSSL"); remove these two lines with the fix.
"/opt/homebrew/opt/openssl@3/lib/libssl.3.dylib",
"/opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib"},
# Security + CoreFoundation: cpp-httplib's system-certificate lookup under
# CPPHTTPLIB_OPENSSL_SUPPORT. The SSL library itself is BoringSSL, linked
# statically (llama/CMakeLists.txt, "HTTPS"); a /opt/homebrew/.../libssl line
# here would mean the build picked up the runner's OpenSSL again.
"/System/Library/Frameworks/Security.framework/Versions/A/Security"},
}
# The Visual Studio generator build of the same compiler and runtime.
ALLOWED["Windows/x86_64/msvc"] = ALLOWED["Windows/x86_64/cpu"]
Expand Down
4 changes: 3 additions & 1 deletion .github/dockcross/dockcross-android-arm64
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash

DEFAULT_DOCKCROSS_IMAGE=dockcross/android-arm64:20260712-79e54f9
# Pinned by digest (tag 20260712-79e54f9, the manifest Docker Hub served for it on 2026-10-09);
# the one line of this generated wrapper that is edited. Bump it deliberately, with a Publish run.
DEFAULT_DOCKCROSS_IMAGE=dockcross/android-arm64@sha256:60f04acad0d16fccbefc221d0c3419163f5c65f77c5246cb5302380db0762dac

#------------------------------------------------------------------------------
# Helpers
Expand Down
4 changes: 3 additions & 1 deletion .github/dockcross/dockcross-android-x86_64
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash

DEFAULT_DOCKCROSS_IMAGE=dockcross/android-x86_64:20260712-79e54f9
# Pinned by digest (tag 20260712-79e54f9, the manifest Docker Hub served for it on 2026-10-09);
# the one line of this generated wrapper that is edited. Bump it deliberately, with a Publish run.
DEFAULT_DOCKCROSS_IMAGE=dockcross/android-x86_64@sha256:d12c4091e9983d4ee5ebb4dad4b6f51420b2e9ec3bf6522d3e32ed87f54ea783

#------------------------------------------------------------------------------
# Helpers
Expand Down
4 changes: 3 additions & 1 deletion .github/dockcross/dockcross-manylinux_2_28-x64
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash

DEFAULT_DOCKCROSS_IMAGE=dockcross/manylinux_2_28-x64:20260712-79e54f9
# Pinned by digest (tag 20260712-79e54f9, the manifest Docker Hub served for it on 2026-10-09);
# the one line of this generated wrapper that is edited. Bump it deliberately, with a Publish run.
DEFAULT_DOCKCROSS_IMAGE=dockcross/manylinux_2_28-x64@sha256:44fa969013dd9a27d25b2f2d2d86651bf961c02c66bce785890a0ea4ff4190fa

#------------------------------------------------------------------------------
# Helpers
Expand Down
62 changes: 56 additions & 6 deletions .github/smoke-native-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,27 @@
# that never exits, and the assertion that matters here is native-library loadability, not a CLI
# exit code. Same job shape, repo-specific assertions.
#
# Deliberately model-free: no GGUF, no cache restore, no network — it runs in ~1 min. A full
# model-backed macOS server smoke would be strictly more, but the failure class that actually
# shipped is caught here, so this is the version that is cheap enough to always run.
# No cache restore and no model of the CI set: it runs in ~1 min. A full model-backed macOS server
# smoke would be strictly more, but the failure class that actually shipped is caught here, so this
# is the version that is cheap enough to always run. The one model it touches is the 1 MB
# stories260K.gguf of .github/models.csv, which step 3 downloads itself over HTTPS -- that download
# IS the check: the library links BoringSSL statically (llama/CMakeLists.txt, "HTTPS") and this
# dylib used to depend on the runner's Homebrew OpenSSL, which no check here could see because the
# runner has it. Step 3 proves the HTTPS client against the macOS certificate store and the
# embedded server behind its own TLS certificate, the same round the Linux and Windows rows of
# smoke-fatjar run.
#
# Usage: smoke-native-macos.sh <jar-dir> <jar-glob>
# Usage: smoke-native-macos.sh <jar-dir> <jar-glob> [port]
# Server output of step 3 is written to server-tls-out.log / server-tls-err.log in the working dir.
# <jar-dir> directory to search for the jar (recursively)
# <jar-glob> filename glob; must match EXACTLY ONE jar

set -euo pipefail

JAR_DIR="${1:?usage: smoke-native-macos.sh <jar-dir> <jar-glob>}"
JAR_GLOB="${2:?usage: smoke-native-macos.sh <jar-dir> <jar-glob>}"
JAR_DIR="${1:?usage: smoke-native-macos.sh <jar-dir> <jar-glob> [port]}"
JAR_GLOB="${2:?usage: smoke-native-macos.sh <jar-dir> <jar-glob> [port]}"
TLS_PORT="${3:-18081}"
HTTPS_MODEL_NAME="stories260K.gguf"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Expand Down Expand Up @@ -91,4 +100,45 @@ echo "== JVM load + JNI round-trip =="
java -cp "$JAR" "$SCRIPT_DIR/smoke/NativeLoadSmoke.java" \
|| fail "the packaged native library did not load in a JVM"

# 3) HTTPS in both directions out of the packaged jar: the server behind a self-signed certificate
# (--ssl-key-file / --ssl-cert-file) serving a model it downloaded from an https:// URL, verified
# against the macOS certificate store (Security.framework). -ngl 0: this is about TLS, not Metal.
echo "== HTTPS server + https:// model download =="
HTTPS_MODEL_URL="$(grep -E "^${HTTPS_MODEL_NAME//./\\.}," "$SCRIPT_DIR/models.csv" | cut -d, -f2- || true)"
[ -n "$HTTPS_MODEL_URL" ] || fail "$HTTPS_MODEL_NAME has no row in $SCRIPT_DIR/models.csv"
openssl req -x509 -newkey rsa:2048 -nodes -keyout "$WORK/tls-key.pem" -out "$WORK/tls-cert.pem" -days 2 \
-subj "/CN=127.0.0.1" > /dev/null 2>&1 || fail "could not create the self-signed TLS certificate"
export LLAMA_CACHE="$WORK/llama-cache"
mkdir -p "$LLAMA_CACHE"
# -m names where the download lands; with --model-url alone the server starts in ROUTER mode.
java -jar "$JAR" -m "$LLAMA_CACHE/$HTTPS_MODEL_NAME" --model-url "$HTTPS_MODEL_URL" \
--host 127.0.0.1 --port "$TLS_PORT" --chat-template chatml \
-ngl 0 --ssl-key-file "$WORK/tls-key.pem" --ssl-cert-file "$WORK/tls-cert.pem" \
> server-tls-out.log 2> server-tls-err.log &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2> /dev/null || true; rm -rf "$WORK"' EXIT
CODE=""
for _ in $(seq 1 100); do
if ! kill -0 "$SERVER_PID" 2> /dev/null; then
echo "--- server-tls-out.log ---" && cat server-tls-out.log
echo "--- server-tls-err.log ---" && cat server-tls-err.log
fail "the TLS server exited before becoming healthy (download or load failed)"
fi
CODE="$(curl -sk -o /dev/null -w '%{http_code}' "https://127.0.0.1:$TLS_PORT/health" || true)"
[ "$CODE" = "200" ] && break
sleep 3
done
if [ "$CODE" != "200" ]; then
echo "--- server-tls-err.log (tail) ---" && tail -50 server-tls-err.log
fail "https://127.0.0.1:$TLS_PORT/health never returned 200 (last code: ${CODE:-none})"
fi
echo "HTTPS health OK"
if curl -s -o /dev/null --max-time 10 "http://127.0.0.1:$TLS_PORT/health"; then
fail "the TLS port answered a plain-HTTP request — the server did not use the certificate"
fi
echo "plain HTTP on the TLS port refused: OK"
find "$LLAMA_CACHE" -type f -name '*.gguf' | grep -q . \
|| fail "no .gguf under $LLAMA_CACHE — the https:// download did not happen"
echo "https:// model download OK: $(find "$LLAMA_CACHE" -type f -name '*.gguf' -exec basename {} \; | head -1)"

echo "smoke test PASSED"
129 changes: 92 additions & 37 deletions .github/smoke-test-fatjar.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,135 @@
# SPDX-License-Identifier: MIT OR Apache-2.0

# Smoke test for an all-backends server fat jar on a GPU-less Windows runner —
# the PowerShell analogue of smoke-test-fatjar.sh (see there for what it proves).
# the PowerShell analogue of smoke-test-fatjar.sh (see there for what the two rounds prove:
# round 1 plain HTTP with the cached model, round 2 an HTTPS server behind a self-signed
# certificate serving a model it downloaded itself from an https:// URL).
#
# Usage: smoke-test-fatjar.ps1 -JarDir <dir> -JarGlob <glob> -Model <gguf> [-Port <p>]
# Server output is written to server-out.log / server-err.log in the working dir
# (uploaded by the CI job on failure).
# Server output is written to server-out.log / server-err.log (round 1) and server-tls-out.log /
# server-tls-err.log (round 2) in the working dir (uploaded by the CI job on failure).
param(
[Parameter(Mandatory = $true)][string]$JarDir,
[Parameter(Mandatory = $true)][string]$JarGlob,
[Parameter(Mandatory = $true)][string]$Model,
[int]$Port = 18080
)
$ErrorActionPreference = 'Stop'
$TlsPort = $Port + 1
$HttpsModelName = 'stories260K.gguf'

function Dump-ServerLogs {
foreach ($log in 'server-out.log', 'server-err.log') {
function Dump-ServerLogs([string[]]$Logs) {
foreach ($log in $Logs) {
if (Test-Path $log) {
Write-Host "--- $log (tail) ---"
Get-Content $log -Tail 50
}
}
}

$jars = @(Get-ChildItem -Path $JarDir -Filter $JarGlob -File)
if ($jars.Count -ne 1) {
Write-Error "expected exactly 1 jar matching $JarGlob in $JarDir, got $($jars.Count)"
}
$jar = $jars[0].FullName
if (-not (Test-Path $Model)) { Write-Error "model file missing: $Model" }
Write-Host "smoke jar: $jar"

$proc = Start-Process java -PassThru -NoNewWindow `
-RedirectStandardOutput server-out.log -RedirectStandardError server-err.log `
-ArgumentList '-jar', $jar, '-m', $Model, '--host', '127.0.0.1', '--port', "$Port", '--chat-template', 'chatml'
try {
# Poll /health until 200 (model loaded); 100 x 3 s = 5 min budget. An early server
# exit (e.g. an UnsatisfiedLinkError the fallback chain failed to absorb) fails fast.
$healthy = $false
# Poll /health until 200 (model loaded); 100 x 3 s = 5 min budget. An early server exit (e.g. an
# UnsatisfiedLinkError the fallback chain failed to absorb) fails fast. -SkipCertificateCheck
# accepts round 2's self-signed certificate and is a no-op over plain HTTP.
function Wait-Healthy($Proc, [string]$Base, [string[]]$Logs) {
foreach ($i in 1..100) {
if ($proc.HasExited) {
Dump-ServerLogs
Write-Error "server process exited before becoming healthy (exit code $($proc.ExitCode))"
if ($Proc.HasExited) {
Dump-ServerLogs $Logs
Write-Error "server process exited before becoming healthy ($Base, exit code $($Proc.ExitCode))"
}
try {
$health = Invoke-WebRequest -Uri "http://127.0.0.1:$Port/health" -UseBasicParsing -TimeoutSec 5
if ($health.StatusCode -eq 200) { $healthy = $true; break }
$health = Invoke-WebRequest -Uri "$Base/health" -UseBasicParsing -SkipCertificateCheck -TimeoutSec 5
if ($health.StatusCode -eq 200) { return }
} catch {
# 503 while loading / connection refused before listening: keep polling.
}
Start-Sleep -Seconds 3
}
if (-not $healthy) {
Dump-ServerLogs
Write-Error "/health never returned 200"
}
Write-Host "health OK"
Dump-ServerLogs $Logs
Write-Error "$Base/health never returned 200"
}

# A chat completion with one valid choice, over $Base.
function Test-ChatCompletion([string]$Base) {
$body = '{"messages":[{"role":"user","content":"Say hello."}],"max_tokens":16,"temperature":0}'
$response = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/v1/chat/completions" `
$response = Invoke-RestMethod -Uri "$Base/v1/chat/completions" -SkipCertificateCheck `
-Method Post -ContentType 'application/json' -Body $body -TimeoutSec 300
if (-not $response.choices -or $response.choices.Count -lt 1 -or -not $response.choices[0].message) {
Write-Error "malformed chat completion response: $($response | ConvertTo-Json -Depth 6 -Compress)"
}
Write-Host "chat completion OK: $($response.choices[0].message.content)"
Write-Host "chat completion OK ($Base): $($response.choices[0].message.content)"
}

$jars = @(Get-ChildItem -Path $JarDir -Filter $JarGlob -File)
if ($jars.Count -ne 1) {
Write-Error "expected exactly 1 jar matching $JarGlob in $JarDir, got $($jars.Count)"
}
$jar = $jars[0].FullName
if (-not (Test-Path $Model)) { Write-Error "model file missing: $Model" }
Write-Host "smoke jar: $jar"

$modelsCsv = Join-Path $PSScriptRoot 'models.csv'
$httpsModelUrl = (Get-Content $modelsCsv | Where-Object { $_ -like "$HttpsModelName,*" } | Select-Object -First 1)
if (-not $httpsModelUrl) { Write-Error "$HttpsModelName has no row in $modelsCsv (round 2 downloads it over HTTPS)" }
$httpsModelUrl = $httpsModelUrl.Substring($HttpsModelName.Length + 1)

# ---- Round 1: plain HTTP, the cached model -------------------------------------------------------
$proc = Start-Process java -PassThru -NoNewWindow `
-RedirectStandardOutput server-out.log -RedirectStandardError server-err.log `
-ArgumentList '-jar', $jar, '-m', $Model, '--host', '127.0.0.1', '--port', "$Port", '--chat-template', 'chatml'
try {
Wait-Healthy $proc "http://127.0.0.1:$Port" @('server-out.log', 'server-err.log')
Write-Host "health OK"
Test-ChatCompletion "http://127.0.0.1:$Port"

# The loader must have reported its backend decision (a chosen backend on a GPU
# machine, the CPU fallback on a GPU-less runner) — this pins that the manifest was
# actually read, i.e. the smoke really ran the multi-backend code path.
# machine, the CPU fallback on a GPU-less runner) — this pins that the smoke really
# ran the multi-backend code path.
$selection = Select-String -Path 'server-out.log', 'server-err.log' `
-Pattern '\[jllama\] (using native backend|no manifest backend loadable)'
-Pattern '\[jllama\] using native backend'
if (-not $selection) {
Dump-ServerLogs
Write-Error "no backend-selection log line found - the backend manifest was not processed"
Dump-ServerLogs @('server-out.log', 'server-err.log')
Write-Error "no backend-selection log line found - the loader did not report a backend"
}
Write-Host "backend selection: $($selection[0].Line)"
} finally {
if (-not $proc.HasExited) { Stop-Process -Id $proc.Id -Force }
}

# ---- Round 2: HTTPS server + https:// model download ---------------------------------------------
# openssl comes with Git for Windows (usr\bin is not on PATH); the runner images have it.
$openssl = (Get-Command openssl -ErrorAction SilentlyContinue).Source
if (-not $openssl) { $openssl = Join-Path $env:ProgramFiles 'Git\usr\bin\openssl.exe' }
if (-not (Test-Path $openssl)) { Write-Error "no openssl CLI found to mint the TLS test certificate" }
& $openssl req -x509 -newkey rsa:2048 -nodes -keyout tls-key.pem -out tls-cert.pem -days 2 -subj '/CN=127.0.0.1' 2>$null
if ($LASTEXITCODE -ne 0 -or -not (Test-Path tls-cert.pem)) { Write-Error "could not create the self-signed TLS certificate" }
$env:LLAMA_CACHE = Join-Path (Get-Location) 'llama-cache'
if (Test-Path $env:LLAMA_CACHE) { Remove-Item -Recurse -Force $env:LLAMA_CACHE }
New-Item -ItemType Directory -Path $env:LLAMA_CACHE | Out-Null

# -m names where the download lands; with --model-url alone the server starts in ROUTER mode.
$proc = Start-Process java -PassThru -NoNewWindow `
-RedirectStandardOutput server-tls-out.log -RedirectStandardError server-tls-err.log `
-ArgumentList '-jar', $jar, '-m', (Join-Path $env:LLAMA_CACHE $HttpsModelName), '--model-url', $httpsModelUrl, `
'--host', '127.0.0.1', '--port', "$TlsPort", `
'--chat-template', 'chatml', '--ssl-key-file', 'tls-key.pem', '--ssl-cert-file', 'tls-cert.pem'
try {
Wait-Healthy $proc "https://127.0.0.1:$TlsPort" @('server-tls-out.log', 'server-tls-err.log')
Write-Host "HTTPS health OK"
Test-ChatCompletion "https://127.0.0.1:$TlsPort"
# The port must really speak TLS: a plain-HTTP request to it fails.
$plainAnswered = $false
try {
Invoke-WebRequest -Uri "http://127.0.0.1:$TlsPort/health" -UseBasicParsing -TimeoutSec 10 | Out-Null
$plainAnswered = $true
} catch {
# expected: the TLS server rejects the plain request
}
if ($plainAnswered) { Write-Error "the TLS port answered a plain-HTTP request - the server did not use the certificate" }
Write-Host "plain HTTP on the TLS port refused: OK"
# The model came over HTTPS into this run's cache, not from the runner's model directory.
$downloaded = @(Get-ChildItem -Path $env:LLAMA_CACHE -Recurse -File -Filter '*.gguf')
if ($downloaded.Count -lt 1) { Write-Error "no .gguf under $env:LLAMA_CACHE - the https:// download did not happen" }
Write-Host "https:// model download OK: $($downloaded[0].Name)"
Write-Host "smoke test PASSED"
} finally {
if (-not $proc.HasExited) { Stop-Process -Id $proc.Id -Force }
Expand Down
Loading
Loading