Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -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);
};
}
24 changes: 24 additions & 0 deletions nix/check-installed.py
Original file line number Diff line number Diff line change
@@ -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")
62 changes: 62 additions & 0 deletions nix/package.nix
Original file line number Diff line number Diff line change
@@ -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;
};
}
61 changes: 61 additions & 0 deletions notes/nix.md
Original file line number Diff line number Diff line change
@@ -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.
Loading