Repository navigation
Expand file tree
/
Copy pathflake.nix
More file actions
311 lines (300 loc) · 15.9 KB
/
Copy pathflake.nix
File metadata and controls
311 lines (300 loc) · 15.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
{
# Nix flake: hermetic dev shell + reproducible Linux build.
#
# WHY a flake in a repo that mainly ships via GitHub Actions: it is the
# strongest reproducibility claim we can make. `nix develop` gives every
# engineer the same compiler, linker, and packaging tools down to the store
# hash, and `nix build` produces the Linux binary in a sandbox with no
# network and no host toolchain, which is the environment in which
# "reproducible" stops being a slogan. It also gets the project into
# nixpkgs/NixOS distribution basically for free later.
#
# Toolchain source: rust-overlay reads rust-toolchain.toml, so the flake
# and rustup agree on the compiler BY CONSTRUCTION rather than by two pins
# that drift apart.
description = "outloud-spike: local edit-by-voice M0 spike";
inputs = {
# nixos-unstable, not a stable release: the ALSA client library must be
# new enough for the PipeWire ALSA plugin on the host. Pinned to 25.05,
# microphone capture failed on current NixOS with
# "snd_pcm_open failed ... No such device or address" because
# libasound_module_pcm_pipewire.so refused to load into the older
# libasound.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, rust-overlay, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default ];
};
# The single source of truth for the compiler version is
# rust-toolchain.toml; see the WHY at the top of that file.
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
outloudSpike = pkgs.rustPlatform.buildRustPackage {
pname = "outloud-spike";
version = "0.1.0";
src = self;
cargoLock.lockFile = ./Cargo.lock;
# Linux: cpal pulls alsa-sys, which needs pkg-config + alsa headers.
# makeWrapper: see postFixup below.
nativeBuildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.pkg-config
pkgs.makeWrapper
];
buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.alsa-lib ];
# Text delivery on Wayland SHELLS OUT: the synthetic-keys tier runs
# `wtype` (virtual-keyboard protocol) and the paste fallback runs
# `wl-copy`/`wl-paste`. Those are looked up on PATH at runtime, so
# without this the package "works" only on machines that happen to
# have them installed globally, and degrades to a confusing
# no-delivery state on any other -- the exact class of bug a Nix
# package exists to prevent. Wrap the binaries so the tools are
# always found, while still PREPENDING rather than replacing PATH
# so a user's own newer wtype wins if they want it.
postFixup = pkgs.lib.optionalString pkgs.stdenv.isLinux ''
for _bin in $out/bin/*; do
wrapProgram "$_bin" \
--prefix PATH : ${
pkgs.lib.makeBinPath [
pkgs.wtype
pkgs.wl-clipboard
]
}
done
'';
# The sandboxed nix build is itself the reproducibility check:
# no network, no impure env, pinned inputs. macOS-specific FFI in
# ax-edit compiles to the Unsupported stub on Linux, so this
# builds everywhere the flake evaluates.
doCheck = true;
meta = {
description = "M0 spike harness for local edit-by-voice";
license = pkgs.lib.licenses.mit;
mainProgram = "spike-cli";
};
};
# CUDA-accelerated `outloud` for Linux, built with whisper-rs/cuda.
# `null` everywhere but x86_64-linux, folded out of `packages` below
# by `lib.filterAttrs`, so this is the ONLY place a system check
# needs to live: every other output (`devShells`, `packages.default`)
# stays a plain per-system value with no CUDA-shaped conditionals
# anywhere near it.
#
# WHY a separate package rather than teaching `packages.default` a
# `cudaSupport` flag: `packages.default` must stay buildable by
# `nix build` with the sandbox's normal (network-disabled, no GPU,
# free-software-only) settings, because that sandboxed build IS the
# reproducibility check the top-of-file comment promises, and it
# runs on every system the flake evaluates, including macOS CI. CUDA
# is NVIDIA's unfree redistributable, x86_64-linux only, and multiple
# gigabytes once unpacked; making it reachable from the default
# output would mean either eval-time `allowUnfree`/`cudaSupport`
# config bleeding into every other package built from this flake
# (nixpkgs config is a single global knob per `pkgs` instantiation),
# or a conditional so tangled it stops being auditable. A second
# `pkgs` import scoped to this one output keeps `packages.default`
# and `devShells.default` exactly as they were: CPU-only,
# unfree-free, and buildable everywhere including macOS.
#
# Matches whisper.cpp's own upstream CUDA recipe
# (nixpkgs pkgs/by-name/wh/whisper-cpp/package.nix), which is the
# only build in nixpkgs solving this exact problem (whisper.cpp +
# CUDA via cmake) and is exercised by nixpkgs CI: `backendStdenv`
# (CUDA imposes an upper bound on the host gcc version whisper-rs's
# own `cmake` crate does not know to enforce), `cuda_nvcc` +
# `autoAddDriverRunpath` as native inputs, `cccl` (for `<nv/target>`,
# a header whisper.cpp's CUDA path includes) + `cuda_cudart` +
# `libcublas` as link inputs, matching exactly what
# `whisper-rs-sys`'s build.rs links against (cublas, cudart,
# cublasLt, cuda, culibos -- see crates/asr/Cargo.toml's whisper-cuda
# feature and its sibling whisper-rs-sys build.rs for the full list).
outloudCuda =
if system != "x86_64-linux" then null else
let
cudaPkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ rust-overlay.overlays.default ];
config = {
allowUnfree = true;
cudaSupport = true;
# NOTE: nixpkgs' `cudaCapabilities` does NOT reach whisper.cpp
# here, which is why it is not set. whisper-rs-sys drives its
# own cmake invocation (sys/build.rs sets GGML_CUDA and
# CMAKE_CUDA_FLAGS by hand), and ggml's ggml-cuda/CMakeLists
# then fills in its own architecture list whenever
# CMAKE_CUDA_ARCHITECTURES is undefined:
# 50-virtual 61-virtual 70-virtual 75-virtual 80-virtual
# 86-real 89-real 90-virtual
# That list is eight architectures of nvcc work -- and it
# contains no 12.0, so it would not even emit Blackwell
# kernels for the card this package exists for. The real
# control is the CUDAARCHS environment variable, which cmake
# uses to initialise CMAKE_CUDA_ARCHITECTURES; see
# `env.CUDAARCHS` on the derivation below.
};
};
cudaToolchain =
cudaPkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
# `makeRustPlatform` rather than the plain `cudaPkgs.rustPlatform`:
# this is what actually rebinds the derivation builder to
# `backendStdenv.mkDerivation`. Setting a `stdenv = ...` attribute
# inside `buildRustPackage`'s argument set would NOT do that --
# `rustPlatform.buildRustPackage` is already a function closed
# over a fixed `stdenv.mkDerivation`, baked in when the platform
# was constructed, so a same-named attribute in the call site
# would just be inert. `makeRustPlatform` is nixpkgs' own
# supported seam for changing which stdenv a Rust build uses.
cudaRustPlatform = cudaPkgs.makeRustPlatform {
cargo = cudaToolchain;
rustc = cudaToolchain;
stdenv = cudaPkgs.cudaPackages.backendStdenv;
};
in
cudaRustPlatform.buildRustPackage {
pname = "outloud-cuda";
version = "0.1.0";
src = self;
cargoLock.lockFile = ./Cargo.lock;
nativeBuildInputs = [
cudaPkgs.pkg-config
# `cmake`: what `whisper-rs-sys`'s build.rs shells out to.
# Absent from the default package's inputs because that build
# never turns on the `whisper` feature at all.
cudaPkgs.cmake
cudaPkgs.cudaPackages.cuda_nvcc
cudaPkgs.autoAddDriverRunpath
# `whisper-rs-sys` also runs `bindgen` to generate the FFI
# layer over whisper.cpp's C API, which needs libclang and a
# `LIBCLANG_PATH` pointed at it -- the exact failure mode
# `docs/asr-integration.md` documents for Windows ("Unable to
# find libclang") and that is just as real on Linux, only
# undocumented here because nothing in this flake had turned
# on the `whisper` feature before now. `bindgenHook` is
# nixpkgs' standard fix: a setup hook that exports
# `LIBCLANG_PATH` for the duration of the build. Pulled from
# the ordinary (non-CUDA) `rustPlatform`, not
# `cudaRustPlatform`: the hook only shells out to `clang`, has
# nothing to do with the compiler bound to `mkDerivation`, and
# constructing it from `cudaRustPlatform` before it exists
# here would be a definition-order cycle.
cudaPkgs.rustPlatform.bindgenHook
# makeWrapper: see postFixup below.
cudaPkgs.makeWrapper
];
buildInputs = [
cudaPkgs.alsa-lib
cudaPkgs.cudaPackages.cccl
cudaPkgs.cudaPackages.cuda_cudart
cudaPkgs.cudaPackages.libcublas
];
# `-lcuda` is the DRIVER library, not part of the toolkit: it
# ships with the installed NVIDIA driver and is deliberately
# absent from anything Nix can vendor. The build still has to
# LINK against it, which is what the `stubs` output exists for:
# an ABI-compatible libcuda.so that resolves symbols at link
# time and is never loaded at runtime. Without this the whole
# CUDA build compiles -- cmake, nvcc, every .cu kernel -- and
# then dies on the final link with
# rust-lld: error: unable to find library -lcuda
# which reads like a missing dependency rather than the
# deliberate toolkit/driver split that it is.
#
# `autoAddDriverRunpath` (in nativeBuildInputs) then rewrites the
# finished binary's RUNPATH to /run/opengl-driver/lib, so at
# RUNTIME it picks up the real libcuda from the host driver
# rather than this stub.
# This nixpkgs keeps the stubs INSIDE cuda_cudart's default
# output (pkgs/development/cuda-modules/packages/cuda_cudart.nix:
# "We have stubs but we don't have an explicit stubs output"), so
# the path is $out/lib/stubs and there is no `.stubs` attribute
# to reference -- asking for one fails evaluation with
# "attribute 'stubs' missing".
NIX_LDFLAGS = "-L${cudaPkgs.lib.getLib cudaPkgs.cudaPackages.cuda_cudart}/lib/stubs";
# `buildFeatures`/`checkFeatures`, not `cargoBuildFeatures`: the
# latter is `buildRustPackage`'s INTERNAL derived env-var name
# (see nixpkgs pkgs/build-support/rust/build-rust-package), and
# passing it directly is silently accepted as an arbitrary extra
# derivation attribute rather than an error -- it shows up in
# `nix derivation show` looking plausible and does precisely
# nothing. Caught only by inspecting the actual derivation env
# (`cargoBuildFeatures` came back empty) rather than by the
# flake evaluating without error, which it did either way.
buildFeatures = [ "outloud/whisper-cuda" ];
# Build Blackwell kernels only (sm_120, the RTX 5090). cmake
# seeds CMAKE_CUDA_ARCHITECTURES from CUDAARCHS, which is the
# only lever that reaches ggml's CUDA build through
# whisper-rs-sys's own cmake run -- see the note on cudaSupport
# above for why the nixpkgs-level setting does nothing here.
#
# "120-real" and not "120": `-real` emits SASS for that
# architecture without also embedding PTX. PTX exists so a
# future GPU can JIT the kernels, which is a tradeoff worth
# making for a redistributable binary and not for one built
# against a known card -- it is roughly double the compile for
# hardware this package is not for.
env.CUDAARCHS = "120-real";
# The sandbox has no GPU (CUDA's own docs are explicit that the
# driver's user-mode libraries, libcuda.so included, come from
# the host driver install and are never part of the CUDA
# toolkit/redistributables Nix can vendor), so a real whisper.cpp
# model load and inference pass cannot run here or in CI. The
# sandboxed build DOES still exercise the entire compile and
# link step -- cmake configuring GGML_CUDA, nvcc compiling the
# .cu kernels, and the final binary linking against libcuda,
# libcudart and libcublas -- which is everything short of
# touching a physical device. See docs/asr-integration.md and
# the swarm handoff notes for exactly what still needs a real
# GPU to confirm.
doCheck = false;
# Same Wayland delivery tools as the default package: this is a
# separate derivation, so it does NOT inherit that postFixup and
# would otherwise ship a CUDA binary that transcribes perfectly
# and then cannot type the result anywhere.
postFixup = ''
for _bin in $out/bin/*; do
wrapProgram "$_bin" \
--prefix PATH : ${
cudaPkgs.lib.makeBinPath [
cudaPkgs.wtype
cudaPkgs.wl-clipboard
]
}
done
'';
meta = {
description = "outloud with whisper.cpp CUDA acceleration (NVIDIA, x86_64-linux)";
license = pkgs.lib.licenses.mit;
mainProgram = "outloud";
};
};
in
{
devShells.default = pkgs.mkShell {
packages = [
toolchain
pkgs.cargo-deny
pkgs.cargo-audit
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [
# Linux packaging tools used by scripts/build-linux.sh.
pkgs.dpkg
pkgs.rpm
pkgs.binutils # objdump for scripts/ci-verify-baseline.sh
];
};
# `filterAttrs` drops `outloud-cuda` entirely on every system but
# x86_64-linux (it is `null` there, see `outloudCuda` above), rather
# than exposing a `null`-valued attribute that `nix build .#outloud-cuda`
# would fail on with a confusing error far from this comment.
packages = pkgs.lib.filterAttrs (_: v: v != null) {
default = outloudSpike;
outloud-cuda = outloudCuda;
};
});
}