-
Notifications
You must be signed in to change notification settings - Fork 0
207 lines (189 loc) · 8.53 KB
/
Copy pathcontainer.yml
File metadata and controls
207 lines (189 loc) · 8.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
name: Container
# Building the image compiles whisper.cpp from source, which is by far the most
# expensive thing in CI. Application behavior is covered by the Quality
# workflow; this one runs on the container definition and dependency inputs
# that decide whether the image itself assembles. Its CPU smoke test then proves
# the installed package starts and serves from that image.
#
# One Dockerfile now builds all three accelerator images, so a change to it can
# break cuda or vulkan while cpu still succeeds. The matrix below builds each
# one. They are the reason this belongs in CI rather than on a laptop. A
# portable cuda image compiles kernels for a broad GPU architecture spread; the
# matrix narrows its compile-only validation to one architecture because no GPU
# executes the cache-only result. Release/operator builds keep the portable
# Dockerfile default.
on:
push:
branches: [main]
paths: &container-paths
- 'Dockerfile'
- '.dockerignore'
- 'compose.yaml'
- 'pyproject.toml'
- 'uv.lock'
- '.github/workflows/container.yml'
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
paths: *container-paths
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-24.04
timeout-minutes: ${{ matrix.timeout }}
strategy:
# One accelerator failing says nothing about the others, and the cuda job
# is long enough that cancelling it on a fast vulkan failure wastes the
# whole run.
fail-fast: false
matrix:
include:
# The cpu image is the one every deployment gets by default, so it is
# also the one that is loaded and actually run below.
- accel: cpu
timeout: 40
smoke: true
reclaim-disk: false
build_jobs: 4
cmake_extra: ''
- accel: vulkan
timeout: 40
smoke: false
reclaim-disk: false
build_jobs: 4
cmake_extra: ''
# The CUDA devel/runtime bases and Python engine layers are still much
# larger than the other variants, so reclaim the runner's unused SDKs.
- accel: cuda
timeout: 40
smoke: false
reclaim-disk: true
build_jobs: 3
# Compiling every CUDA architecture dominates the job despite the
# image never running on a GPU. Ada is a representative real-code
# compile; CPU dispatch is already built and exercised above.
cmake_extra: >-
-DCMAKE_CUDA_ARCHITECTURES=89-real
-DGGML_CPU_ALL_VARIANTS=OFF
name: build (${{ matrix.accel }})
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Reclaim runner disk space
if: matrix.reclaim-disk
run: |
# Preinstalled SDKs this build never touches. Worth ~25 GB, which is
# the difference between the cuda image building and the runner
# filling up partway through.
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
sudo rm -rf /usr/local/share/boost "${AGENT_TOOLSDIRECTORY}"
df -h /
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Build ${{ matrix.accel }} image
run: |
# A smoke-tested image has to reach the local daemon; the others only
# need to prove they assemble, so they stop at the cache.
if [ "${SMOKE}" = "true" ]; then
output="type=docker"
else
output="type=cacheonly"
fi
# Only the sha is stamped here: a commit subject is attacker-supplied
# text on a pull request, and expanding it into this shell would be an
# injection. Release builds that publish an image add the subject from
# a checkout instead.
docker buildx build \
--tag "vocagateway:test-${ACCEL}" \
--build-arg "ACCEL=${ACCEL}" \
--build-arg "BUILD_JOBS=${BUILD_JOBS}" \
--build-arg "WHISPER_CMAKE_EXTRA=${CMAKE_EXTRA}" \
--build-arg VOCAGATEWAY_GIT_COMMIT="${GITHUB_SHA}" \
--cache-from "type=gha,scope=gateway-${ACCEL}" \
--cache-to "type=gha,mode=max,scope=gateway-${ACCEL}" \
--output "${output}" \
.
env:
ACCEL: ${{ matrix.accel }}
BUILD_JOBS: ${{ matrix.build_jobs }}
CMAKE_EXTRA: ${{ matrix.cmake_extra }}
SMOKE: ${{ matrix.smoke }}
# Everything below is the cpu image only. It is what a laptop build was
# being used to check by hand, and it catches the failures a successful
# `docker build` does not: a backend that is present but never loaded, a
# wheel that is missing its templates, a container that cannot start under
# the hardening compose applies.
- name: Check ggml loads a CPU backend variant
if: matrix.smoke
run: |
set -euo pipefail
curl --fail --location --silent --show-error \
--output jfk.wav \
https://raw.githubusercontent.com/ggml-org/whisper.cpp/v1.9.1/samples/jfk.wav
curl --fail --location --silent --show-error \
--output ggml-tiny.en.bin \
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin
docker run --rm --volume "${PWD}:/bench:ro" \
--entrypoint whisper-cli vocagateway:test-cpu \
-m /bench/ggml-tiny.en.bin -f /bench/jfk.wav -nt -t 2 -bs 2 -bo 2 \
> transcript.txt 2> backend.log || { cat backend.log; exit 1; }
grep 'load_backend: loaded CPU backend' backend.log
# GGML_CPU_ALL_VARIANTS exists so a portable image still runs the
# host's instruction set. A GitHub runner is well past Haswell, so
# loading the x64 baseline means dispatch silently regressed and every
# deployment is running scalar kernels.
if grep -q 'libggml-cpu-x64\.so' backend.log; then
echo "::error::ggml fell back to the baseline x64 CPU backend" >&2
cat backend.log >&2
exit 1
fi
grep -qi 'ask not what your country can do for you' transcript.txt
- name: Check the container serves under compose's hardening
if: matrix.smoke
run: |
set -euo pipefail
# The same restrictions compose.yaml applies, so a capability the
# gateway turns out to need fails here rather than on a deployment.
docker run --detach --name vg-smoke --publish 18765:8765 \
--cap-drop ALL \
--security-opt no-new-privileges:true \
--tmpfs /tmp:size=64m,mode=1777 \
vocagateway:test-cpu
for _ in $(seq 1 30); do
status="$(docker inspect --format '{{.State.Health.Status}}' vg-smoke)"
[ "${status}" = "healthy" ] && break
[ "${status}" = "unhealthy" ] && break
sleep 2
done
if [ "${status}" != "healthy" ]; then
echo "::error::container never became healthy (${status})" >&2
docker logs vg-smoke >&2
exit 1
fi
curl --fail --silent --show-error --output /dev/null http://127.0.0.1:18765/health/live
# The WebUI renders Jinja templates from the installed wheel, so a 200
# here is what proves the non-editable install carried app/templates.
curl --fail --silent --show-error --output /dev/null http://127.0.0.1:18765/
- name: Container logs
if: failure() && matrix.smoke
run: docker logs vg-smoke || true
compose:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Validate every profile interpolates
run: |
# `just compose` checks the default service. The profile services carry
# the device wiring, so they need naming explicitly to be parsed.
VOCAGATEWAY_TOKEN=test-token-with-at-least-thirty-two-characters \
docker compose --profile cuda --profile vulkan config --quiet