From c0fcb039cac3c699c4ad7ec2a87f7155218c0414 Mon Sep 17 00:00:00 2001 From: Sloptimist Prime <315576608+SloptimistPrime@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:48:00 -0500 Subject: [PATCH] Package the core CLI with Nix --- .github/workflows/nix.yml | 28 +++++++++++++++ README.md | 3 ++ flake.lock | 27 ++++++++++++++ flake.nix | 76 +++++++++++++++++++++++++++++++++++++++ nix/check-installed.py | 24 +++++++++++++ nix/package.nix | 62 ++++++++++++++++++++++++++++++++ notes/nix.md | 61 +++++++++++++++++++++++++++++++ 7 files changed, 281 insertions(+) create mode 100644 .github/workflows/nix.yml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/check-installed.py create mode 100644 nix/package.nix create mode 100644 notes/nix.md diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000..381c8fa --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,28 @@ +name: nix + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +jobs: + check: + strategy: + fail-fast: false + matrix: + runner: [ubuntu-24.04, ubuntu-24.04-arm] + runs-on: ${{ matrix.runner }} + timeout-minutes: 20 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Check formatting + run: nix fmt -- --check flake.nix nix/package.nix + - name: Build core package and test installed commands + run: nix flake check --no-write-lock-file diff --git a/README.md b/README.md index 408cf6d..cf75391 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,9 @@ flowchart LR ## Quick start +Nix/NixOS users can build and install the core CLI with the +[Nix flake](notes/nix.md). GPU quantization dependencies and llama.cpp are separate. + ```bash ./install.sh # tools + the llama.cpp runtime, one shot pollard-calc --model Qwen/Qwen3-30B-A3B --ram 16 # what CAN this machine do diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..4b67419 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1791048980, + "narHash": "sha256-KgItSKML8Xvte0B7/uGnBDsYzSnnKHcOaiUbgWBXLXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a7868a727837f3c09cee2ce0ca671c76b1589fed", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f00bc89 --- /dev/null +++ b/flake.nix @@ -0,0 +1,76 @@ +{ + description = "Pollard Weights core CLI and development environment"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = + { self, nixpkgs }: + let + systems = [ + "aarch64-linux" + "x86_64-linux" + "aarch64-darwin" + ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + in + { + overlays.default = final: prev: { + pollard-weights = final.callPackage ./nix/package.nix { }; + }; + + packages = forAllSystems (pkgs: rec { + pollard-weights = pkgs.callPackage ./nix/package.nix { }; + default = pollard-weights; + }); + + apps = forAllSystems (pkgs: { + default = { + type = "app"; + program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.default}/bin/pollard"; + meta.description = "Pollard Weights automatic planning CLI"; + }; + calc = { + type = "app"; + program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.default}/bin/pollard-calc"; + meta.description = "Pollard model memory calculator"; + }; + }); + + checks = forAllSystems (pkgs: { + package = self.packages.${pkgs.stdenv.hostPlatform.system}.default; + installed-cli = + pkgs.runCommand "pollard-installed-cli" + { + nativeBuildInputs = [ + self.packages.${pkgs.stdenv.hostPlatform.system}.default + pkgs.python3 + ]; + } + '' + mkdir -p work + cd work + unset PYTHONPATH + ${pkgs.python3}/bin/python ${./nix/check-installed.py} + touch "$out" + ''; + }); + + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = [ + (pkgs.python3.withPackages (ps: [ + ps.numpy + ps.gguf + ps.pytest + ps.setuptools + ps.build + ])) + pkgs.git + pkgs.nixfmt + ]; + }; + }); + + formatter = forAllSystems (pkgs: pkgs.nixfmt); + }; +} diff --git a/nix/check-installed.py b/nix/check-installed.py new file mode 100644 index 0000000..7bdfd7d --- /dev/null +++ b/nix/check-installed.py @@ -0,0 +1,24 @@ +"""Exercise store-installed commands outside the checkout, without network/GPU.""" +import json +from pathlib import Path +import subprocess + +COMMANDS = ("pollard", "pollard-calc", "pollard-fit", "pollard-experts", + "pollard-runtime", "pollard-serve-eval", "pollard-ggufcheck", "pollard-node", + "pollard-ngram", "pollard-forge", "pollard-thinklean", "pollard-kvbench") + +for command in COMMANDS: + result = subprocess.run([command, "--help"], capture_output=True, text=True, timeout=30) + assert result.returncode == 0, f"{command}: {result.stderr}" + assert "usage:" in result.stdout.lower(), f"{command}: no CLI help" + +Path("config.json").write_text(json.dumps({ + "model_type": "llama", "hidden_size": 1024, "num_hidden_layers": 4, + "num_attention_heads": 8, "num_key_value_heads": 8, + "intermediate_size": 4096, "vocab_size": 32000, +})) +result = subprocess.run(["pollard-calc", "--config", "config.json", "--ram", "32", "--ctx", "4096"], + capture_output=True, text=True, timeout=30) +assert result.returncode == 0, result.stderr +assert "32 GB RAM" in result.stdout and "KV" in result.stdout, result.stdout +print(f"{len(COMMANDS)} installed commands and offline calculator passed") diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..0d93e25 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,62 @@ +{ + lib, + python3, + git, +}: +let + project = (builtins.fromTOML (builtins.readFile ../pyproject.toml)).project; +in +python3.pkgs.buildPythonApplication { + pname = project.name; + inherit (project) version; + pyproject = true; + + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ + ../pyproject.toml + ../README.md + ../LICENSE + (lib.fileset.fileFilter ( + file: + builtins.any file.hasExt [ + "py" + "html" + "css" + "js" + "png" + ] + ) ../tools) + (lib.fileset.fileFilter (file: file.hasExt "py") ../tests) + (lib.fileset.fileFilter (file: file.hasExt "py") ../experiments) + ]; + }; + build-system = [ python3.pkgs.setuptools ]; + dependencies = with python3.pkgs; [ + numpy + gguf + ]; + + nativeCheckInputs = [ + python3.pkgs.pytestCheckHook + git + ]; + # The core package excludes GUI dependencies; Studio has its own pip extra. + # Optional tensor fixtures skip here when torch is absent. + pytestFlags = [ + "tests" + "--ignore=tests/studio" + ]; + pythonImportsCheck = [ + "pollard_calc" + "pollard_serve_eval" + ]; + + meta = { + inherit (project) description; + homepage = "https://github.com/WestWaters/pollard-weights"; + license = lib.licenses.asl20; + mainProgram = "pollard"; + platforms = lib.platforms.unix; + }; +} diff --git a/notes/nix.md b/notes/nix.md new file mode 100644 index 0000000..eaa7fcc --- /dev/null +++ b/notes/nix.md @@ -0,0 +1,61 @@ +# Nix installation + +The flake packages the core Python tools with numpy and gguf. It does not run +`install.sh`, modify your system or fetch models. `flake.lock` pins Nixpkgs and +the Python dependency set. The package source is limited to packaging metadata, +tools, tests and Python experiments, excluding runtime logs and local caches. +Use a clean checkout: Nix flake inputs themselves can enter the world-readable +store before the package source filter runs. Never keep secrets in tracked files. + +From this checkout: + +```sh +nix build # result/bin contains the commands +nix run . -- --help # pollard +nix run .#calc -- --help # pollard-calc +nix profile install . # install the core commands in your profile +nix flake check # core tests and installed-command checks +``` + +To add the package to a NixOS configuration, add this repository as a flake input +and use `inputs.pollard.packages.${pkgs.stdenv.hostPlatform.system}.default` in +`environment.systemPackages`. No overlay is required; `overlays.default` is also +available for configurations that prefer it. + +For source development: + +```sh +nix develop +python tools/pollard_calc.py --help +pytest tests --ignore=tests/studio +nix fmt -- flake.nix nix/package.nix +``` + +The development shell supplies core Python dependencies and pytest, not an +editable installation. Run source files with Python to test local edits. + +## Runtime boundaries + +This is a core CLI package, not a CUDA-enabled inference or quantization stack. +Like a core pip installation, entry points for optional tools are present, but +Studio and the Torch/GPTQ/EXL3/MX/MLX paths need their respective dependencies. +Their presence in `result/bin` is not a claim that those paths work. The Nix +checks exclude Studio's tests and skip optional tensor fixtures when Torch is +absent. Studio and GPU extras require separate test environments; installing +this core package does not validate them. + +llama.cpp is separate too: supply a compatible build with the executables your +chosen lane requires on PATH. This flake does not enable RPC, choose a GPU +backend, or guarantee compatibility with the project's current runtime recipes. + +Pollard launches several helpers with its own `sys.executable`. A host Python +environment containing Torch does not automatically add Torch to this package's +Nix Python environment. Do not use `pip install --user` or force another +interpreter on the wrappers as a workaround. GPU extras need an explicitly +packaged and tested Python environment, with driver/runtime compatibility +checked separately. That is outside this core installation change. + +The flake exposes Linux x86_64/ARM64 and macOS ARM64 outputs. Intel macOS is +excluded because the pinned Nixpkgs branch no longer supports it. Native build +results should be reported separately from evaluation of another platform's +derivation; evaluation alone is not a successful build.