nixpkgs/nixos/modules/services/home-automation/wyoming/faster-whisper.nix
Martin Weinelt 0ed62e5f94
nixos/wyoming/faster-whisper: set huggingface home
This is the global prefix for all cache dirs. Everything the library
downloads is expendable, since faster-whisper copies the results over
into `/var/lib/wyoming/faster-whisper/`.

Since that makes the data redundant `/tmp` is preferable to `/var/cache`.

Fixes: #429974
2025-08-01 02:00:00 +02:00

383 lines
11 KiB
Nix

{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.wyoming.faster-whisper;
inherit (lib)
mapAttrsToList
mkOption
mkEnableOption
mkPackageOption
optionals
types
;
inherit (builtins)
toString
;
inherit (utils)
escapeSystemdExecArgs
;
in
{
options.services.wyoming.faster-whisper = with types; {
package = mkPackageOption pkgs "wyoming-faster-whisper" { };
servers = mkOption {
default = { };
description = ''
Attribute set of wyoming-faster-whisper instances to spawn.
'';
type = attrsOf (submodule {
options = {
enable = mkEnableOption "Wyoming faster-whisper server";
model = mkOption {
type = str;
default = "tiny-int8";
example = "Systran/faster-distil-whisper-small.en";
# https://github.com/home-assistant/addons/blob/master/whisper/DOCS.md#option-model
description = ''
Name of the voice model to use. Can also be a HuggingFace model ID or a path to
a custom model directory.
With {option}`useTranformers` enabled, a HuggingFace transformers Whisper model
ID from HuggingFace like `openai/whisper-tiny.en` must be used.
Compressed models (`int8`) are slightly less accurate, but smaller and faster.
Distilled models are uncompressed and faster and smaller than non-distilled models.
Available models:
- `tiny-int8` (compressed)
- `tiny`
- `tiny.en` (English only)
- `base-int8` (compressed)
- `base`
- `base.en` (English only)
- `small-int8` (compressed)
- `distil-small.en` (distilled, English only)
- `small`
- `small.en` (English only)
- `medium-int8` (compressed)
- `distil-medium.en` (distilled, English only)
- `medium`
- `medium.en` (English only)
- `large`
- `large-v1`
- `distil-large-v2` (distilled, English only)
- `large-v2`
- `distil-large-v3` (distilled, English only)
- `large-v3`
- `turbo` (faster than large-v3)
'';
};
useTransformers = mkOption {
type = bool;
default = false;
description = ''
Whether to provide the dependencies to allow using transformer models.
'';
};
uri = mkOption {
type = strMatching "^(tcp|unix)://.*$";
example = "tcp://0.0.0.0:10300";
description = ''
URI to bind the wyoming server to.
'';
};
device = mkOption {
# https://opennmt.net/CTranslate2/python/ctranslate2.models.Whisper.html#
type = enum [
"cpu"
"cuda"
"auto"
];
default = "cpu";
description = ''
Determines the platform faster-whisper is run on. CPU works everywhere, CUDA requires a compatible NVIDIA GPU.
'';
};
language = mkOption {
type = enum [
# https://github.com/home-assistant/addons/blob/master/whisper/config.yaml#L20
"auto"
"af"
"am"
"ar"
"as"
"az"
"ba"
"be"
"bg"
"bn"
"bo"
"br"
"bs"
"ca"
"cs"
"cy"
"da"
"de"
"el"
"en"
"es"
"et"
"eu"
"fa"
"fi"
"fo"
"fr"
"gl"
"gu"
"ha"
"haw"
"he"
"hi"
"hr"
"ht"
"hu"
"hy"
"id"
"is"
"it"
"ja"
"jw"
"ka"
"kk"
"km"
"kn"
"ko"
"la"
"lb"
"ln"
"lo"
"lt"
"lv"
"mg"
"mi"
"mk"
"ml"
"mn"
"mr"
"ms"
"mt"
"my"
"ne"
"nl"
"nn"
"no"
"oc"
"pa"
"pl"
"ps"
"pt"
"ro"
"ru"
"sa"
"sd"
"si"
"sk"
"sl"
"sn"
"so"
"sq"
"sr"
"su"
"sv"
"sw"
"ta"
"te"
"tg"
"th"
"tk"
"tl"
"tr"
"tt"
"uk"
"ur"
"uz"
"vi"
"yi"
"yue"
"yo"
"zh"
];
example = "en";
description = ''
The language used to to parse words and sentences.
'';
};
initialPrompt = mkOption {
type = nullOr str;
default = null;
# https://github.com/home-assistant/addons/blob/master/whisper/DOCS.md#option-custom_model_type
example = ''
The following conversation takes place in the universe of
Wizard of Oz. Key terms include 'Yellow Brick Road' (the path
to follow), 'Emerald City' (the ultimate goal), and 'Ruby
Slippers' (the magical tools to succeed). Keep these in mind as
they guide the journey.
'';
description = ''
Optional text to provide as a prompt for the first window. This can be used to provide, or
"prompt-engineer" a context for transcription, e.g. custom vocabularies or proper nouns
to make it more likely to predict those word correctly.
Not supported when the {option}`customModelType` is `transformers`.
'';
};
beamSize = mkOption {
type = ints.unsigned;
default = 0;
example = 5;
description = ''
The number of beams to use in beam search.
Use `0` to automatically select a value based on the CPU.
'';
apply = toString;
};
extraArgs = mkOption {
type = listOf str;
default = [ ];
description = ''
Extra arguments to pass to the server commandline.
'';
};
};
});
};
};
config =
let
inherit (lib)
mapAttrs'
mkIf
nameValuePair
;
in
mkIf (cfg.servers != { }) {
assertions = mapAttrsToList (server: options: {
assertion = options.useTransformers -> options.initialPrompt == null;
message = "wyoming-faster-whisper/${server}: Transformer models (`useTransformers`) do not currently support an `initialPrompt`.";
}) cfg.servers;
systemd.services = mapAttrs' (
server: options:
let
finalPackage = cfg.package.overridePythonAttrs (oldAttrs: {
dependencies =
oldAttrs.dependencies
# for transformer model support
++ optionals options.useTransformers oldAttrs.optional-dependencies.transformers;
});
in
nameValuePair "wyoming-faster-whisper-${server}" {
inherit (options) enable;
description = "Wyoming faster-whisper server instance ${server}";
wants = [
"network-online.target"
];
after = [
"network-online.target"
];
wantedBy = [
"multi-user.target"
];
# https://github.com/rhasspy/wyoming-faster-whisper/issues/27
# https://github.com/NixOS/nixpkgs/issues/429974
environment."HF_HOME" = "/tmp";
serviceConfig = {
DynamicUser = true;
User = "wyoming-faster-whisper";
StateDirectory = [ "wyoming/faster-whisper" ];
# https://github.com/home-assistant/addons/blob/master/whisper/rootfs/etc/s6-overlay/s6-rc.d/whisper/run
ExecStart = escapeSystemdExecArgs (
[
(lib.getExe finalPackage)
"--data-dir"
"/var/lib/wyoming/faster-whisper"
"--uri"
options.uri
"--device"
options.device
"--model"
options.model
"--language"
options.language
"--beam-size"
options.beamSize
]
++ lib.optionals options.useTransformers [
"--use-transformers"
]
++ lib.optionals (options.initialPrompt != null) [
"--initial-prompt"
options.initialPrompt
]
++ options.extraArgs
);
CapabilityBoundingSet = "";
DeviceAllow =
if
builtins.elem options.device [
"cuda"
"auto"
]
then
[
# https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
"char-nvidia-uvm"
"char-nvidia-frontend"
"char-nvidia-caps"
"char-nvidiactl"
]
else
"";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateUsers = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
# "all" is required because faster-whisper accesses /proc/cpuinfo to determine cpu capabilities
ProcSubset = "all";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
}
) cfg.servers;
};
}