hipfire
/docs · beta branch · view source · edit on GitHub

NixOS

Installing and running hipfire on NixOS.

First-class Nix flake support lives at the repo root (flake.nix) with packages and the NixOS module under nix/.

OutputPath / attributeRole
Packagepackages.hipfire (default)Native hipfire CLI + wrapped hipfire-daemon
Kernelspackages.hipfire-kernelsIndexed exact-source registry packages for gfx1201/gfx1100/gfx1151/gfx906/gfx942; also bundled with packages.hipfire
Dev shelldevShells.defaultRust and hipcc/ROCm tools when rocmSupport
ModulenixosModules.defaultservices.hipfire systemd unit(s)
Overlayoverlays.defaultPins rocmPackages from this flake’s nixpkgs + hipfire packages
FieldValue
Flake nixpkgs inputgithub:NixOS/nixpkgs/nixos-unstable
Module default port11435
System model dir option default/var/lib/hipfire/models (exported as HIPFIRE_MODELS_DIR; see modelDir)
Kernel package default targetsgfx1201, gfx1100, gfx1151, gfx906, gfx942

Truth state: shipped / ref-pinned for the flake/module surface described here. Pin a release tag or commit for production hosts; building github:warpfront/hipfire without a rev follows that input’s default branch and is not a stable release pin. Branch-only inference features are not implied by enabling the module.

Prerequisites

  • NixOS with flakes (examples below use nixos-unstable or a 25.11-class channel for your system flake; hipfire’s own flake input is unstable for ROCm 7.x — see ROCm).
  • AMD GPU with amdgpu loaded; /dev/kfd and /dev/dri/renderD* present.
  • Interactive users in video and render when not using the system service user.
