diff --git a/nixos/modules/services/networking/llama-swap.nix b/nixos/modules/services/networking/llama-swap.nix index b68c1b650c93..0a838afd155f 100644 --- a/nixos/modules/services/networking/llama-swap.nix +++ b/nixos/modules/services/networking/llama-swap.nix @@ -7,7 +7,29 @@ let cfg = config.services.llama-swap; settingsFormat = pkgs.formats.yaml { }; - configFile = settingsFormat.generate "config.yaml" cfg.settings; + unvalidatedConfigFile = settingsFormat.generate "llama-swap-config.yaml" cfg.settings; + + # similar to writeNginxConfig + validateLlamaSwapConfig = + file: + pkgs.runCommandLocal "llama-swap-validated-config.yaml" + { + inherit file; + __structuredAttrs = true; + nativeBuildInputs = [ + cfg.package + ]; + } # sh + '' + llama-swap -validate -config $file + cp $file $out + ''; + + configFile = + if cfg.validateConfigFile then + validateLlamaSwapConfig unvalidatedConfigFile + else + unvalidatedConfigFile; in { options.services.llama-swap = { @@ -15,6 +37,10 @@ in package = lib.mkPackageOption pkgs "llama-swap" { }; + validateConfigFile = lib.mkEnableOption "validating configuration with `llama-swap -validate`" // { + default = true; + }; + listenAddress = lib.mkOption { type = lib.types.str; default = "localhost"; diff --git a/nixos/tests/web-servers/llama-swap.nix b/nixos/tests/web-servers/llama-swap.nix index 1e80f6ab233c..5f022c67880b 100644 --- a/nixos/tests/web-servers/llama-swap.nix +++ b/nixos/tests/web-servers/llama-swap.nix @@ -54,6 +54,7 @@ let machine = { pkgs, ... }: { + environment.systemPackages = [ pkgs.jq ]; # running models can be memory intensive but # default `virtualisation.memorySize` is fine @@ -118,6 +119,7 @@ let '' # core tests import json + import datetime as dt def get_json(route): args = [ @@ -144,7 +146,7 @@ let '' + lib.optionalString withUI '' with subtest('check is serving ui'): - machine.succeed('curl --fail http:/localhost:8080/ui/') + machine.succeed('curl --fail http://localhost:8080/ui/') '' + '' @@ -156,7 +158,9 @@ let + lib.optionalString useSmollm2-135m '' # extended tests using SmolLM2 with subtest('check `/running` for preloaded smollm2'): - machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | grep "smollm2"') + # wait until quick jq check passes + machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | jq -e ".running[] | select(.model == \\"smollm2\\" and .state == \\"ready\\")"') + # now check granular details running_response = get_json('/running') assert len(running_response['running']) == 1, f'running doesn\'t have one entry as expected: {running_response}' running_model = running_response['running'][0] @@ -199,7 +203,7 @@ let assert running_model['state'] == 'ready', f'running smollm2 is not ready: {running_response}' with subtest('check `/running` for smollm2 to timeout'): - machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | grep -v "smollm2"', timeout=11) + machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | grep -v "smollm2"', timeout=dt.timedelta(seconds=11)) running_response = get_json('/running') assert len(running_response['running']) == 0, "smollm2 was still running after timeout" diff --git a/pkgs/by-name/ll/llama-swap/package.nix b/pkgs/by-name/ll/llama-swap/package.nix index 85cae79471c4..23b1ed9c097c 100644 --- a/pkgs/by-name/ll/llama-swap/package.nix +++ b/pkgs/by-name/ll/llama-swap/package.nix @@ -2,7 +2,7 @@ lib, stdenv, - buildGoModule, + buildGo127Module, fetchFromGitHub, versionCheckHook, @@ -19,20 +19,23 @@ let canExecute = stdenv.buildPlatform.canExecute stdenv.hostPlatform; in -buildGoModule (finalAttrs: { +buildGo127Module (finalAttrs: { pname = "llama-swap"; - version = "249"; + version = "262"; outputs = [ "out" + "kubeswap" "wol" # wake on lan proxy + "vllm_wrapper" # helper for using `vllm` with `--enable-sleep-mode` + "utils" ]; src = fetchFromGitHub { owner = "mostlygeek"; repo = "llama-swap"; tag = "v${finalAttrs.version}"; - hash = "sha256-7wXOL8XtcKV6Abdxar25C85ODQ34RYOAGYCTaCXxPpY="; + hash = "sha256-DAYaR+N7alGbdLsGlaiDlsHiqxOMg65069i/vHOkLKk="; # populate values that require us to use git. By doing this in postFetch we # can delete .git afterwards and maintain better reproducibility of the src. leaveDotGit = true; @@ -45,7 +48,7 @@ buildGoModule (finalAttrs: { ''; }; - vendorHash = "sha256-MhR8B2+Yb/xqrTlIxaVHLoQf1eTOO49c65l72IAuZyU="; + vendorHash = "sha256-yelob7FlaGymASUP0DAUkALQm5vnXZnN5ThbnSkH2Ak="; # Upstream only embeds the UI when this build tag is set. tags = lib.optionals withUI [ "embed_ui" ]; @@ -68,6 +71,8 @@ buildGoModule (finalAttrs: { postPatch = '' substituteInPlace internal/process/process_command_forking_test.go \ --replace-fail "#!/bin/bash" "#!${lib.getExe bash}" + substituteInPlace cmd/vllm-wrapper/main_test.go \ + --replace-fail "#!/bin/bash" "#!${lib.getExe bash}" ''; preBuild = '' @@ -86,8 +91,10 @@ buildGoModule (finalAttrs: { "misc/process-cmd-test" # benchmark/regression testing tool "misc/benchmark-chatcompletion" + # testing tool for the performance monitoring code + "cmd/monitor-test" ] - ++ lib.optionals (!canExecute) [ + ++ lib.optionals finalAttrs.doCheck [ # some tests expect to execute `simple-something`; if it can't be executed # it's unneeded "misc/simple-responder" @@ -124,14 +131,44 @@ buildGoModule (finalAttrs: { mkdir build ln -s "$GOPATH/bin/simple-responder" "./build/simple-responder_''${GOOS}_''${GOARCH}" ''; + # doesn't need to be cleaned up outside of the check path as we don't build it + # due to `excludedPackages` postCheck = '' rm "$GOPATH/bin/simple-responder" ''; - postInstall = '' + # project regularly adds small utility binaries + # this `installPhase` means we need to sort them + # into outputs or skip building them + installPhase = '' + runHook preInstall + install -Dm444 -t "$out/share/llama-swap" config.example.yaml + + mkdir -p "$out/bin" + mv "$GOPATH/bin/llama-swap" "$out/bin/" + + mkdir -p "$kubeswap/bin" + mv "$GOPATH/bin/kubeswap" "$kubeswap/bin/" + mkdir -p "$wol/bin" - mv "$out/bin/wol-proxy" "$wol/bin/" + mv "$GOPATH/bin/wol-proxy" "$wol/bin/" + + mkdir -p "$vllm_wrapper/bin" + mv "$GOPATH/bin/vllm-wrapper" "$vllm_wrapper/bin/" + + mkdir -p "$utils/bin" + mv "$GOPATH/bin/"{fake-model,test-concurrency} "$utils/bin/" + + # log if we don't handle a new executable + EXECUTABLES=$(find "$GOPATH/bin" -maxdepth 1 -type f -executable -print -quit) + if [ -n "$EXECUTABLES" ]; then + echo "Error: Directory '$GOPATH/bin' still contains executables." >&2 + echo " $EXECUTABLES" >&2 + exit 1 + fi + + runHook postInstall ''; doInstallCheck = true; diff --git a/pkgs/by-name/ll/llama-swap/ui.nix b/pkgs/by-name/ll/llama-swap/ui.nix index a8871af6532a..135daeb4599e 100644 --- a/pkgs/by-name/ll/llama-swap/ui.nix +++ b/pkgs/by-name/ll/llama-swap/ui.nix @@ -7,14 +7,14 @@ buildNpmPackage (finalAttrs: { pname = "${llama-swap.pname}-ui"; inherit (llama-swap) version src; - npmDepsHash = "sha256-6MPXQtmaz97D9PUU2Nn5DH/2HZNP/rnAWVSck/FiCyk="; + npmDepsHash = "sha256-lmhRJ8275PIQ+7vHdr9aZ31lYeXUkXrWnlvuwOadjRQ="; postPatch = '' substituteInPlace vite.config.ts \ --replace-fail "../internal/server/ui_dist" "${placeholder "out"}/ui_dist" ''; - sourceRoot = "${finalAttrs.src.name}/ui-svelte"; + sourceRoot = "${finalAttrs.src.name}/ui"; # bundled "ui_dist" doesn't need node_modules postInstall = ''