Repository navigation
208 lines (187 loc) · 8.87 KB
/
Copy pathci.yml
File metadata and controls
208 lines (187 loc) · 8.87 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
208
name: ci
on:
push:
pull_request:
# closed-loop job runs on pull_request (gate) and manual dispatch (see below).
workflow_dispatch:
jobs:
# -------------------------------------------------------------------------
# build: configure, compile, run model-independent unit tests.
# Runs on every push and pull_request. Never downloads a model — fast gate.
# -------------------------------------------------------------------------
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: configure
# GGML_NATIVE=OFF for portability in CI (no host-cpu ISA extensions)
run: cmake -B build -DPARAKEET_BUILD_TESTS=ON -DGGML_NATIVE=OFF
- name: build
run: cmake --build build -j
- name: test (model-independent only)
# -LE model excludes the model-labelled tests (check_convert,
# test_model_loader, check_baseline) that need the Python venv and the
# cached NeMo checkpoint. Those will run in a separate workflow_dispatch
# job once a models bundle is published (Phase 4).
run: ctest --test-dir build --output-on-failure -LE model
- name: build without ced and voice-detect (PARAKEET_WITH_CED=OFF, PARAKEET_WITH_VOICEDETECT=OFF)
# PARAKEET_WITH_CED and PARAKEET_WITH_VOICEDETECT are on by default, so
# this is the gate that catches anything that quietly starts depending
# on ced.cpp or voice-detect.cpp being present.
run: |
cmake -B build-noced -DPARAKEET_BUILD_TESTS=ON -DGGML_NATIVE=OFF -DPARAKEET_WITH_CED=OFF -DPARAKEET_WITH_VOICEDETECT=OFF
cmake --build build-noced -j
ctest --test-dir build-noced --output-on-failure -LE model
# scene --sound must fail cleanly (exit 2) with a clear message.
# Capture first: steps run with -e -o pipefail.
rc=0
out=$(build-noced/examples/cli/parakeet-cli scene --sound x --input y 2>&1) || rc=$?
echo "$out"
test "$rc" -eq 2
grep -q "built without sound tagging" <<< "$out"
# enroll must fail the same way without voice-detect.cpp.
rc=0
out=$(build-noced/examples/cli/parakeet-cli enroll --model x.gguf --name a --input x.wav --registry /tmp/r.bin 2>&1) || rc=$?
echo "$out"
test "$rc" -eq 2
grep -q "built without speaker identification" <<< "$out"
# -------------------------------------------------------------------------
# server-e2e: drive the real parakeet-server over HTTP.
#
# Runs on pull_request (merge gate) and manual workflow_dispatch. Not on every
# push: it downloads the ~125 MB tdt_ctc-110m-q4_k model via the alias path.
# Much lighter than closed-loop (no NeMo/Python venv) — it builds the server,
# then tests/server_e2e.sh starts it, transcribes tests/fixtures/speech.wav in
# json/text/verbose_json (with word timestamps), and checks the 400 paths.
# -------------------------------------------------------------------------
server-e2e:
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake build-essential curl ca-certificates
- name: Configure
run: |
cmake -B build \
-DPARAKEET_BUILD_SERVER=ON \
-DPARAKEET_BUILD_TESTS=ON \
-DGGML_NATIVE=OFF
- name: Build parakeet-server
run: cmake --build build --target parakeet-server -j
- name: Run server e2e
# PARAKEET_SERVER_E2E=1 flips the test from skip (77) to a real run.
env:
PARAKEET_SERVER_E2E: "1"
# First-run model download speed varies on hosted runners; the job
# timeout remains the outer cap.
PARAKEET_SERVER_E2E_READY_TIMEOUT: "600"
run: ctest --test-dir build --output-on-failure -R '^server_e2e$'
# -------------------------------------------------------------------------
# closed-loop: full end-to-end transcript assertion.
#
# Runs on pull_request (merge gate) and manual workflow_dispatch. Not on every
# push, since this job downloads nvidia/parakeet-tdt_ctc-110m (~440 MB via
# NeMo/HuggingFace Hub) and installs the nemo_toolkit Python env (~60 min).
#
# What it does:
# 1. Check out the repo (submodules recursive).
# 2. Install system build deps + Python 3.12 + pip.
# 3. Create a venv and install CPU torch + scripts/requirements.txt
# (nemo_toolkit[asr] + gguf) — same setup as the developer guide.
# 4. Build the project (GGML_NATIVE=OFF for runner portability,
# PARAKEET_BUILD_TESTS=ON, PARAKEET_BUILD_CLI=ON).
# 5. Convert nvidia/parakeet-tdt_ctc-110m → /tmp/pk110m_cl.gguf
# (F32, default dtype; NeMo auto-downloads the checkpoint on first use).
# 6. Run `parakeet-cli transcribe` on tests/fixtures/speech.wav with
# --decoder tdt (the TDT head of the hybrid model).
# 7. Assert the output matches the committed NeMo reference transcript:
# "Well, I don't wish to see it any more, observed Phoebe,
# turning away her eyes. It is certainly very like the old portrait."
# Fail the job if it doesn't match.
# -------------------------------------------------------------------------
closed-loop:
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout (with submodules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install system build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake build-essential python3 python3-venv python3-pip
- name: Set up Python venv
# CPU-only torch wheel; nemo_toolkit[asr] + gguf from requirements.txt.
# Mirror the developer setup documented in AGENTS.md / README.md.
run: |
python3 -m venv .venv
.venv/bin/pip install --quiet --upgrade pip
.venv/bin/pip install --quiet torch --index-url https://download.pytorch.org/whl/cpu
.venv/bin/pip install --quiet -r scripts/requirements.txt
- name: Configure CMake
run: |
cmake -B build \
-DPARAKEET_BUILD_TESTS=ON \
-DPARAKEET_BUILD_CLI=ON \
-DGGML_NATIVE=OFF
- name: Build
run: cmake --build build -j
- name: Convert nvidia/parakeet-tdt_ctc-110m to GGUF
# NeMo downloads the ~440 MB checkpoint from HuggingFace on first use.
# The resulting GGUF is ~438 MB (F32).
run: |
.venv/bin/python scripts/convert_parakeet_to_gguf.py \
--model nvidia/parakeet-tdt_ctc-110m \
--output /tmp/pk110m_cl.gguf
- name: Generate NeMo TDT N-best reference
run: |
.venv/bin/python scripts/gen_nemo_baseline.py \
--model nvidia/parakeet-tdt_ctc-110m \
--audio tests/fixtures/speech.wav \
--tdt-beam-size 4 \
--output /tmp/tdt_nbest_baseline.gguf
- name: Assert TDT N-best parity with NeMo
env:
PARAKEET_TEST_GGUF: /tmp/pk110m_cl.gguf
PARAKEET_TEST_TDT_NBEST_BASELINE: /tmp/tdt_nbest_baseline.gguf
run: |
ctest --test-dir build --output-on-failure -R '^test_tdt_beam$'
- name: Transcribe speech fixture with TDT decoder
run: |
./build/examples/cli/parakeet-cli transcribe \
--model /tmp/pk110m_cl.gguf \
--input tests/fixtures/speech.wav \
--decoder tdt \
> /tmp/cl_transcript.txt
echo "--- transcript ---"
cat /tmp/cl_transcript.txt
- name: Assert transcript matches NeMo reference
# Reference transcript (TDT head, hybrid 110m) committed to docs/parity.md
# and validated at WER 0.0 vs NeMo 2.7.3 (see docs/parity.md § Phase 3).
run: |
EXPECTED="Well, I don't wish to see it any more, observed Phoebe, turning away her eyes. It is certainly very like the old portrait."
ACTUAL="$(cat /tmp/cl_transcript.txt)"
python3 - <<'PYEOF'
import sys
expected = "Well, I don't wish to see it any more, observed Phoebe, turning away her eyes. It is certainly very like the old portrait."
with open("/tmp/cl_transcript.txt") as f:
actual = f.read().strip()
if actual == expected:
print("PASS: transcript matches NeMo reference (WER 0.0)")
sys.exit(0)
else:
print("FAIL: transcript mismatch")
print(f" expected: {expected!r}")
print(f" actual: {actual!r}")
sys.exit(1)
PYEOF