Ship a managed Vulkan whisper-server for Linux x64

This commit is contained in:
nomoreshow
2026-09-04 00:34:55 +03:00
parent 310ef8d7cf
commit e0eae4d8fe
16 changed files with 1117 additions and 15 deletions
+44
View File
@@ -0,0 +1,44 @@
FROM ubuntu@sha256:2edbbc5dc405e9612ba3584ce95480277e3eb374407b5505fe26f17df77c7dbc
ARG DEBIAN_FRONTEND=noninteractive
ARG CMAKE_VERSION=3.31.6
ARG CMAKE_SHA256=5a1133ff103c71eb5120e2cc3de922733e7d8a26a98ae716397e8676adb367bf
COPY lunarg-signing-key-pub.asc /tmp/lunarg.asc
RUN set -eux; \
test "$(sha256sum /tmp/lunarg.asc | cut -d' ' -f1)" = aa1c3c29673140e77f0d6a9aaeed5d9b5621e305ead51c59fae4458bbb4df92b; \
apt-get update; \
apt-get install --no-install-recommends -y \
build-essential=12.9ubuntu3 \
ca-certificates \
curl \
file \
git \
gnupg \
ninja-build=1.10.1-1 \
patchelf=0.14.3-1 \
python3 \
xz-utils; \
install -d -m 0755 /usr/share/keyrings; \
gpg --dearmor -o /usr/share/keyrings/lunarg.gpg /tmp/lunarg.asc; \
printf '%s\n' 'deb [signed-by=/usr/share/keyrings/lunarg.gpg] https://packages.lunarg.com/vulkan jammy main' \
> /etc/apt/sources.list.d/lunarg-vulkan.list; \
apt-get update; \
apt-get install --no-install-recommends -y \
libvulkan-dev=1.4.313.0~rc1-1lunarg22.04-1 \
vulkan-headers=1.4.313.0~rc1-1lunarg22.04-1 \
shaderc=2025.2~rc1-1lunarg22.04-1 \
spirv-headers=1.6.1+1.4.313.0~rc1-1lunarg22.04-1; \
curl --fail --location --retry 3 \
"https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz" \
-o /tmp/cmake.tar.gz; \
test "$(sha256sum /tmp/cmake.tar.gz | cut -d' ' -f1)" = "$CMAKE_SHA256"; \
tar -xzf /tmp/cmake.tar.gz --strip-components=1 -C /usr/local; \
rm -rf /var/lib/apt/lists/* /tmp/cmake.tar.gz /tmp/lunarg.asc; \
cmake --version; \
glslc --version; \
test -f /usr/include/vulkan/vulkan.h; \
test -f /usr/share/cmake/SPIRV-Headers/SPIRV-HeadersConfig.cmake
WORKDIR /work
@@ -0,0 +1,6 @@
FROM ubuntu@sha256:2edbbc5dc405e9612ba3584ce95480277e3eb374407b5505fe26f17df77c7dbc
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install --no-install-recommends -y ca-certificates curl libstdc++6 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /bundle
@@ -0,0 +1,7 @@
FROM ubuntu@sha256:2edbbc5dc405e9612ba3584ce95480277e3eb374407b5505fe26f17df77c7dbc
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install --no-install-recommends -y \
ca-certificates curl libstdc++6 libvulkan1 mesa-vulkan-drivers vulkan-tools \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /bundle
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
: "${SOURCE_DIR:=/src}"
: "${OUT_DIR:=/work/out}"
: "${WHISPER_VERSION:=1.9.3}"
: "${WHISPER_COMMIT:=371b5a7561823ab2bb32142d2751e35e7534727b}"
: "${SOURCE_DATE_EPOCH:=1787219223}"
export SOURCE_DATE_EPOCH TZ=UTC LC_ALL=C LANG=C
asset=whisper-bin-ubuntu-vulkan-x64
build=/work/build
source_copy=/work/source
root="$OUT_DIR/root/$asset"
rm -rf "$build" "$source_copy" "$OUT_DIR"
mkdir -p "$build" "$root/LICENSES"
# Upstream configures bindings/javascript/package.json in the source directory.
# Build a private copy so the checked-out, verified source remains untouched.
cp -a "$SOURCE_DIR" "$source_copy"
chmod -R u+w "$source_copy"
git config --global --add safe.directory "$source_copy"
cmake -S "$source_copy" -B "$build" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_BUILD_RPATH='$ORIGIN' \
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DCMAKE_C_FLAGS="-ffile-prefix-map=$source_copy=. -fdebug-prefix-map=$source_copy=. -fmacro-prefix-map=$source_copy=." \
-DCMAKE_CXX_FLAGS="-ffile-prefix-map=$source_copy=. -fdebug-prefix-map=$source_copy=. -fmacro-prefix-map=$source_copy=." \
-DBUILD_SHARED_LIBS=ON \
-DGGML_BACKEND_DL=ON \
-DGGML_CPU_ALL_VARIANTS=ON \
-DGGML_NATIVE=OFF \
-DGGML_CCACHE=OFF \
-DGGML_OPENMP=OFF \
-DGGML_VULKAN=ON \
-DWHISPER_BUILD_EXAMPLES=ON \
-DWHISPER_BUILD_SERVER=ON \
-DWHISPER_BUILD_TESTS=OFF \
-DWHISPER_BUILD_IS_DEV=OFF \
-DWHISPER_CURL=OFF \
-DWHISPER_SDL2=OFF \
-DWHISPER_COMMON_FFMPEG=OFF \
-DWHISPER_BUILD_COMMIT="$WHISPER_COMMIT" \
-DWHISPER_BUILD_NUMBER=0
cmake --build "$build" --target whisper-server --parallel "$(nproc)"
# Package an allowlist, not everything examples/ happens to build in the future.
cp -a "$build/bin/whisper-server" "$root/"
cp -a "$build/bin"/libwhisper.so* "$root/"
cp -a "$build/bin"/libggml.so* "$root/"
cp -a "$build/bin"/libggml-base.so* "$root/"
cp -a "$build/bin"/libggml-cpu*.so* "$root/"
cp -a "$build/bin"/libggml-vulkan.so* "$root/"
# Strip real ELF files only; preserve the SONAME symlink chains.
while IFS= read -r -d '' file; do
if file "$file" | grep -q ELF; then
strip --strip-unneeded "$file"
patchelf --set-rpath '$ORIGIN' "$file"
fi
done < <(find "$root" -type f -print0)
cp "$SOURCE_DIR/LICENSE" "$root/LICENSES/whisper.cpp-MIT.txt"
cp /packaging/licenses/cpp-httplib-MIT.txt "$root/LICENSES/"
cp /packaging/licenses/nlohmann-json-MIT.txt "$root/LICENSES/"
cat > "$root/BUILD-INFO.json" <<EOF
{
"asset": "$asset.tar.gz",
"source": "https://github.com/ggml-org/whisper.cpp",
"source_version": "v$WHISPER_VERSION",
"source_commit": "$WHISPER_COMMIT",
"source_date_epoch": $SOURCE_DATE_EPOCH,
"build_platform": "ubuntu-22.04-x86_64",
"base_image": "ubuntu@sha256:2edbbc5dc405e9612ba3584ce95480277e3eb374407b5505fe26f17df77c7dbc",
"cmake": "3.31.6",
"cmake_flags": [
"BUILD_SHARED_LIBS=ON",
"C/CXX_FILE_PREFIX_MAP=/work/source=.",
"GGML_BACKEND_DL=ON",
"GGML_CPU_ALL_VARIANTS=ON",
"GGML_NATIVE=OFF",
"GGML_CCACHE=OFF",
"GGML_OPENMP=OFF",
"GGML_VULKAN=ON",
"WHISPER_BUILD_EXAMPLES=ON",
"WHISPER_BUILD_SERVER=ON",
"WHISPER_BUILD_TESTS=OFF",
"WHISPER_BUILD_IS_DEV=OFF",
"WHISPER_CURL=OFF",
"WHISPER_SDL2=OFF",
"WHISPER_COMMON_FFMPEG=OFF"
],
"runtime_contract": {
"minimum_glibc": "2.34",
"minimum_glibcxx": "3.4.30",
"required": ["x86_64 Linux", "glibc", "libstdc++.so.6", "libgcc_s.so.1"],
"optional_gpu": ["libvulkan.so.1", "a working Vulkan ICD"],
"cpu_fallback": "dynamic CPU backends are included; -ng forces CPU"
}
}
EOF
# A deterministic CycloneDX sidecar generated from the files actually shipped.
ROOT="$root" VERSION="$WHISPER_VERSION" COMMIT="$WHISPER_COMMIT" EPOCH="$SOURCE_DATE_EPOCH" \
python3 /packaging/make-sbom.py > "$root/$asset.cdx.json"
(
cd "$root"
find . -type f ! -name SHA256SUMS -print0 \
| sort -z \
| xargs -0 sha256sum
) > "$root/SHA256SUMS"
mkdir -p "$OUT_DIR"
tar --sort=name --owner=0 --group=0 --numeric-owner \
--mtime="@$SOURCE_DATE_EPOCH" \
--pax-option=delete=atime,delete=ctime \
-C "$OUT_DIR/root" -cf - "$asset" \
| gzip -n -9 > "$OUT_DIR/$asset.tar.gz"
(
cd "$OUT_DIR"
sha256sum "$asset.tar.gz" > "$asset.tar.gz.sha256"
)
cp "$root/$asset.cdx.json" "$OUT_DIR/$asset.cdx.json"
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 yhirose
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2022 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,31 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFuOrjYBCADT5MjShtbeSsWHADqVP7PZIp+m/wWkSUA7/FX/qrixhQE9DFyt
XtKSbBdwh+Jg5nsttCUiePtdrrRD1tcyowG256Tus3vOysZzvpfjWA4gcVmTjJXn
gwezKsPZLQi0wvjwQD8ByxnM1i2eiJC4xcMjT21uZkwDfgLTzVO4InWlVyZDB/da
PLJl4r1MqnsI603RKalMQmZzs43YUDssdeOiGOpXvb1Rj0XcsOOqnAEvIwyUWGku
1Hr+b6C9Nj6wksD7TCB10IdOeuwBqFgrVDzicG4fijwnpzA+UUfncIhKYdI/oIvj
mcAPobWzcBkM3uc+Yf/CxlBahzu6jv7AFdT1ABEBAAG0VEx1bmFyRyBTaWduaW5n
IEtleSAoS2V5IHVzZWQgYnkgTHVuYXJHIHRvIHNpZ24gcGFja2FnZXMpIDxsaW51
eC1wYWNrYWdlc0BsdW5hcmcuY29tPokBTgQTAQoAOBYhBAP11iGjcQ+pWpPYm6qE
UggOOD9+BQJbjq42AhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEKqEUggO
OD9+ECgH/Ro6LVB08FifApBS235v0Af3dsJlZGE0miKu2hR12qAvWackE6//E5GN
5xKSNpgLzV6kyylBntQDhcFzW3hLt/AsMLOXvuxYNFcLes2y10DrqVekNeJiR95V
KiTPI2jP8m4eFpcSnY0riHk2MmstN1icehQhYrWFyUtt3VxSsRWiRDeNUfCHC6YP
MjOXonmTWfH7T+UA2IqLFrt9dAsYGiCtMKVgzaZaZwm727c0aqy0e43nsWqjWxmE
EsEA1RvzjKKyzyixwpnzIyQ8dqL8sH0G3E2OYTlS7A8//yfgykRQVHwg2TsTBKfG
LlTmKj7RCT6GqISo+rbYYo/hZ6l2hH25AQ0EW46uNgEIANZfPWerTPzmvswWqp0P
iQvW+0qTBxZH3gQlwq5s6ahpY1pIebfrL/SAYJUGyjJVcjkG+HBXRGyRxtWFDE+D
+WEuziBfKd3aBUXb5DnvWdCiXeyQnFfwUVYNXhU5PlpAB5M409a30p9gGOrYy3Ah
g4VHhpM9wzGUAOzTwQ4WaC2WkR84sZYyqdKoo6C3m4IR4KHMYXF9nRlPSNEckL9U
MZe6I2uvor9FOPIfIOAI8lN+gbj/anf3lfy0ZYPyUtl3EWveGpWAPvdw3LMKg5QN
B8bR9TkPk0YZyQQcWkmN7gLUg0Vba+PYHH9DRlG8w1rH4TKxXJV3wmHo2aZRF1kc
30kAEQEAAYkBNgQYAQoAIBYhBAP11iGjcQ+pWpPYm6qEUggOOD9+BQJbjq42AhsM
AAoJEKqEUggOOD9+MEUH/2pm2QOttjd7DmEaS4LGvaTlEif0xtymRAh3axGuqQhl
KCZbw0jwsQlo/DwMRZwZHYCj1A/5H8mEg9qNGjF35GEpQTFSQI6Mt7F2DK69J86w
61v8tjxs4eO201ndhy+DRwDwG8vryFldx3f0nEdlE7IusgiUdvkcJPc8rX7p0MJJ
istTREAq8bRnvWYJzd4k3tgwHglEDxyjBRwLtqZyQ19XZb3V/aVKygqvZbwdJyXO
RHAZxK81p9Gp/8VkogJHLx6+3V8UlDepJg9/8MUCBQ9wWkdF0Pfqzgu7xtIHSxvW
62EF4nxqVuC946OIeITgXpd4F+iTFVII8w0P+nyCzac=
=nXAe
-----END PGP PUBLIC KEY BLOCK-----
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
import datetime
import hashlib
import json
import os
import uuid
from pathlib import Path
root = Path(os.environ["ROOT"])
version = os.environ["VERSION"]
commit = os.environ["COMMIT"]
epoch = int(os.environ["EPOCH"])
asset = "whisper-bin-ubuntu-vulkan-x64"
sbom_path = root / f"{asset}.cdx.json"
def digest(path):
h = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
files = []
for path in sorted(root.rglob("*")):
if path != sbom_path and path.is_file() and not path.is_symlink():
rel = path.relative_to(root).as_posix()
files.append({
"type": "file",
"bom-ref": f"file:{rel}",
"name": rel,
"hashes": [{"alg": "SHA-256", "content": digest(path)}],
})
ts = datetime.datetime.fromtimestamp(
epoch, datetime.timezone.utc,
).isoformat().replace("+00:00", "Z")
root_ref = f"pkg:github/ggml-org/whisper.cpp@{version}?commit={commit}"
ggml_ref = "pkg:github/ggml-org/[email protected]"
httplib_ref = "pkg:github/yhirose/[email protected]"
json_ref = "pkg:github/nlohmann/[email protected]"
sbom = {
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": f"urn:uuid:{uuid.uuid5(uuid.NAMESPACE_URL, root_ref)}",
"version": 1,
"metadata": {
"timestamp": ts,
"tools": {"components": [
{"type": "application", "name": "make-sbom.py", "version": "1"},
{"type": "application", "name": "CMake", "version": "3.31.6"},
{"type": "application", "name": "glslc", "version": "2025.2"},
]},
"component": {
"type": "application",
"bom-ref": root_ref,
"group": "ggml-org",
"name": "whisper-server",
"version": version,
"purl": root_ref,
"licenses": [{"expression": "MIT"}],
"externalReferences": [{
"type": "vcs",
"url": f"https://github.com/ggml-org/whisper.cpp/tree/{commit}",
}],
"properties": [
{"name": "dikte:asset-name", "value": f"{asset}.tar.gz"},
{"name": "dikte:source-commit", "value": commit},
{"name": "dikte:runtime:glibc-minimum", "value": "2.34"},
{"name": "dikte:runtime:glibcxx-minimum", "value": "3.4.30"},
{"name": "dikte:runtime:vulkan-loader", "value": "optional; libvulkan.so.1"},
],
},
},
"components": [
{
"type": "library",
"bom-ref": ggml_ref,
"group": "ggml-org",
"name": "ggml",
"version": "0.20.2",
"purl": ggml_ref,
"licenses": [{"expression": "MIT"}],
"properties": [{
"name": "dikte:source",
"value": "vendored by the pinned whisper.cpp commit",
}],
},
{
"type": "library",
"bom-ref": httplib_ref,
"group": "yhirose",
"name": "cpp-httplib",
"version": "0.20.0",
"purl": httplib_ref,
"licenses": [{"expression": "MIT"}],
},
{
"type": "library",
"bom-ref": json_ref,
"group": "nlohmann",
"name": "json",
"version": "3.11.2",
"purl": json_ref,
"licenses": [{"expression": "MIT"}],
},
*files,
],
"dependencies": [{
"ref": root_ref,
"dependsOn": [ggml_ref, httplib_ref, json_ref]
+ [item["bom-ref"] for item in files],
}],
}
json.dump(sbom, fp=os.sys.stdout, indent=2, sort_keys=True)
print()
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
mode=${1:?usage: smoke-runtime.sh cpu|vulkan}
: "${OUT_DIR:=work/out}"
: "${FIXTURE_SOURCE:=vendor/whisper.cpp}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="$(realpath "$OUT_DIR")"
FIXTURE_SOURCE="$(realpath "$FIXTURE_SOURCE")"
asset=whisper-bin-ubuntu-vulkan-x64
case "$mode" in
cpu) dockerfile=Dockerfile.runtime-cpu; image=dikte-whisper-runtime-cpu:spike ;;
vulkan) dockerfile=Dockerfile.runtime-vulkan; image=dikte-whisper-runtime-vulkan:spike ;;
*) echo "unknown mode: $mode" >&2; exit 2 ;;
esac
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
tar -xzf "$OUT_DIR/$asset.tar.gz" -C "$tmp"
docker build --pull=false -f "$SCRIPT_DIR/$dockerfile" -t "$image" "$SCRIPT_DIR"
args=("/bundle/$asset/whisper-server" -m /fixtures/model.bin
--host 127.0.0.1 --port 8080
--inference-path /v1/audio/transcriptions -l auto -sns -nlp)
env_args=()
if [[ "$mode" == cpu ]]; then
args+=(-ng)
else
env_args=(-e LIBGL_ALWAYS_SOFTWARE=1
-e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json)
fi
docker run --rm --name "dikte-whisper-$mode-smoke" \
-e SMOKE_MODE="$mode" \
"${env_args[@]}" \
-v "$tmp/$asset:/bundle/$asset:ro" \
-v "$FIXTURE_SOURCE/models/for-tests-ggml-base.en.bin:/fixtures/model.bin:ro" \
-v "$FIXTURE_SOURCE/samples/jfk.wav:/fixtures/jfk.wav:ro" \
"$image" bash -ec '
if [ "$SMOKE_MODE" = cpu ] && ldconfig -p | grep -q libvulkan.so.1; then
echo "CPU smoke image unexpectedly has a Vulkan loader" >&2
exit 1
fi
"$@" >/tmp/server.log 2>&1 &
pid=$!
trap "kill $pid 2>/dev/null || true" EXIT
for _ in $(seq 1 120); do
kill -0 "$pid" 2>/dev/null || { cat /tmp/server.log; exit 1; }
if curl --silent --show-error --fail --max-time 180 \
-F file=@/fixtures/jfk.wav -F response_format=json \
http://127.0.0.1:8080/v1/audio/transcriptions >/tmp/response.json; then
grep -q "\"text\"" /tmp/response.json
if [ "$SMOKE_MODE" = vulkan ]; then
grep -q "loaded Vulkan backend" /tmp/server.log
fi
cat /tmp/response.json
cat /tmp/server.log
exit 0
fi
sleep 1
done
cat /tmp/server.log
exit 1
' bash "${args[@]}"
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env bash
set -euo pipefail
: "${OUT_DIR:=work/out}"
: "${SOURCE_DIR:=whisper.cpp}"
asset=whisper-bin-ubuntu-vulkan-x64
archive="$OUT_DIR/$asset.tar.gz"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
test -s "$archive"
(cd "$OUT_DIR" && sha256sum --check "$asset.tar.gz.sha256")
ARCHIVE="$archive" ASSET="$asset" python3 - <<'PY'
import os
import posixpath
import tarfile
archive = os.environ["ARCHIVE"]
asset = os.environ["ASSET"]
def under_root(name):
normalized = posixpath.normpath(name)
return (not posixpath.isabs(normalized)
and normalized != ".."
and not normalized.startswith("../")
and normalized.split("/", 1)[0] == asset)
with tarfile.open(archive, "r:gz") as bundle:
for member in bundle:
if not under_root(member.name):
raise SystemExit(f"unsafe archive member: {member.name}")
if member.isdev() or member.isfifo():
raise SystemExit(f"special archive member: {member.name}")
if not (member.isdir() or member.isfile()
or member.issym() or member.islnk()):
raise SystemExit(f"unsupported archive member: {member.name}")
if member.issym():
target = posixpath.join(posixpath.dirname(member.name),
member.linkname)
if not under_root(target):
raise SystemExit(f"unsafe symlink: {member.name}")
if member.islnk() and not under_root(member.linkname):
raise SystemExit(f"unsafe hardlink: {member.name}")
PY
tar -xzf "$archive" -C "$tmp"
root="$tmp/$asset"
test -x "$root/whisper-server"
test -f "$root/libwhisper.so"
test -f "$root/libggml.so"
test -f "$root/libggml-base.so"
test -f "$root/libggml-vulkan.so"
compgen -G "$root/libggml-cpu-*.so" >/dev/null
test -f "$root/LICENSES/whisper.cpp-MIT.txt"
test -f "$root/LICENSES/cpp-httplib-MIT.txt"
test -f "$root/LICENSES/nlohmann-json-MIT.txt"
(cd "$root" && sha256sum --check SHA256SUMS)
# All shipped ELF objects must be relocatable and must not remember /work.
while IFS= read -r -d '' file; do
file "$file" | grep -q ELF || continue
dynamic=$(readelf -d "$file")
if ! grep -Fq 'Library runpath: [$ORIGIN]' <<<"$dynamic"; then
echo "runpath is not \$ORIGIN in $file" >&2
exit 1
fi
if grep -Eq '/(home|tmp|work)/' <<<"$dynamic"; then
echo "build path remains in $file" >&2
exit 1
fi
done < <(find "$root" -type f -print0)
# Vulkan remains a plugin dependency. The executable must start without a loader.
if readelf -d "$root/whisper-server" | grep -q 'libvulkan.so'; then
echo "whisper-server links Vulkan instead of loading it as a plugin" >&2
exit 1
fi
readelf -d "$root/libggml-vulkan.so" | grep -q 'libvulkan.so.1'
# Ubuntu 22.04 establishes the glibc ceiling promised by this artifact.
ROOT="$root" python3 - <<'PY'
import os, pathlib, re, subprocess
root = pathlib.Path(os.environ['ROOT'])
seen = {'GLIBC': set(), 'GLIBCXX': set(), 'CXXABI': set()}
external = {
'libc.so.6', 'libgcc_s.so.1', 'libm.so.6', 'libstdc++.so.6',
'libvulkan.so.1', 'ld-linux-x86-64.so.2',
}
for path in root.iterdir():
if not path.is_file() or path.is_symlink():
continue
header = subprocess.run(['readelf', '-h', path], text=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL).stdout
if not header:
continue
if 'Machine: Advanced Micro Devices X86-64' not in header:
raise SystemExit(f'wrong ELF architecture: {path.name}')
dynamic = subprocess.run(['readelf', '-d', path], text=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL).stdout
needed = re.findall(r'\(NEEDED\).*\[(.*?)\]', dynamic)
unexpected = [name for name in needed
if name not in external
and not re.fullmatch(
r'lib(?:whisper|ggml(?:-base)?)\.so\.\d+', name)]
if unexpected:
raise SystemExit(
f'unexpected DT_NEEDED in {path.name}: {unexpected}')
if path.name != 'libggml-vulkan.so' and 'libvulkan.so.1' in needed:
raise SystemExit(f'Vulkan is not plugin-only in {path.name}')
contents = path.read_bytes()
for marker in (b'/home/', b'/tmp/', b'/work/'):
if marker in contents:
raise SystemExit(
f'build path {marker!r} remains in {path.name}')
text = subprocess.run(['objdump', '-T', path], text=True,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout
for family in seen:
pattern = rf'{family}_([0-9]+(?:\.[0-9]+)+)'
seen[family].update(tuple(map(int, version.split('.')))
for version in re.findall(pattern, text))
assert seen['GLIBC'] and max(seen['GLIBC']) <= (2, 34), max(seen['GLIBC'])
assert seen['GLIBCXX'] and max(seen['GLIBCXX']) <= (3, 4, 30), max(seen['GLIBCXX'])
assert seen['CXXABI'] and max(seen['CXXABI']) <= (1, 3, 13), max(seen['CXXABI'])
for family, versions in seen.items():
print(f'maximum {family} symbol:', '.'.join(map(str, max(versions))))
PY
python3 - "$root/$asset.cdx.json" <<'PY'
import json, sys
with open(sys.argv[1], encoding='utf-8') as stream:
doc = json.load(stream)
assert doc['bomFormat'] == 'CycloneDX'
assert doc['specVersion'] == '1.6'
assert doc['metadata']['component']['name'] == 'whisper-server'
assert len(doc['components']) >= 3
print('SBOM components:', len(doc['components']))
PY
LD_LIBRARY_PATH='' "$root/whisper-server" --help >/dev/null 2>&1
echo "structure: PASS"