ls /dev/kfd
ls /dev/dri/
# architecture hints:
rocminfo 2>/dev/null | grep -oP 'amdgcn-amd-amdhsa--\K\S+' | sort -u
# or:
grep gfx_target_version /sys/class/kfd/kfd/topology/nodes/*/properties

Quick start (flake packages)

Dev shell

nix develop github:warpfront/hipfire

Tools only — not a checkout. From a local tree:

nix develop
cargo build --release -p hipfire-daemon

Build CLI + daemon

nix build github:warpfront/hipfire
./result/bin/hipfire run qwen3.5:9b "Hello"

Stable pin example (replace rev/hash with a real release or commit):

hipfire.url = "github:warpfront/hipfire/<tag-or-commit>";

Precompiled kernels package

nix build github:warpfront/hipfire bundles indexed kernel objects beside hipfire-daemon-unwrapped. The separate nix build github:warpfront/hipfire#hipfire-kernels output contains the same exact-source registry objects and .index.json files. Override gpuTargets with the architecture of your GPU to avoid building the entire five-arch set; the NixOS module uses services.hipfire.gpuTargets. Targets outside the five-arch registry (for example gfx1200 or gfx1030) package no kernels, print an evaluation warning naming them, and are compiled by hipcc JIT on first use, as is any unregistered runtime route. Packaging fails if a required registry entry cannot compile; partial unindexed packages are not installed.

NixOS module

nixosModules.default → services.hipfire.

Minimal system example

# flake.nix (host)
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    hipfire.url = "github:warpfront/hipfire"; # pin rev for production
  };

  outputs = { nixpkgs, hipfire, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        hipfire.nixosModules.default
        {
          nixpkgs.overlays = [ hipfire.overlays.default ];
          services.hipfire.enable = true;
          services.hipfire.gpuTargets = [ "gfx1100" ]; # required non-empty
        }
      ];
    };
  };
}

Assertions when enable = true:

  • gpuTargets must be non-empty (Nix cannot probe the GPU at eval time).
  • With rocmSupport = true (default), hardware.graphics.enable must be true (the module defaults it on and registers the ROCm ICD).

Full options example

services.hipfire = {
  enable = true;
  gpuTargets = [ "gfx1100" ]; # required non-empty
  openFirewall = false;  # does NOT change listen address — only skips opening the port when the host firewall is enabled
  # openFirewall = true opens cfg.port to the network; the listener has no TLS and auth is optional (serve.api_key_file) — see SERVE.md

  defaultModel = "qwen3.5:9b";
  # Module writes these into config.toml as stored globals. run/serve sampling
  # send still omits bare globals (CLI flags / per-model / registry card only);
  # see CONFIG.md. Chat uses the global snapshot exception.
  temperature = 0.3;
  topP = 0.8;
  maxTokens = 512;       # module default; CLI global default is 4096 if unset in config
  maxSeq = 32768;
  repeatPenalty = 1.05;
  kvCache = "auto";
  dflashMode = "off";    # explicit global kill switch; omit to keep the registry per-model default
  idleTimeout = 300;

  # Writes native ~/.hipfire/models.toml per-model overrides.
  perModelSettings = {
    "qwen3.5:27b" = {
      max_seq = 16384;
      kv_cache = "q8";
      dflash_mode = "on";
    };
  };

  extraSettings = {
    # Canonical dotted keys are preferred; legacy snake_case aliases are
    # translated before writing config.toml.
    # Actual loopback-only listen (openFirewall=false alone is not enough):
    host = "127.0.0.1";
  };

  environment = {
    HIPFIRE_NORMALIZE_PROMPT = "1";
  };

  # Exported as HIPFIRE_MODELS_DIR and honored by the native CLI/TUI.
  modelDir = "/var/lib/hipfire/models";
};

Typed options are written to config.toml using the canonical dotted schema (serve.default_model, generation.top_p, serve.idle_timeout_seconds, …). See CONFIG.md for the full key set the CLI understands; module-typed keys are the subset in nix/module.nix.

modelDir and effective paths

services.hipfire.modelDir is exported as HIPFIRE_MODELS_DIR. The native CLI and TUI use it for tag/list/pull/pre-warm discovery:

ModeHOMEEffective CLI models path
System service (userService = false)/var/lib/hipfireconfigured services.hipfire.modelDir
User servicethe login user’s homeconfigured services.hipfire.modelDir

hipfire-setup creates modelDir, and the system service includes it in ReadWritePaths. Absolute model paths remain valid for ad-hoc runs.

Desktop / user service

services.hipfire = {
  enable = true;
  userService = true;
  gpuTargets = [ "gfx1201" ];
  # Must be an absolute path writable by the user running the unit. The default
  # /var/lib/hipfire/models belongs to the system service, so setup's
  # `mkdir -p` fails for an unprivileged user unit. Create the directory once
  # and hand it to that user, for example:
  #   sudo install -d -o <login-user> -g <login-group> /srv/hipfire/models
  # Do not write `~` or `$HOME` here: the value is passed verbatim to
  # `mkdir -p` and to HIPFIRE_MODELS_DIR, and neither expands it.
  modelDir = "/srv/hipfire/models";
  defaultModel = "";                             # module starts --no-prewarm
};

users.users.yourname.extraGroups = [ "video" "render" ];
# hipfire.service only has After=hipfire-setup — it does NOT Wants/Requires setup.
# Start setup first (or ensure default.target already ran it after login):
systemctl --user start hipfire-setup.service
systemctl --user start hipfire.service
systemctl --user status hipfire.service

User mode skips the dedicated hipfire system user and the oneshot precompile unit; setup still copies config and symlinks daemon/kernels under $HOME/.hipfire. Models for tag discovery live in the configured modelDir (HIPFIRE_MODELS_DIR), not under $HOME/.hipfire/models (see modelDir and effective paths).

Pinning source from the module

Precedence: services.hipfire.src > services.hipfire.github.rev > flake package default.

# branch / tag / commit via fetchFromGitHub
services.hipfire = {
  enable = true;
  gpuTargets = [ "gfx1100" ];
  github.rev = "v0.2.1";          # example pin — use a real tag/commit
  github.hash = "";               # first build prints the SRI hash
};

# fork
services.hipfire.github.owner = "my-username";
services.hipfire.github.repo = "hipfire";
services.hipfire.github.rev = "my-branch";
services.hipfire.github.hash = "sha256-…";

# Path source only (module type is nullOr path — not an arbitrary derivation).
# Use a checkout path or a path-coerced fetch result your Nix accepts as path:
services.hipfire.src = ./path/to/hipfire-checkout;
# Prefer github.* when you need fetchFromGitHub (typed owner/repo/rev/hash):
# services.hipfire.github.rev = "abc123…";
# services.hipfire.github.hash = "sha256-…";

Unreleased / working-branch checkouts are branch-implemented relative to whatever you pin — do not describe them as a stable release without an explicit tag or commit hash.

What the system service does

When userService = false (default):

UnitRole
hipfire-setup.serviceOneshot: native config and daemon symlink; the daemon resolves verified kernels from its immutable Nix package
hipfire.serviceExecStart = hipfire serve, restart on failure

Environment always includes HIPFIRE_MODELS_DIR=<modelDir>; the native CLI and TUI honor it for discovery and model lifecycle. With rocmSupport, LD_LIBRARY_PATH is set for nixpkgs ROCm. System mode also sets HOME=/var/lib/hipfire and HIPFIRE_KERNEL_CACHE=/var/cache/hipfire/kernels.

Security: hipfire serve has no TLS, and bearer authentication is optional (serve.api_key_file; SERVE.md). The default listen address is 127.0.0.1; set extraSettings.host = "0.0.0.0" to listen on all interfaces. openFirewall = false does not change the listen address — it only avoids opening cfg.port when the host firewall is enabled. Keep the firewall closed unless the host is on a trusted network or a TLS reverse proxy terminates in front of the port; a key is recommended on any non-loopback listen address. Prefer probing on loopback (127.0.0.1).

Device policy allows /dev/kfd, DRM char devices, and /dev/accel/accel0.

HTTP contract after start: SERVE.md.

GPU targets

Set only arches you need. Common labels (not an admission list):

ArchTypical hardware
gfx906Vega 20 / MI50
gfx908MI100
gfx1010RX 5700 XT
gfx1030RX 6800 XT class
gfx1100RX 7900 XTX class
gfx1151Strix Halo
gfx1200RX 9060 / 9060 XT class
gfx1201RX 9070 / 9070 XT, Radeon AI PRO R9700 class
services.hipfire.gpuTargets = [ "gfx1100" "gfx1030" ];

Module options reference

From nix/module.nix (defaults are module defaults, not every CLI default):

OptionTypeDefaultDescription
enableboolfalseEnable the service
openFirewallboolfalseOpen port when the host firewall is enabled; does not change listen address (default 127.0.0.1)
packagepackagepkgs.hipfirePackage to run
srcpath or nullnullOverride source path (not a package/derivation)
github.ownerstr"warpfront"fetch owner
github.repostr"hipfire"fetch repo
github.revstr or nullnullbranch/tag/commit
github.hashstr""SRI hash for fetch
kernelsPackagepackagepkgs.hipfire-kernelsExact-source indexed kernels bundled alongside the daemon
gpuTargetslist of str[] (required non-empty when enabled)Kernel package architectures
rocmSupportbooltrueUse nixpkgs ROCm + graphics ICD
portport11435Serve port
defaultModelstr""Empty omits serve.default_model and makes the module start hipfire serve --no-prewarm; a non-empty value is pre-warmed
temperaturefloat0.3Stored in config.toml only; not an effective run/serve send default (see CONFIG.md sampling send)
topPfloat0.8Stored global; same send caveat as temperature
maxTokensint512Written to config.toml
maxSeqint32768KV capacity
repeatPenaltyfloat1.05Stored global; same send caveat as temperature
kvCachestr"auto"KV mode string
dflashModeenum"auto" (unset)on / off / auto. Leaving it unset writes no speculation.dflash to config.toml, so the registry’s per-model policy applies: auto only for Qwen3.8-27B tiers whose registry entry declares a paired DFlash draft (CONFIG.md); every other model keeps the schema default off. Setting it explicitly, including "auto", writes a global speculation.dflash that outranks the registry layer ("auto" then applies the same auto to every model); "off" is the kill switch that disables DFlash for all models
idleTimeoutint300Idle unload seconds (0 = never)
extraSettingsattrs{}Extra config.toml keys (e.g. "serve.host" = "127.0.0.1"; legacy host is translated)
perModelSettingsattrs of attrs{}Writes native models.toml per-model overrides
environmentattrs of str{}Extra env for the unit
modelDirstr"/var/lib/hipfire/models"Exported as HIPFIRE_MODELS_DIR; native CLI/TUI model discovery uses it
userServiceboolfalsesystemd --user mode
user / groupstr"hipfire"System service identity

ROCm configuration

Default: flake overlay + nixpkgs ROCm

overlays.default re-imports this flake’s nixpkgs (unstable) with rocmSupport so rocmPackages tracks ROCm 7.x. Comment in-tree: nixos-25.11’s ROCm 6.4.3 libamdhip64 can segfault on gfx1151 during weight upload — prefer the overlay on Strix Halo hosts.

LD_LIBRARY_PATH is injected on the daemon/CLI wrappers and the systemd unit when rocmSupport = true. The wrappers, the kernel package build and the dev shell also set HIPFIRE_ROCM_PATH to the prefix built by nix/rocm-root.nix, which joins clr, rocm-runtime and rocm-device-libs: hipfire requires the HIP headers, libamdhip64, libhsa-runtime64, hipcc and the device bitcode under a single root, and nixpkgs splits them across those packages. The wrappers only set a default, so an exported HIPFIRE_ROCM_PATH wins.

Bring your own ROCm

services.hipfire = {
  rocmSupport = false;
  environment = {
    HIPFIRE_ROCM_PATH = "/opt/rocm";
    LD_LIBRARY_PATH = "/opt/rocm/lib";
  };
};

hipfire loads the HIP runtime and compiler from the one root named by HIPFIRE_ROCM_PATH; LD_LIBRARY_PATH alone does not select it. You must still satisfy GPU device access and any ICD requirements yourself.

Configuration precedence

Same layered model as the CLI:

  1. Engine / daemon defaults
  2. config.toml (module typed options + extraSettings)
  3. Per-model overlay from models.toml (perModelSettings)
  4. Environment (services.hipfire.environment and process env)

Interactive shells outside systemd only see variables you export yourself (env-vars.md).

Production smoke after deploy

Enabling the module starts hipfire serve; it does not by itself certify model quality or GPU numerical correctness.

On a machine with models in the configured services.hipfire.modelDir (/var/lib/hipfire/models by default; the service exports it as HIPFIRE_MODELS_DIR):

curl -s http://127.0.0.1:11435/health
# GPU serve semantics (manual); pass an absolute model file. --no-spawn attaches
# to the running service instead of starting a second server on the same GPU:
python3 scripts/serve_harness.py --no-spawn --port 11435 \
  --model /var/lib/hipfire/models/<file> --tag qwen3.5:9b

Route selection: VALIDATION.md. No-GPU CI does not replace this.

Troubleshooting

libamdhip64.so not found

With rocmSupport = true, confirm the overlay is applied and rocmPackages.clr builds. With BYO ROCm, check LD_LIBRARY_PATH and the .so path.

Permission denied on /dev/kfd

Add the runtime user to video and render, rebuild, re-login (user sessions). The system service user already gets those groups.

No AMD GPU detected

lsmod | grep amdgpu
hardware.firmware = [ pkgs.linux-firmware ];
# and/or:
hardware.amdgpu.initrd.enable = true;

Ensure hardware.graphics.enable = true when using bundled ROCm.

Indexed kernel package build fails

hipfire-kernels now fails its build when an admitted registry entry cannot compile; there is no partial package or boot-time hipfire-precompile service. Check the selected gpuTargets, ROCm hipcc and device libraries, then rebuild. gfx1151 expects ROCm 7.2+ class tooling (aligned with the container base).

  • Serve HTTP: SERVE.md
  • Config keys: CONFIG.md
  • Containers: CONTAINER.md
  • Package implementation: nix/package.nix, nix/kernels.nix, nix/dev-shell.nix, nix/module.nix