1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8
9let
10 cfg = config.services.wyoming.faster-whisper;
11
12 inherit (lib)
13 mapAttrsToList
14 mkOption
15 mkEnableOption
16 mkPackageOption
17 optionals
18 types
19 ;
20
21 inherit (builtins)
22 toString
23 ;
24
25 inherit (utils)
26 escapeSystemdExecArgs
27 ;
28in
29
30{
31 options.services.wyoming.faster-whisper = with types; {
32 package = mkPackageOption pkgs "wyoming-faster-whisper" { };
33
34 servers = mkOption {
35 default = { };
36 description = ''
37 Attribute set of wyoming-faster-whisper instances to spawn.
38 '';
39 type = attrsOf (submodule {
40 options = {
41 enable = mkEnableOption "Wyoming faster-whisper server";
42
43 model = mkOption {
44 type = str;
45 default = "tiny-int8";
46 example = "Systran/faster-distil-whisper-small.en";
47 # https://github.com/home-assistant/addons/blob/master/whisper/DOCS.md#option-model
48 description = ''
49 Name of the voice model to use. Can also be a HuggingFace model ID or a path to
50 a custom model directory.
51
52 With {option}`useTranformers` enabled, a HuggingFace transformers Whisper model
53 ID from HuggingFace like `openai/whisper-tiny.en` must be used.
54
55 Compressed models (`int8`) are slightly less accurate, but smaller and faster.
56 Distilled models are uncompressed and faster and smaller than non-distilled models.
57
58 Available models:
59 - `tiny-int8` (compressed)
60 - `tiny`
61 - `tiny.en` (English only)
62 - `base-int8` (compressed)
63 - `base`
64 - `base.en` (English only)
65 - `small-int8` (compressed)
66 - `distil-small.en` (distilled, English only)
67 - `small`
68 - `small.en` (English only)
69 - `medium-int8` (compressed)
70 - `distil-medium.en` (distilled, English only)
71 - `medium`
72 - `medium.en` (English only)
73 - `large`
74 - `large-v1`
75 - `distil-large-v2` (distilled, English only)
76 - `large-v2`
77 - `distil-large-v3` (distilled, English only)
78 - `large-v3`
79 - `turbo` (faster than large-v3)
80 '';
81 };
82
83 useTransformers = mkOption {
84 type = bool;
85 default = false;
86 description = ''
87 Whether to provide the dependencies to allow using transformer models.
88 '';
89 };
90
91 uri = mkOption {
92 type = strMatching "^(tcp|unix)://.*$";
93 example = "tcp://0.0.0.0:10300";
94 description = ''
95 URI to bind the wyoming server to.
96 '';
97 };
98
99 device = mkOption {
100 # https://opennmt.net/CTranslate2/python/ctranslate2.models.Whisper.html#
101 type = enum [
102 "cpu"
103 "cuda"
104 "auto"
105 ];
106 default = "cpu";
107 description = ''
108 Determines the platform faster-whisper is run on. CPU works everywhere, CUDA requires a compatible NVIDIA GPU.
109 '';
110 };
111
112 language = mkOption {
113 type = enum [
114 # https://github.com/home-assistant/addons/blob/master/whisper/config.yaml#L20
115 "auto"
116 "af"
117 "am"
118 "ar"
119 "as"
120 "az"
121 "ba"
122 "be"
123 "bg"
124 "bn"
125 "bo"
126 "br"
127 "bs"
128 "ca"
129 "cs"
130 "cy"
131 "da"
132 "de"
133 "el"
134 "en"
135 "es"
136 "et"
137 "eu"
138 "fa"
139 "fi"
140 "fo"
141 "fr"
142 "gl"
143 "gu"
144 "ha"
145 "haw"
146 "he"
147 "hi"
148 "hr"
149 "ht"
150 "hu"
151 "hy"
152 "id"
153 "is"
154 "it"
155 "ja"
156 "jw"
157 "ka"
158 "kk"
159 "km"
160 "kn"
161 "ko"
162 "la"
163 "lb"
164 "ln"
165 "lo"
166 "lt"
167 "lv"
168 "mg"
169 "mi"
170 "mk"
171 "ml"
172 "mn"
173 "mr"
174 "ms"
175 "mt"
176 "my"
177 "ne"
178 "nl"
179 "nn"
180 "no"
181 "oc"
182 "pa"
183 "pl"
184 "ps"
185 "pt"
186 "ro"
187 "ru"
188 "sa"
189 "sd"
190 "si"
191 "sk"
192 "sl"
193 "sn"
194 "so"
195 "sq"
196 "sr"
197 "su"
198 "sv"
199 "sw"
200 "ta"
201 "te"
202 "tg"
203 "th"
204 "tk"
205 "tl"
206 "tr"
207 "tt"
208 "uk"
209 "ur"
210 "uz"
211 "vi"
212 "yi"
213 "yue"
214 "yo"
215 "zh"
216 ];
217 example = "en";
218 description = ''
219 The language used to to parse words and sentences.
220 '';
221 };
222
223 initialPrompt = mkOption {
224 type = nullOr str;
225 default = null;
226 # https://github.com/home-assistant/addons/blob/master/whisper/DOCS.md#option-custom_model_type
227 example = ''
228 The following conversation takes place in the universe of
229 Wizard of Oz. Key terms include 'Yellow Brick Road' (the path
230 to follow), 'Emerald City' (the ultimate goal), and 'Ruby
231 Slippers' (the magical tools to succeed). Keep these in mind as
232 they guide the journey.
233 '';
234 description = ''
235 Optional text to provide as a prompt for the first window. This can be used to provide, or
236 "prompt-engineer" a context for transcription, e.g. custom vocabularies or proper nouns
237 to make it more likely to predict those word correctly.
238
239 Not supported when the {option}`customModelType` is `transformers`.
240 '';
241 };
242
243 beamSize = mkOption {
244 type = ints.unsigned;
245 default = 0;
246 example = 5;
247 description = ''
248 The number of beams to use in beam search.
249 Use `0` to automatically select a value based on the CPU.
250 '';
251 apply = toString;
252 };
253
254 extraArgs = mkOption {
255 type = listOf str;
256 default = [ ];
257 description = ''
258 Extra arguments to pass to the server commandline.
259 '';
260 };
261 };
262 });
263 };
264 };
265
266 config =
267 let
268 inherit (lib)
269 mapAttrs'
270 mkIf
271 nameValuePair
272 ;
273 in
274 mkIf (cfg.servers != { }) {
275 assertions = mapAttrsToList (server: options: {
276 assertion = options.useTransformers -> options.initialPrompt == null;
277 message = "wyoming-faster-whisper/${server}: Transformer models (`useTransformers`) do not currently support an `initialPrompt`.";
278 }) cfg.servers;
279
280 systemd.services = mapAttrs' (
281 server: options:
282 let
283 finalPackage = cfg.package.overridePythonAttrs (oldAttrs: {
284 dependencies =
285 oldAttrs.dependencies
286 # for transformer model support
287 ++ optionals options.useTransformers oldAttrs.optional-dependencies.transformers;
288 });
289 in
290 nameValuePair "wyoming-faster-whisper-${server}" {
291 inherit (options) enable;
292 description = "Wyoming faster-whisper server instance ${server}";
293 wants = [
294 "network-online.target"
295 ];
296 after = [
297 "network-online.target"
298 ];
299 wantedBy = [
300 "multi-user.target"
301 ];
302 # https://github.com/rhasspy/wyoming-faster-whisper/issues/27
303 # https://github.com/NixOS/nixpkgs/issues/429974
304 environment."HF_HOME" = "/tmp";
305 serviceConfig = {
306 DynamicUser = true;
307 User = "wyoming-faster-whisper";
308 StateDirectory = [ "wyoming/faster-whisper" ];
309 # https://github.com/home-assistant/addons/blob/master/whisper/rootfs/etc/s6-overlay/s6-rc.d/whisper/run
310 ExecStart = escapeSystemdExecArgs (
311 [
312 (lib.getExe finalPackage)
313 "--data-dir"
314 "/var/lib/wyoming/faster-whisper"
315 "--uri"
316 options.uri
317 "--device"
318 options.device
319 "--model"
320 options.model
321 "--language"
322 options.language
323 "--beam-size"
324 options.beamSize
325 ]
326 ++ lib.optionals options.useTransformers [
327 "--use-transformers"
328 ]
329 ++ lib.optionals (options.initialPrompt != null) [
330 "--initial-prompt"
331 options.initialPrompt
332 ]
333 ++ options.extraArgs
334 );
335 CapabilityBoundingSet = "";
336 DeviceAllow =
337 if
338 builtins.elem options.device [
339 "cuda"
340 "auto"
341 ]
342 then
343 [
344 # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
345 "char-nvidia-uvm"
346 "char-nvidia-frontend"
347 "char-nvidia-caps"
348 "char-nvidiactl"
349 ]
350 else
351 "";
352 DevicePolicy = "closed";
353 LockPersonality = true;
354 MemoryDenyWriteExecute = true;
355 PrivateUsers = true;
356 ProtectHome = true;
357 ProtectHostname = true;
358 ProtectKernelLogs = true;
359 ProtectKernelModules = true;
360 ProtectKernelTunables = true;
361 ProtectControlGroups = true;
362 ProtectProc = "invisible";
363 # "all" is required because faster-whisper accesses /proc/cpuinfo to determine cpu capabilities
364 ProcSubset = "all";
365 RestrictAddressFamilies = [
366 "AF_INET"
367 "AF_INET6"
368 "AF_UNIX"
369 ];
370 RestrictNamespaces = true;
371 RestrictRealtime = true;
372 SystemCallArchitectures = "native";
373 SystemCallFilter = [
374 "@system-service"
375 "~@privileged"
376 ];
377 UMask = "0077";
378 };
379 }
380 ) cfg.servers;
381 };
382}