Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

### Fixes

- NixOS: fix `qmd embed` crash on immutable-root systems. Prebuilt
node-llama-cpp binaries expect FHS paths like `/lib64/libc.so.6`
which don't exist on NixOS. The flake wrapper sets `LD_LIBRARY_PATH`
to include Nix's glibc and libstdc++. When the install directory
is read-only, `getLlama()` uses `build: "never"` to skip source
builds that would fail with EACCES.
- GPU: respect explicit `QMD_LLAMA_GPU=metal|vulkan|cuda` backend overrides instead of always using auto GPU selection. #529
- Fix: preserve original filename case in `handelize()`. The previous
`.toLowerCase()` call made indexed paths unreachable on case-sensitive
Expand Down
6 changes: 3 additions & 3 deletions flake.lock

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

31 changes: 22 additions & 9 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils }:
outputs =
{
homeModules.default = { config, lib, pkgs, ... }:
self,
nixpkgs,
flake-utils,
}:
{
homeModules.default =
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.programs.qmd;
Expand All @@ -29,16 +40,17 @@
home.packages = [ cfg.package ];
};
};
} //
flake-utils.lib.eachDefaultSystem (system:
}
// flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
packageJson = builtins.fromJSON (builtins.readFile ./package.json);
version = packageJson.version;

# SQLite with loadable extension support for sqlite-vec
sqliteWithExtensions = pkgs.sqlite.overrideAttrs (old: {
configureFlags = (old.configureFlags or []) ++ [
configureFlags = (old.configureFlags or [ ]) ++ [
"--enable-load-extension"
];
});
Expand Down Expand Up @@ -103,9 +115,10 @@
pkgs.makeWrapper
pkgs.nodejs
pkgs.node-gyp
pkgs.python3 # needed by node-gyp to compile better-sqlite3
] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
pkgs.darwin.cctools # provides libtool needed by node-gyp on macOS
pkgs.python3
]
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
pkgs.darwin.cctools
];

buildInputs = [ pkgs.sqlite ];
Expand All @@ -130,7 +143,7 @@
makeWrapper ${pkgs.bun}/bin/bun $out/bin/qmd \
--add-flags "$out/lib/qmd/src/cli/qmd.ts" \
--set DYLD_LIBRARY_PATH "${pkgs.sqlite.out}/lib" \
--set LD_LIBRARY_PATH "${pkgs.sqlite.out}/lib"
--set LD_LIBRARY_PATH "${pkgs.sqlite.out}/lib:${pkgs.stdenv.cc.libc.out}/lib:${pkgs.stdenv.cc.cc.lib}/lib"
'';

meta = with pkgs.lib; {
Expand Down
27 changes: 23 additions & 4 deletions src/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
type Token as LlamaToken,
} from "node-llama-cpp";
import { homedir } from "os";
import { join } from "path";
import { existsSync, mkdirSync, statSync, unlinkSync, readdirSync, readFileSync, writeFileSync, openSync, readSync, closeSync } from "fs";
import { dirname, join } from "path";
import { accessSync, constants, existsSync, mkdirSync, statSync, unlinkSync, readdirSync, readFileSync, writeFileSync, openSync, readSync, closeSync } from "fs";
import { createRequire } from "module";

// =============================================================================
// Embedding Formatting Functions
Expand Down Expand Up @@ -57,6 +58,23 @@ export function formatDocForEmbedding(text: string, title?: string, modelUri?: s
return `title: ${title || "none"} | text: ${text}`;
}

// =============================================================================
// Build Writability Check
// =============================================================================

const require = createRequire(import.meta.url);

/** Whether node-llama-cpp can write to its llama/ directory (false on NixOS). */
function canWriteLlamaDir(): boolean {
try {
const pkgDir = dirname(require.resolve("node-llama-cpp/package.json"));
accessSync(join(pkgDir, "llama"), constants.W_OK);
return true;
} catch {
return false;
}
}

// =============================================================================
// Types
// =============================================================================
Expand Down Expand Up @@ -618,13 +636,14 @@ export class LlamaCpp implements LLM {
private async ensureLlama(allowBuild = true): Promise<Llama> {
if (!this.llama) {
const gpuMode = resolveLlamaGpuMode();
const buildMode = allowBuild && canWriteLlamaDir() ? "autoAttempt" : "never";

const loadLlama = async (gpu: LlamaGpuMode) =>
await getLlama({
build: allowBuild ? "autoAttempt" : "never",
build: buildMode,
logLevel: LlamaLogLevel.error,
gpu,
skipDownload: !allowBuild,
skipDownload: buildMode === "never",
});

let llama: Llama;
Expand Down