forked from ggml-org/whisper.cpp
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathCMakeLists.txt
More file actions
955 lines (895 loc) · 47.2 KB
/
Copy pathCMakeLists.txt
File metadata and controls
955 lines (895 loc) · 47.2 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
cmake_minimum_required(VERSION 3.20)
project(parakeet-cpp LANGUAGES C CXX VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(PARAKEET_STANDALONE_DEFAULT ON)
else()
set(PARAKEET_STANDALONE_DEFAULT OFF)
endif()
option(PARAKEET_BUILD_LIBRARY "parakeet: build the parakeet library (linkage follows BUILD_SHARED_LIBS; defaults to STATIC when unset)" ON)
option(PARAKEET_BUILD_EXECUTABLES "parakeet: build the parakeet CLI" ${PARAKEET_STANDALONE_DEFAULT})
option(PARAKEET_BUILD_TESTS "parakeet: build per-stage numerical validation harnesses" ${PARAKEET_STANDALONE_DEFAULT})
option(PARAKEET_BUILD_EXAMPLES "parakeet: build standalone examples (live-mic, ...)" ${PARAKEET_STANDALONE_DEFAULT})
option(PARAKEET_INSTALL "parakeet: generate install rules" ON)
option(PARAKEET_USE_SYSTEM_GGML "parakeet: use system-installed GGML library" OFF)
option(PARAKEET_CCACHE "parakeet: use ccache as compiler launcher if available" ON)
option(PARAKEET_GGML_LIB_PREFIX "parakeet: rename bundled ggml libraries to libspeech-ggml-* (filename only; CMake target names and C symbols are unchanged). Prevents shared-library filename collisions with other ggml consumers in the same host process; the `speech-` prefix is shared with the rest of the QVAC speech stack (whisper, parakeet, chatterbox, supertonic, ...) so they vendor a single ggml file set. Has no effect when PARAKEET_USE_SYSTEM_GGML=ON." ON)
# ccache shaves ~30 % off iterative rebuild time on developer machines.
# Opt-out via -DPARAKEET_CCACHE=OFF for clean reproducibility runs.
# Scoped to parakeet's own targets via the helper below; ggml has
# its own GGML_CCACHE option which we leave alone so the two launchers
# don't double-up on the ggml subdirectory.
if (PARAKEET_CCACHE)
find_program(PARAKEET_CCACHE_PROGRAM ccache)
if (PARAKEET_CCACHE_PROGRAM)
message(STATUS "parakeet: using ccache at ${PARAKEET_CCACHE_PROGRAM} (parakeet targets only; ggml uses GGML_CCACHE)")
endif()
endif()
function(parakeet_apply_ccache target)
if (PARAKEET_CCACHE_PROGRAM)
set_target_properties(${target} PROPERTIES
C_COMPILER_LAUNCHER "${PARAKEET_CCACHE_PROGRAM}"
CXX_COMPILER_LAUNCHER "${PARAKEET_CCACHE_PROGRAM}"
)
endif()
endfunction()
if (CMAKE_CROSSCOMPILING)
if (NOT DEFINED GGML_NATIVE OR GGML_NATIVE)
message(STATUS "parakeet: cross-compiling to ${CMAKE_SYSTEM_NAME} -- "
"forcing GGML_NATIVE=OFF (ggml's ARM feature probes use try_run() "
"which CMake refuses in cross-compile mode). The EOU q8_0 precision "
"caveat from native macOS/Linux builds does not apply: mobile prebuilds "
"are per-arch and use the armv8-a baseline.")
set(GGML_NATIVE OFF CACHE BOOL "" FORCE)
endif()
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "Android")
if (NOT DEFINED GGML_BLAS OR GGML_BLAS)
set(GGML_BLAS OFF CACHE BOOL "" FORCE)
endif()
if (NOT DEFINED GGML_ACCELERATE OR GGML_ACCELERATE)
set(GGML_ACCELERATE OFF CACHE BOOL "" FORCE)
endif()
endif()
# Android default backend stack: dynamic loading of Vulkan + OpenCL +
# per-arch CPU variants. Mirrors the qvac llm-llamacpp Android config
# (see qvac-registry-vcpkg/ports/llama-cpp/portfile.cmake) so the
# parakeet prebuilds drop into the same `qvac__transcription-parakeet/`
# folder shape as the llamacpp ones: a `.bare` module + sibling
# `lib<prefix>ggml-{vulkan,opencl,cpu-android_armv*_*}.so` files that
# `ggml_backend_load_all_from_path()` discovers at runtime.
#
# Selection at runtime is centralised in `init_gpu_backend()`
# (src/parakeet_ctc.cpp): OpenCL when an Adreno 700+ device is
# present, Vulkan for every other GPU (non-Adreno, Adreno < 700,
# Mali, Xclipse, ...). No static GPU backend entry points are linked
# anywhere in libparakeet; the registry walk reaches the right
# backend in both GGML_BACKEND_DL=ON (Android prebuild) and
# GGML_BACKEND_DL=OFF (desktop dev) modes.
#
# Callers that have specific reasons to deviate (e.g. a desktop bring-
# up build that wants Vulkan only) can still override any of these
# at the cmake command line; we only set defaults that haven't already
# been provided.
if (CMAKE_SYSTEM_NAME STREQUAL "Android")
if (NOT DEFINED CACHE{GGML_BACKEND_DL})
set(GGML_BACKEND_DL ON CACHE BOOL "" FORCE)
endif()
if (NOT DEFINED CACHE{GGML_CPU_ALL_VARIANTS})
set(GGML_CPU_ALL_VARIANTS ON CACHE BOOL "" FORCE)
endif()
if (NOT DEFINED CACHE{GGML_CPU_REPACK})
set(GGML_CPU_REPACK ON CACHE BOOL "" FORCE)
endif()
if (NOT DEFINED CACHE{GGML_VULKAN})
set(GGML_VULKAN ON CACHE BOOL "" FORCE)
endif()
if (NOT DEFINED CACHE{GGML_OPENCL})
set(GGML_OPENCL ON CACHE BOOL "" FORCE)
endif()
# ggml-vulkan's coopmat / coopmat2 shader compile pulls in extensions
# that most Android Vulkan drivers don't expose; the upstream llama
# Android build disables both for the same reason.
if (NOT DEFINED CACHE{GGML_VULKAN_DISABLE_COOPMAT})
set(GGML_VULKAN_DISABLE_COOPMAT ON CACHE BOOL "" FORCE)
endif()
if (NOT DEFINED CACHE{GGML_VULKAN_DISABLE_COOPMAT2})
set(GGML_VULKAN_DISABLE_COOPMAT2 ON CACHE BOOL "" FORCE)
endif()
endif()
# Two related workarounds for clang-cl / MSVC builds on Windows. Both
# come from msys2 sneaking GCC-flavoured libraries onto CMake's search
# paths and then being mismatched against clang-cl-compiled translation
# units. Both are no-ops on MinGW (excluded) and on non-Windows.
#
# (1) libm. ggml/src/CMakeLists.txt does
# find_library(MATH_LIBRARY m)
# if (MATH_LIBRARY AND (NOT WIN32 OR NOT DEFINED ENV{ONEAPI_ROOT}))
# target_link_libraries(ggml-base PRIVATE m)
# and unconditionally links libm on Windows unless ONEAPI_ROOT is in
# the environment. With clang-cl / lld-link libm isn't a separate
# library (math is in the MSVC C runtime); find_library picks up a
# stray `libm.dll.a` from msys2 and lld-link fails with
# `could not open 'm.lib'`. Piggyback on ggml's existing escape hatch
# by setting ONEAPI_ROOT in the configure-time environment.
# `set(ENV{...})` affects only the running CMake configure process,
# which is the same scope ggml's check runs in.
#
# (2) OpenMP. CMake's FindOpenMP picks `-fopenmp=libomp` (LLVM-style)
# for clang-cl as the compile flag, but its `OpenMP_*_LIBRARIES`
# resolves to msys2's `libgomp.dll.a` if msys2 is on PATH. The
# resulting object files reference LLVM `__kmpc_*` symbols that GOMP
# doesn't provide and lld-link errors out with `undefined symbol:
# __kmpc_global_thread_num` etc. Setting up clang-cl + libomp end
# to end on Windows requires per-environment knobs (LLVM's own
# `libomp.lib` location, OMP_PATH, runtime DLL on PATH, ...) that
# don't belong in the listfile. Disable OpenMP for this toolchain;
# the perf hit on the CPU encoder is bounded and only affects this
# specific build environment.
if (WIN32 AND NOT MINGW)
if (NOT DEFINED ENV{ONEAPI_ROOT})
set(ENV{ONEAPI_ROOT} "1")
endif()
if (NOT DEFINED GGML_OPENMP AND NOT DEFINED CACHE{GGML_OPENMP})
# Normal (non-cache) variable on purpose: scoped to this listfile +
# its add_subdirectory(ggml) below, doesn't poison parent-project
# CMakeCache when parakeet-cpp is consumed via add_subdirectory.
# ggml's `option(GGML_OPENMP ... ON)` respects an existing normal
# variable under CMP0077 (CMake 3.13+), so this still takes effect.
# Consumers can override at any time with `-DGGML_OPENMP=ON` (cache)
# or by pre-`set()`ing the variable in the parent listfile.
set(GGML_OPENMP OFF)
endif()
endif()
# Bundled-ggml library filename prefix. qvac-ext-ggml's `speech` branch
# exposes `GGML_LIB_OUTPUT_PREFIX` (commit 4cec2d3a) which handles both
# the OUTPUT_NAME rename for every ggml target (core + per-backend
# .so/.dll/.a) AND the runtime loader's filename prefix
# (`GGML_BACKEND_DL_PROJECT_PREFIX` compile define on ggml-base), so
# the renamed `libspeech-ggml-{vulkan,opencl,cpu-*}.so` files are
# actually discovered by `ggml_backend_load_all_from_path()` at
# runtime.
#
# Setting `GGML_LIB_OUTPUT_PREFIX` here (as a cache variable, before
# `add_subdirectory(ggml)`) is the supported way to override the
# branch default (`qvac-speech-`) on a per-consumer basis without
# editing the ggml subtree. The `speech-` prefix is shared across the
# QVAC speech stack (whisper, parakeet, chatterbox, supertonic, ...)
# so they can vendor a single ggml file set side-by-side without
# colliding with the `qvac-` prefix used by the llm fork.
if (PARAKEET_GGML_LIB_PREFIX AND NOT PARAKEET_USE_SYSTEM_GGML)
if (NOT DEFINED CACHE{GGML_LIB_OUTPUT_PREFIX})
set(GGML_LIB_OUTPUT_PREFIX "speech-" CACHE STRING
"ggml: prefix for built ggml library filenames (parakeet default)" FORCE)
endif()
message(STATUS "parakeet: bundled ggml libraries will be emitted with prefix '${GGML_LIB_OUTPUT_PREFIX}' (set PARAKEET_GGML_LIB_PREFIX=OFF to use the qvac-ext-ggml@speech default, or override -DGGML_LIB_OUTPUT_PREFIX=<other>)")
endif()
if (NOT TARGET ggml)
if (PARAKEET_USE_SYSTEM_GGML)
find_package(ggml CONFIG REQUIRED)
if (NOT ggml_FOUND)
message(FATAL_ERROR "System-installed GGML library not found.")
endif()
add_library(ggml ALIAS ggml::ggml)
else()
add_subdirectory(ggml)
endif()
endif()
# Same OpenMP avoidance as for ggml above: on Windows non-MinGW builds
# CMake's FindOpenMP picks LLVM's `-fopenmp=libomp` compile flag but
# resolves OpenMP_*_LIBRARIES to msys2 libgomp -> link-time mismatch.
# Skip find_package() entirely so OpenMP_*_FOUND stays falsy and the
# parakeet target falls back to its non-OpenMP code paths. Pass
# `-DPARAKEET_OPENMP=ON` to override after wiring up LLVM libomp.
option(PARAKEET_OPENMP "parakeet: enable OpenMP for the parakeet target" ON)
if (WIN32 AND NOT MINGW AND PARAKEET_OPENMP AND NOT DEFINED CACHE{PARAKEET_OPENMP_USER_OVERRIDE})
set(PARAKEET_OPENMP OFF CACHE BOOL "" FORCE)
message(STATUS "parakeet: OpenMP disabled on Windows non-MinGW (clang-cl / MSVC + msys2 libgomp mismatch). Set -DPARAKEET_OPENMP_USER_OVERRIDE=ON and -DPARAKEET_OPENMP=ON to force-enable.")
endif()
if (PARAKEET_OPENMP)
find_package(OpenMP)
endif()
# Legacy interface library kept for export-set compatibility (it is
# still part of `install(EXPORT parakeet-cpp-targets)` below and
# downstream `find_package(parakeet-cpp)` consumers list it as a link
# dep). Body intentionally empty: parakeet routes every backend
# decision through the ggml-backend registry
# (`ggml_backend_load_all` + `ggml_backend_dev_*`, see
# `init_gpu_backend()` / `init_cpu_backend()` / `init_blas_backend()`
# in src/parakeet_ctc.cpp) and does NOT call any
# `ggml_backend_<backend>_init` / `ggml_backend_is_<backend>` entry
# point directly. The `GGML_USE_VULKAN` / `GGML_USE_OPENCL` /
# `GGML_USE_METAL` / `GGML_USE_CUDA` / `GGML_USE_BLAS` compile defines
# that used to live here were only consumed by `#ifdef` cascades that
# called those static entry points; with the registry-only design
# they're dead, and shipping them would falsely advertise a static
# backend dependency that the GGML_BACKEND_DL=ON Android/Linux builds
# explicitly do not have (their backends live in separately-loadable
# `.so` files that are dlopen()'d by `ggml_backend_load_all_from_path`
# at runtime).
add_library(parakeet-backend-defs INTERFACE)
set(PARAKEET_LIB_SOURCES
src/parakeet_ctc.cpp
src/parakeet_engine.cpp
src/parakeet_tdt.cpp
src/parakeet_eou.cpp
src/parakeet_sortformer.cpp
src/parakeet_log.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp
src/energy_vad.cpp
# Lives in the library so `parakeet_cli_main` (declared in
# the umbrella header `parakeet.h`) is actually a library
# symbol that consumers / host applications can link against.
# The CLI executable below is a thin shim that just calls into it.
src/main.cpp
)
if (PARAKEET_BUILD_LIBRARY)
# Honour BUILD_SHARED_LIBS when set explicitly; otherwise default to
# STATIC. Same shape as the standard llama-style library contract
# (BUILD_SHARED_LIBS-driven), so consumers that build other ggml-
# based libraries shared see a consistent linkage selector.
if (DEFINED BUILD_SHARED_LIBS)
add_library(parakeet ${PARAKEET_LIB_SOURCES})
else()
add_library(parakeet STATIC ${PARAKEET_LIB_SOURCES})
endif()
add_library(parakeet::parakeet ALIAS parakeet)
parakeet_apply_ccache(parakeet)
target_include_directories(parakeet
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/ggml/include
)
target_link_libraries(parakeet PUBLIC ggml)
if (OpenMP_CXX_FOUND)
target_link_libraries(parakeet PRIVATE OpenMP::OpenMP_CXX)
endif()
target_link_libraries(parakeet PRIVATE parakeet-backend-defs)
# Single source of truth for the version string the CLI prints
# under `--version`. Kept PRIVATE because it only drives the in-
# library `main.cpp` impl; consumers that need a runtime version
# can read it via the public API surface or the CMake package
# version file (`parakeet-cppConfigVersion.cmake`).
target_compile_definitions(parakeet PRIVATE
PARAKEET_VERSION="${PROJECT_VERSION}"
)
# `PARAKEET_EXPERIMENTAL_FLASH_ATTN` gates the rel-pos MHA
# fused-attention path in `parakeet_ctc.cpp`. Per-backend defaults:
#
# - Metal: ON. Validated on M3 Ultra (5 invocations x 15 runs,
# sample-16k.wav, q8_0): encoder 67.35 -> 66.96 ms
# (-0.39 ms / -0.6 %), inference 119.24 -> 118.84 ms
# (-0.40 ms / -0.34 %), parity gates byte-exact (TDT
# decoder 95 tokens; encoder-capture 258048 + 258300
# floats bit-equal).
# `kernel_flash_attn_ext_f32_dk128_dv128` is the kernel
# actually loaded; head_dim 128 covers parakeet-{ctc,
# tdt,eou} d_model=1024/n_heads=8 and parakeet-tdt-1.1b
# d_model=2048/n_heads=16. Sortformer's transformer
# head_dim=24 falls below ggml_flash_attn_ext's
# supported set so the encoder path there is untouched
# (Sortformer uses `parakeet_sortformer.cpp` not
# `parakeet_ctc.cpp::rel_pos_mha_graph`).
# - CPU: OFF. Cast-to-f16 of the BD mask before softmax
# measurably changed accumulator order and showed
# +3.1 % encoder regression on the CPU parity sweep;
# risks the bit-equal-NeMo gate.
# - Others: OFF until backend-specific A/B. ggml-opencl supports
# `flash_attn_ext` for head_dim in
# {40,64,80,96,112,128,192,256}; first-Adreno bring-up
# should flip and bench. Vulkan validation harness
# (test/test_vk_vs_cpu.cpp) is the natural home for
# that A/B once a non-Tegra Vulkan device is available.
if (GGML_METAL)
set(PARAKEET_FLASH_ATTN_DEFAULT ON)
else()
set(PARAKEET_FLASH_ATTN_DEFAULT OFF)
endif()
option(PARAKEET_FLASH_ATTN "parakeet: enable fused flash-attn in MHA (default ON for Metal; OFF elsewhere pending per-backend A/B)" ${PARAKEET_FLASH_ATTN_DEFAULT})
if (PARAKEET_FLASH_ATTN)
target_compile_definitions(parakeet PRIVATE PARAKEET_EXPERIMENTAL_FLASH_ATTN)
endif()
set_target_properties(parakeet PROPERTIES
POSITION_INDEPENDENT_CODE ON
EXPORT_NAME parakeet
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
# VERSION drives the full filename suffix on Linux/macOS shared
# libs (libparakeet.so.0.1.0); SOVERSION drives the symlink the
# dynamic loader actually opens (libparakeet.so.0). Pinning
# SOVERSION to 0 (rather than ${PROJECT_VERSION_MAJOR}) keeps
# the ABI tag stable across pre-1.0 PROJECT_VERSION bumps so
# downstream consumers do not need to re-link on every patch
# release. Both properties are no-ops for STATIC builds.
VERSION ${PROJECT_VERSION}
SOVERSION 0
)
# Symbol-export contract for shared builds. Standard llama-style
# `*_SHARED` / `*_BUILD` pair: PUBLIC define so consumers see the
# import side of PARAKEET_API, PRIVATE define inside the library's
# TUs to flip Windows from `dllimport` to `dllexport`. Both no-ops
# in static builds (the default), so static consumers see exactly
# the same surface as before.
get_target_property(_parakeet_type parakeet TYPE)
if (_parakeet_type STREQUAL "SHARED_LIBRARY")
target_compile_definitions(parakeet PUBLIC PARAKEET_SHARED)
target_compile_definitions(parakeet PRIVATE PARAKEET_BUILD)
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR NOT CMAKE_BUILD_TYPE)
target_compile_options(parakeet PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-O3>
$<$<COMPILE_LANGUAGE:C>:-O3>
$<$<COMPILE_LANGUAGE:CXX>:-ffast-math>
$<$<COMPILE_LANGUAGE:C>:-ffast-math>
$<$<COMPILE_LANGUAGE:CXX>:-funroll-loops>
$<$<COMPILE_LANGUAGE:C>:-funroll-loops>
)
endif()
endif()
if (PARAKEET_BUILD_EXECUTABLES)
if (NOT PARAKEET_BUILD_LIBRARY)
message(FATAL_ERROR "PARAKEET_BUILD_EXECUTABLES requires PARAKEET_BUILD_LIBRARY=ON")
endif()
add_executable(parakeet-cli src/cli_main.cpp)
target_link_libraries(parakeet-cli PRIVATE parakeet)
set_target_properties(parakeet-cli PROPERTIES OUTPUT_NAME parakeet)
parakeet_apply_ccache(parakeet-cli)
endif()
if (PARAKEET_BUILD_TESTS)
enable_testing()
include(CTest)
# Roots that the test registrations below resolve fixture paths against.
# All three are CACHE PATHs so CI / package builds can point them at
# pre-staged mirrors (e.g. a tarball of `.gguf`s + reference `.npy`s)
# without editing the listfile.
#
# MODEL_DIR - GGUF checkpoints (produced by scripts/convert-nemo-to-gguf.py)
# AUDIO_DIR - 16 kHz mono WAV fixtures committed to test/samples/
# REF_DIR - NeMo reference dumps (`.npy`) produced by
# scripts/dump-{ctc,tdt,eou,sortformer}-reference.py;
# expected layout is REF_DIR/{ctc,tdt,eou,sortformer}-ref/.
# Tests that need .npy refs auto-disable when the file
# isn't present, so a fresh checkout still gives a green
# ctest run on the model+wav harnesses.
set(PARAKEET_TEST_MODEL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/models" CACHE PATH "parakeet: directory containing .gguf fixtures used by the test suite")
set(PARAKEET_TEST_AUDIO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test/samples" CACHE PATH "parakeet: directory containing .wav fixtures used by the test suite")
set(PARAKEET_TEST_REF_DIR "${CMAKE_CURRENT_SOURCE_DIR}/artifacts" CACHE PATH "parakeet: directory containing NeMo .npy reference dumps (scripts/dump-*-reference.py)")
# Helper: register a built test target with CTest. Same shape as
# the standard llama-style `llama_test()` macro: every harness
# ends up surfaced via `ctest` against the build dir, with labels
# that allow filtering (e.g. `ctest -L cpu` to skip GPU-only tests
# on CI fleets without the matching backend). The optional REQUIRES
# list names paths that must exist at configure time; if any is
# missing the test is still registered (so it shows up in
# `ctest -N`) but marked DISABLED so a missing reference dump
# shows as "Not Run" rather than a failure.
function(parakeet_register_test target)
cmake_parse_arguments(QPRT "" "LABEL;EXE" "ARGS;REQUIRES" ${ARGN})
if (NOT DEFINED QPRT_LABEL)
set(QPRT_LABEL "cpu")
endif()
# EXE defaults to the test name when the test has its own
# executable target. Pass an explicit EXE when several tests
# share one binary and differ only in CLI args / fixtures
# (e.g. test-encoder-capture-parity-{tdt,eou,sortformer} all
# invoke test-encoder-capture-parity.exe).
if (NOT DEFINED QPRT_EXE)
set(QPRT_EXE ${target})
endif()
add_test(NAME ${target}
COMMAND $<TARGET_FILE:${QPRT_EXE}> ${QPRT_ARGS})
set_property(TEST ${target} PROPERTY LABELS ${QPRT_LABEL})
if (WIN32)
# When ggml builds as shared (the clang-cl / MSVC default with
# GGML_BACKEND_DL on), test exes link against import libs and
# need to find `speech-ggml-*.dll` at runtime. CMake places
# those at `${CMAKE_BINARY_DIR}/bin`, but Windows has no rpath
# and the test exes live next to the binary root, not in bin/.
# Without this, the OS loader fails with STATUS_DLL_NOT_FOUND
# (0xc0000135) before main() runs. Inject the bin/ dir into
# the test's PATH; configure-time $ENV{PATH} is appended so
# any compiler-runtime DLLs the user needs stay reachable.
# Static-ggml builds (msys2 .a archives) are unaffected --
# the prepended dir simply doesn't exist and is ignored.
set_property(TEST ${target} PROPERTY ENVIRONMENT
"PATH=${CMAKE_BINARY_DIR}/bin\;$ENV{PATH}")
endif()
set(_qprt_missing "")
foreach (_qprt_req IN LISTS QPRT_REQUIRES)
if (NOT EXISTS "${_qprt_req}")
list(APPEND _qprt_missing "${_qprt_req}")
endif()
endforeach()
if (_qprt_missing)
set_property(TEST ${target} PROPERTY DISABLED TRUE)
list(JOIN _qprt_missing "\n " _qprt_missing_pretty)
message(STATUS "parakeet: test ${target} disabled (missing fixture(s):\n ${_qprt_missing_pretty})")
endif()
endfunction()
# Helper: keep PARAKEET_EXPERIMENTAL_FLASH_ATTN consistent across the
# parakeet library and any test executable that recompiles
# parakeet_ctc.cpp from source. Backend selection itself goes
# through the ggml-backend registry (no per-backend `GGML_USE_*`
# #ifdef cascade in parakeet_ctc.cpp anymore -- see the comment on
# `parakeet-backend-defs` above), so this helper only carries the
# flash-attn gate plus the shared ccache launcher.
function(parakeet_apply_backend_defs target)
if (PARAKEET_FLASH_ATTN)
target_compile_definitions(${target} PRIVATE PARAKEET_EXPERIMENTAL_FLASH_ATTN)
endif()
parakeet_apply_ccache(${target})
endfunction()
# Stable shorthands for the fixtures referenced by the registrations
# below. Resolved at configure time; missing files turn into DISABLED
# tests via parakeet_register_test(REQUIRES ...).
#
# q8_0 GGUFs are the runtime fixtures (smoke, streaming, perf): smaller
# download, exercises the same code paths as production. f16 GGUFs are
# the *parity* fixtures: their per-stage NeMo `.npy` references were
# dumped from FP32 PyTorch, and even q8_0's well-behaved 0.5-1 % drift
# swamps the per-block tolerance gates (3e-3 ... 5e-3) the parity
# harnesses use to catch real operator bugs. So we point the parity
# tests at the f16 GGUFs and leave q8_0 in place for everything else.
set(_qvp_ctc_q8_gguf "${PARAKEET_TEST_MODEL_DIR}/parakeet-ctc-0.6b.q8_0.gguf")
set(_qvp_ctc_f16_gguf "${PARAKEET_TEST_MODEL_DIR}/parakeet-ctc-0.6b.f16.gguf")
set(_qvp_tdt_q8_gguf "${PARAKEET_TEST_MODEL_DIR}/parakeet-tdt-0.6b-v3.q8_0.gguf")
set(_qvp_tdt_f16_gguf "${PARAKEET_TEST_MODEL_DIR}/parakeet-tdt-0.6b-v3.f16.gguf")
set(_qvp_eou_q8_gguf "${PARAKEET_TEST_MODEL_DIR}/parakeet_realtime_eou_120m-v1.q8_0.gguf")
set(_qvp_sf_q8_gguf "${PARAKEET_TEST_MODEL_DIR}/diar_sortformer_4spk-v1.q8_0.gguf")
set(_qvp_sf_f16_gguf "${PARAKEET_TEST_MODEL_DIR}/diar_sortformer_4spk-v1.f16.gguf")
set(_qvp_sfs_q8_gguf "${PARAKEET_TEST_MODEL_DIR}/diar_streaming_sortformer_4spk-v2.q8_0.gguf")
set(_qvp_sfsv21_q8_gguf "${PARAKEET_TEST_MODEL_DIR}/diar_streaming_sortformer_4spk-v2.1.q8_0.gguf")
set(_qvp_jfk_wav "${PARAKEET_TEST_AUDIO_DIR}/jfk.wav")
set(_qvp_diar_wav "${PARAKEET_TEST_AUDIO_DIR}/diarization-sample-16k.wav")
set(_qvp_abcba_wav "${PARAKEET_TEST_AUDIO_DIR}/abcba.wav")
set(_qvp_abcba_rttm "${PARAKEET_TEST_AUDIO_DIR}/abcba.rttm")
set(_qvp_abcdba_wav "${PARAKEET_TEST_AUDIO_DIR}/abcdba.wav")
set(_qvp_abcdba_rttm "${PARAKEET_TEST_AUDIO_DIR}/abcdba.rttm")
set(_qvp_ctc_ref "${PARAKEET_TEST_REF_DIR}/ctc-ref")
set(_qvp_tdt_ref "${PARAKEET_TEST_REF_DIR}/tdt-ref")
set(_qvp_sf_ref "${PARAKEET_TEST_REF_DIR}/sortformer-ref")
add_executable(test-mel
test/test_mel.cpp
src/mel_preprocess.cpp
src/parakeet_ctc.cpp
src/parakeet_log.cpp
src/sentencepiece_bpe.cpp)
target_link_libraries(test-mel PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-mel PRIVATE ggml/include src include)
parakeet_apply_backend_defs(test-mel)
parakeet_register_test(test-mel
LABEL "fixture"
ARGS "${_qvp_ctc_f16_gguf}" "${_qvp_jfk_wav}" "${_qvp_ctc_ref}/mel.npy"
REQUIRES "${_qvp_ctc_f16_gguf}" "${_qvp_jfk_wav}" "${_qvp_ctc_ref}/mel.npy")
add_executable(test-encoder
test/test_encoder.cpp
src/parakeet_ctc.cpp
src/parakeet_log.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp)
target_link_libraries(test-encoder PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-encoder PRIVATE ggml/include src include)
parakeet_apply_backend_defs(test-encoder)
parakeet_register_test(test-encoder
LABEL "fixture"
ARGS "${_qvp_ctc_f16_gguf}" "${_qvp_ctc_ref}"
REQUIRES "${_qvp_ctc_f16_gguf}" "${_qvp_ctc_ref}/mel.npy" "${_qvp_ctc_ref}/encoder_out.npy")
add_executable(test-ctc
test/test_ctc.cpp
src/parakeet_ctc.cpp
src/parakeet_log.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp)
target_link_libraries(test-ctc PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-ctc PRIVATE ggml/include src include)
parakeet_apply_backend_defs(test-ctc)
parakeet_register_test(test-ctc
LABEL "fixture"
ARGS "${_qvp_ctc_q8_gguf}" "${_qvp_ctc_ref}/logits.npy" "${_qvp_ctc_ref}/decoded.txt"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_ctc_ref}/logits.npy" "${_qvp_ctc_ref}/decoded.txt")
add_executable(test-streaming test/test_streaming.cpp)
target_link_libraries(test-streaming PRIVATE parakeet)
target_include_directories(test-streaming PRIVATE include src ggml/include)
parakeet_apply_ccache(test-streaming)
parakeet_register_test(test-streaming
LABEL "fixture"
ARGS "--model" "${_qvp_ctc_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
add_executable(test-eou-streaming test/test_eou_streaming.cpp)
target_link_libraries(test-eou-streaming PRIVATE parakeet)
target_include_directories(test-eou-streaming PRIVATE include src ggml/include)
parakeet_apply_ccache(test-eou-streaming)
parakeet_register_test(test-eou-streaming
LABEL "fixture"
ARGS "--model" "${_qvp_eou_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_eou_q8_gguf}" "${_qvp_jfk_wav}")
add_executable(test-sortformer-streaming test/test_sortformer_streaming.cpp)
target_link_libraries(test-sortformer-streaming PRIVATE parakeet)
target_include_directories(test-sortformer-streaming PRIVATE include src ggml/include)
parakeet_apply_ccache(test-sortformer-streaming)
parakeet_register_test(test-sortformer-streaming
LABEL "fixture"
ARGS "--model" "${_qvp_sfsv21_q8_gguf}" "--wav" "${_qvp_diar_wav}"
REQUIRES "${_qvp_sfsv21_q8_gguf}" "${_qvp_diar_wav}")
# v2.1 AOSC speaker-correctness regression. Asserts speaker coverage,
# re-entry slot continuity (the AOSC contract), and frame-level DER
# ceiling against the RTTM ground truth. One binary, two ctest
# registrations (one per LIFO re-entry fixture).
add_executable(test-sortformer-aosc-speakers test/test_sortformer_aosc_speakers.cpp)
target_link_libraries(test-sortformer-aosc-speakers PRIVATE parakeet)
target_include_directories(test-sortformer-aosc-speakers PRIVATE include src ggml/include)
parakeet_apply_ccache(test-sortformer-aosc-speakers)
parakeet_register_test(test-sortformer-aosc-speakers-abcba
LABEL "fixture"
EXE test-sortformer-aosc-speakers
ARGS "--model" "${_qvp_sfsv21_q8_gguf}"
"--wav" "${_qvp_abcba_wav}"
"--ref-rttm" "${_qvp_abcba_rttm}"
REQUIRES "${_qvp_sfsv21_q8_gguf}" "${_qvp_abcba_wav}" "${_qvp_abcba_rttm}")
parakeet_register_test(test-sortformer-aosc-speakers-abcdba
LABEL "fixture"
EXE test-sortformer-aosc-speakers
ARGS "--model" "${_qvp_sfsv21_q8_gguf}"
"--wav" "${_qvp_abcdba_wav}"
"--ref-rttm" "${_qvp_abcdba_rttm}"
REQUIRES "${_qvp_sfsv21_q8_gguf}" "${_qvp_abcdba_wav}" "${_qvp_abcdba_rttm}")
add_executable(test-perf-regression test/test_perf_regression.cpp)
target_link_libraries(test-perf-regression PRIVATE parakeet)
target_include_directories(test-perf-regression PRIVATE include src ggml/include)
parakeet_apply_ccache(test-perf-regression)
parakeet_register_test(test-perf-regression
LABEL "perf"
ARGS "--model" "${_qvp_ctc_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
# Sortformer dispatch path for the perf-regression
# harness. Same gates (cache-hit ratio, cold-overhead, mel-ms ceiling)
# as transcribe-path; the only difference is that
# `engine.diarize(wav)` produces a `DiarizationResult` instead of an
# `EngineResult`, and the deterministic comparison runs over a
# segment-list fingerprint rather than the transcript text.
parakeet_register_test(test-perf-regression-sortformer
LABEL "perf"
EXE test-perf-regression
ARGS "--model" "${_qvp_sf_q8_gguf}" "--wav" "${_qvp_diar_wav}"
REQUIRES "${_qvp_sf_q8_gguf}" "${_qvp_diar_wav}")
# Internal regression gate for the round-2 mel preprocess sweep
# (real-FFT, MelState reuse). Pure unit test -- no GGUF needed.
add_executable(test-mel-fft-parity
test/test_mel_fft_parity.cpp
src/mel_preprocess.cpp
src/parakeet_log.cpp)
target_link_libraries(test-mel-fft-parity PRIVATE ggml)
target_include_directories(test-mel-fft-parity PRIVATE ggml/include src include)
parakeet_apply_backend_defs(test-mel-fft-parity)
parakeet_register_test(test-mel-fft-parity LABEL "unit")
# Regression gate for the round-1 capture_intermediates flag.
# Asserts run_encoder(capture=true) and (capture=false) produce
# bit-equal encoder_out + logits on a CTC GGUF.
add_executable(test-encoder-capture-parity
test/test_encoder_capture_parity.cpp
src/parakeet_ctc.cpp
src/parakeet_log.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp)
target_link_libraries(test-encoder-capture-parity PRIVATE ggml)
target_include_directories(test-encoder-capture-parity PRIVATE ggml/include src include)
parakeet_apply_backend_defs(test-encoder-capture-parity)
parakeet_register_test(test-encoder-capture-parity
LABEL "fixture"
ARGS "--model" "${_qvp_ctc_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
# Same capture-parity contract on TDT/EOU/Sortformer. Each shares
# the test-encoder-capture-parity exe and gets auto-disabled by
# parakeet_register_test() when the relevant GGUF isn't present,
# so the CTC variant stays enabled even when the others aren't.
parakeet_register_test(test-encoder-capture-parity-tdt
LABEL "fixture"
EXE test-encoder-capture-parity
ARGS "--model" "${_qvp_tdt_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_tdt_q8_gguf}" "${_qvp_jfk_wav}")
parakeet_register_test(test-encoder-capture-parity-eou
LABEL "fixture"
EXE test-encoder-capture-parity
ARGS "--model" "${_qvp_eou_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_eou_q8_gguf}" "${_qvp_jfk_wav}")
parakeet_register_test(test-encoder-capture-parity-sortformer
LABEL "fixture"
EXE test-encoder-capture-parity
ARGS "--model" "${_qvp_sf_q8_gguf}" "--wav" "${_qvp_diar_wav}"
REQUIRES "${_qvp_sf_q8_gguf}" "${_qvp_diar_wav}")
# Regression gate for cached-encoder-graph reuse determinism. Compiles the
# sources directly (run_encoder is internal, not exported). Runs run_encoder 20x
# on the same cached graph and asserts the output stays byte-identical across
# every reuse; the -gpu variant locks the Adreno OpenCL/Vulkan reuse path that
# regressed under the shared scheduler and is fixed by the persistent gallocr.
add_executable(test-sched-encoder-determinism
test/test_sched_encoder_determinism.cpp
src/parakeet_ctc.cpp
src/parakeet_log.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp)
target_link_libraries(test-sched-encoder-determinism PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-sched-encoder-determinism PRIVATE include src ggml/include)
parakeet_apply_backend_defs(test-sched-encoder-determinism)
parakeet_register_test(test-sched-encoder-determinism
LABEL "fixture"
ARGS "--model" "${_qvp_ctc_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
parakeet_register_test(test-sched-encoder-determinism-gpu
LABEL "gpu"
EXE test-sched-encoder-determinism
ARGS "--model" "${_qvp_ctc_q8_gguf}" "--wav" "${_qvp_jfk_wav}" "--n-gpu-layers" "1"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
# Regression gate for the shared-scheduler lifecycle: subsampling -> encoder ->
# Sortformer head through one sched must match a clean encoder -> head reference,
# byte-identical, across reuse. Locks reset-at-head / download-before-reset order.
add_executable(test-sched-diarize-lifecycle
test/test_sched_diarize_lifecycle.cpp
src/parakeet_ctc.cpp
src/parakeet_engine.cpp
src/parakeet_log.cpp
src/parakeet_tdt.cpp
src/parakeet_eou.cpp
src/parakeet_sortformer.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp
src/energy_vad.cpp)
target_link_libraries(test-sched-diarize-lifecycle PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-sched-diarize-lifecycle PRIVATE include src ggml/include)
parakeet_apply_backend_defs(test-sched-diarize-lifecycle)
parakeet_register_test(test-sched-diarize-lifecycle
LABEL "fixture"
ARGS "--model" "${_qvp_sf_q8_gguf}" "--wav" "${_qvp_diar_wav}"
REQUIRES "${_qvp_sf_q8_gguf}" "${_qvp_diar_wav}")
parakeet_register_test(test-sched-diarize-lifecycle-gpu
LABEL "gpu"
EXE test-sched-diarize-lifecycle
ARGS "--model" "${_qvp_sf_q8_gguf}" "--wav" "${_qvp_diar_wav}" "--n-gpu-layers" "1"
REQUIRES "${_qvp_sf_q8_gguf}" "${_qvp_diar_wav}")
add_executable(test-tdt-encoder-parity
test/test_tdt_encoder_parity.cpp
src/parakeet_ctc.cpp
src/parakeet_engine.cpp
src/parakeet_log.cpp
src/parakeet_tdt.cpp
src/parakeet_eou.cpp
src/parakeet_sortformer.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp
src/energy_vad.cpp)
target_link_libraries(test-tdt-encoder-parity PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-tdt-encoder-parity PRIVATE include src ggml/include)
parakeet_apply_backend_defs(test-tdt-encoder-parity)
parakeet_register_test(test-tdt-encoder-parity
LABEL "fixture"
ARGS "${_qvp_tdt_f16_gguf}" "${_qvp_jfk_wav}" "${_qvp_tdt_ref}"
REQUIRES "${_qvp_tdt_f16_gguf}" "${_qvp_jfk_wav}" "${_qvp_tdt_ref}/encoder_out.npy")
add_executable(test-tdt-decoder-parity
test/test_tdt_decoder_parity.cpp
src/parakeet_ctc.cpp
src/parakeet_engine.cpp
src/parakeet_log.cpp
src/parakeet_tdt.cpp
src/parakeet_eou.cpp
src/parakeet_sortformer.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp
src/energy_vad.cpp)
target_link_libraries(test-tdt-decoder-parity PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-tdt-decoder-parity PRIVATE include src ggml/include)
parakeet_apply_backend_defs(test-tdt-decoder-parity)
parakeet_register_test(test-tdt-decoder-parity
LABEL "fixture"
ARGS "${_qvp_tdt_q8_gguf}" "${_qvp_jfk_wav}" "${_qvp_tdt_ref}"
REQUIRES "${_qvp_tdt_q8_gguf}" "${_qvp_jfk_wav}")
add_executable(test-sortformer-parity
test/test_sortformer_parity.cpp
src/parakeet_ctc.cpp
src/parakeet_engine.cpp
src/parakeet_log.cpp
src/parakeet_tdt.cpp
src/parakeet_eou.cpp
src/parakeet_sortformer.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp
src/energy_vad.cpp)
target_link_libraries(test-sortformer-parity PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-sortformer-parity PRIVATE include src ggml/include)
parakeet_apply_backend_defs(test-sortformer-parity)
parakeet_register_test(test-sortformer-parity
LABEL "fixture"
ARGS "${_qvp_sf_f16_gguf}" "${_qvp_diar_wav}" "${_qvp_sf_ref}"
REQUIRES "${_qvp_sf_f16_gguf}" "${_qvp_diar_wav}" "${_qvp_sf_ref}/encoder_out.npy")
# Decoder determinism gate. Runs each decoder N times
# against the same encoder output and asserts byte-equal results
# across all repetitions. Catches buffer-reuse correctness bugs
# (residual scratch state leaking into the next call) that the
# existing per-stage parity tests (which load+decode once) wouldn't
# see. Runs against any model type; switches to diarize() when the
# GGUF declares Sortformer.
add_executable(test-decoder-determinism test/test_decoder_determinism.cpp)
target_link_libraries(test-decoder-determinism PRIVATE parakeet)
target_include_directories(test-decoder-determinism PRIVATE include src ggml/include)
parakeet_apply_ccache(test-decoder-determinism)
parakeet_register_test(test-decoder-determinism
LABEL "fixture"
ARGS "--model" "${_qvp_ctc_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
# Per-model-type instances so ctest exercises each path. Each is
# individually DISABLED via parakeet_register_test() when its GGUF
# isn't present, so a fresh checkout with only ctc.q8_0.gguf cached
# still gives a green ctest.
parakeet_register_test(test-decoder-determinism-tdt
LABEL "fixture"
EXE test-decoder-determinism
ARGS "--model" "${_qvp_tdt_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_tdt_q8_gguf}" "${_qvp_jfk_wav}")
parakeet_register_test(test-decoder-determinism-eou
LABEL "fixture"
EXE test-decoder-determinism
ARGS "--model" "${_qvp_eou_q8_gguf}" "--wav" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_eou_q8_gguf}" "${_qvp_jfk_wav}")
parakeet_register_test(test-decoder-determinism-sortformer
LABEL "fixture"
EXE test-decoder-determinism
ARGS "--model" "${_qvp_sf_q8_gguf}" "--wav" "${_qvp_diar_wav}"
REQUIRES "${_qvp_sf_q8_gguf}" "${_qvp_diar_wav}")
if (GGML_VULKAN)
add_executable(test-vk-vs-cpu
test/test_vk_vs_cpu.cpp
src/parakeet_ctc.cpp
src/parakeet_log.cpp
src/mel_preprocess.cpp
src/sentencepiece_bpe.cpp)
target_link_libraries(test-vk-vs-cpu PRIVATE ggml parakeet-backend-defs)
target_include_directories(test-vk-vs-cpu PRIVATE ggml/include src include)
parakeet_apply_backend_defs(test-vk-vs-cpu)
parakeet_register_test(test-vk-vs-cpu
LABEL "gpu"
ARGS "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}"
REQUIRES "${_qvp_ctc_q8_gguf}" "${_qvp_jfk_wav}")
endif()
endif()
if (PARAKEET_BUILD_EXAMPLES)
if (NOT PARAKEET_BUILD_LIBRARY)
message(FATAL_ERROR "PARAKEET_BUILD_EXAMPLES requires PARAKEET_BUILD_LIBRARY=ON")
endif()
add_executable(live-mic
examples/live-mic.cpp
examples/miniaudio_impl.cpp)
target_link_libraries(live-mic PRIVATE parakeet)
target_include_directories(live-mic PRIVATE include examples)
target_compile_options(live-mic PRIVATE -Wno-deprecated-declarations)
parakeet_apply_ccache(live-mic)
add_executable(live-mic-attributed
examples/live-mic-attributed.cpp
examples/miniaudio_impl.cpp)
target_link_libraries(live-mic-attributed PRIVATE parakeet)
target_include_directories(live-mic-attributed PRIVATE include examples)
target_compile_options(live-mic-attributed PRIVATE -Wno-deprecated-declarations)
parakeet_apply_ccache(live-mic-attributed)
if (APPLE)
find_library(COREAUDIO_LIBRARY CoreAudio REQUIRED)
find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED)
find_library(AUDIOUNIT_LIBRARY AudioUnit REQUIRED)
find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox REQUIRED)
foreach (tgt live-mic live-mic-attributed)
target_link_libraries(${tgt} PRIVATE
${COREAUDIO_LIBRARY}
${COREFOUNDATION_LIBRARY}
${AUDIOUNIT_LIBRARY}
${AUDIOTOOLBOX_LIBRARY})
endforeach()
elseif (UNIX)
find_package(Threads REQUIRED)
foreach (tgt live-mic live-mic-attributed)
target_link_libraries(${tgt} PRIVATE Threads::Threads dl m)
endforeach()
elseif (WIN32)
foreach (tgt live-mic live-mic-attributed)
target_link_libraries(${tgt} PRIVATE winmm ole32)
endforeach()
endif()
endif()
if (PARAKEET_INSTALL AND PARAKEET_BUILD_LIBRARY)
# Re-install the bundled ggml targets into a `ggml-targets` export
# set so `install(EXPORT parakeet-cpp-targets)` can resolve the
# `parakeet PUBLIC ggml` link as the namespaced `ggml::ggml`.
# Without this, CMake refuses to generate parakeet-cpp-targets.cmake
# because the in-tree `ggml` target is not part of any export set.
# Replicates the standard
# `install(TARGETS ggml ggml-base EXPORT ggml-targets)` pattern
# that downstream ggml-based projects ship in their own CMake
# (e.g. llama.cpp); we add it here at the parakeet level because
# the upstream ggml at the pinned commit installs ggml without
# an EXPORT clause. The bundled ggml's hand-written
# ggml-config.cmake (find_library + UNKNOWN IMPORTED) still
# ships and is what's loaded via find_dependency(ggml) -- we
# don't replace it; the new ggml-targets.cmake just carries the
# export so CMake's cross-package reference check passes here.
if (NOT PARAKEET_USE_SYSTEM_GGML)
set(_parakeet_ggml_export_targets ggml ggml-base)
if (DEFINED GGML_AVAILABLE_BACKENDS)
list(APPEND _parakeet_ggml_export_targets ${GGML_AVAILABLE_BACKENDS})
endif()
# No PUBLIC_HEADER DESTINATION here: the bundled ggml's own
# `install(TARGETS ggml LIBRARY PUBLIC_HEADER)` already copies
# the ggml/include/*.h headers into the install tree (relative
# paths resolve against ggml/CMakeLists.txt's source dir). If
# we duplicated the PUBLIC_HEADER install here, the same
# relative paths would be re-resolved against the parakeet
# source dir and `cmake --install` would fail with
# "file INSTALL cannot find <root>/include/ggml.h: File exists".
# CMake emits a developer warning ("PUBLIC_HEADER files but no
# PUBLIC_HEADER DESTINATION") about the gap, which is benign
# here -- ggml's own rule still publishes the headers. Keep
# the rule minimal: we only need EXPORT + library destinations
# so `parakeet-cpp-targets` can resolve the namespaced
# `ggml::ggml` link without rebuilding the headers install.
install(TARGETS ${_parakeet_ggml_export_targets}
EXPORT ggml-targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(EXPORT ggml-targets
FILE ggml-targets.cmake
NAMESPACE ggml::
DESTINATION ${CMAKE_INSTALL_DATADIR}/ggml
)
unset(_parakeet_ggml_export_targets)
endif()
# Export the parakeet target via an install(EXPORT ...) set so the
# generated parakeet-cpp-targets.cmake carries the correct shape
# for whatever linkage the build produced (STATIC vs SHARED, with
# IMPORTED_IMPLIB on Windows shared builds, etc.). Hand-writing
# a STATIC IMPORTED stub here would silently mis-resolve the
# downstream target on every shared / Windows install and is the
# bug this replaces. Same pattern as the standard llama-style
# `install(EXPORT <project>-targets NAMESPACE <project>::)` flow.
# parakeet-backend-defs is an INTERFACE library that carries the
# GGML_USE_* compile defines; for STATIC parakeet builds the
# PRIVATE link bleeds into the interface (static archives don't
# carry transitive PRIVATE deps the way shared libs do), so the
# exporter would otherwise refuse with
# "requires target parakeet-backend-defs that is not in any export set".
install(TARGETS parakeet parakeet-backend-defs
EXPORT parakeet-cpp-targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(DIRECTORY include/parakeet
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
set(PARAKEET_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_DATADIR}/parakeet-cpp)
install(EXPORT parakeet-cpp-targets
FILE parakeet-cpp-targets.cmake
NAMESPACE parakeet::
DESTINATION ${PARAKEET_CONFIG_INSTALL_DIR}
)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/parakeet-cppConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/parakeet-cppConfig.cmake
INSTALL_DESTINATION ${PARAKEET_CONFIG_INSTALL_DIR}
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/parakeet-cppConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/parakeet-cppConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/parakeet-cppConfigVersion.cmake
DESTINATION ${PARAKEET_CONFIG_INSTALL_DIR}
)
endif()