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
50 changes: 48 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,37 @@ jobs:
- name: "macOS arm64"
os: macos-26
artifact: "mac-arm64"
- name: "Ubuntu riscv64"
os: ubuntu-24.04-riscv
artifact: "linux-riscv64"

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
if: matrix.config.artifact != 'linux-riscv64'
with:
node-version: "20"

- name: Install Node.js (riscv64 unofficial build)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is node installed on the ubuntu-24.04-riscv machines. Why do you need to do that?

if: matrix.config.artifact == 'linux-riscv64'
run: |
set -e
need=24.10.0
if command -v node >/dev/null 2>&1; then
cur="$(node -p 'process.versions.node')"
if [ "$(printf '%s\n%s\n' "$need" "$cur" | sort -V | head -1)" = "$need" ]; then
echo "Using preinstalled Node.js v$cur at $(command -v node)"
exit 0
fi
echo "Preinstalled Node.js v$cur is older than $need; installing unofficial build"
fi
NODE_VER=v24.16.0
curl -fsSL -o /tmp/node.tar.xz "https://unofficial-builds.nodejs.org/download/release/${NODE_VER}/node-${NODE_VER}-linux-riscv64.tar.xz"
mkdir -p "$HOME/node"
tar -xJf /tmp/node.tar.xz -C "$HOME/node" --strip-components=1
echo "$HOME/node/bin" >> "$GITHUB_PATH"
"$HOME/node/bin/node" --version

Comment on lines +89 to +108

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe updating to actions/setup-node@v6 would work out of the box?
Maybe some config option on it would solve the issue so we don't have to run a custom node installation step?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup-node@v6 won't help here, unfortunately. It pulls official Node binaries from nodejs.org/dist, and there are no riscv64 builds there. Same gap as setup-python and setup-uv. The only place shipping riscv64 Node is unofficial-builds.nodejs.org, which is what this step grabs. I don't think a config flag changes that, but if you know one I'll happily switch.

The jump to Node 24 is a separate reason: Node 20's npm 10.8.2 hits "set.delete is not a function" during npm ci on riscv64, and a couple of devDeps want node >= 24.10. The built binary still targets the Node 20 N-API.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the mirror key?

- uses: actions/setup-node@v6
  with:
    mirror: "https://unofficial-builds.nodejs.org/download/release/"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I tested it on the riscv64 runner. It half-works. With node-version: "24" it passes, but only because the RISE runner image already has a riscv64 node in its tool cache, so setup-node never downloads anything. When I pin a version that forces a download (24.16.0), it fails:

Not found in manifest. Falling back to download directly from https://unofficial-builds.nodejs.org/download/release
Unable to find Node version '24.16.0' for platform linux and architecture riscv64.

That tarball does exist on unofficial-builds, so setup-node's mirror fallback isn't resolving riscv64; it just leans on whatever's cached on the runner. The curl step always pulls the pinned version, so I'd rather keep it. Happy to switch if you know a config that fixes the resolution.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dug a little bit deeper and it seems that the reason it failed is because this file doesn't include any riscv64 entries.
You might be interested in sending a PR there to add support for the actions/setup-node action on riscv64 machines (and then updating this build.yml to use it in a subsequent PR).

For now I think we can proceed with a custom step, and it might be a good idea to gate it to only execute if nodejs v24 isn't already present; @luhenry said nodejs v24 is preinstalled on ubuntu-24.04-riscv machines, so we better use the existing installation rather than override it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The step now prefers the preinstalled Node when it's 24.10+ and only downloads the unofficial build as a fallback. Thanks for tracking down the manifest gap, that explains the setup-node failure. Adding riscv64 to actions/node-versions is a good idea; I'll look into that as a separate PR so setup-node can work here directly.

- name: Download build artifact
uses: actions/download-artifact@v4
with:
Expand Down Expand Up @@ -131,6 +155,19 @@ jobs:

cmake --version

# llama.cpp's ggml-cpu uses the RVV _Float16 (zvfh) vector intrinsics,
# which only exist in GCC 14+. The runner defaults to GCC 13, so make
# gcc-14/g++-14 the default compilers (same as upstream build-riscv).
- name: Install dependencies on Ubuntu riscv64
if: matrix.config.name == 'Ubuntu riscv64'
run: |
sudo apt-get update
sudo apt-get install -y cmake build-essential ninja-build libtbb-dev gcc-14 g++-14 retry
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
cmake --version
gcc --version

- name: Install Cuda 13.1 on Windows (1)
if: matrix.config.name == 'Windows (1)'
uses: Jimver/cuda-toolkit@v0.2.30
Expand Down Expand Up @@ -207,9 +244,16 @@ jobs:
timeout-minutes: 300
env:
ARTIFACT_NAME: ${{ matrix.config.artifact }}
ELECTRON_SKIP_BINARY_DOWNLOAD: "1"
run: |
npm ci

if [ "$ARTIFACT_NAME" = "linux-riscv64" ]; then
# The unofficial riscv64 Node runtime intermittently throws internal
# TypeErrors during npm ci (e.g. "key.match is not a function")
retry --times=3 npm ci
else
npm ci
fi

npx zx -y <<'EOF'

async function getLatestNodeVersions(maxDate) {
Expand Down Expand Up @@ -275,6 +319,8 @@ jobs:
await buildBinary("x64", ["--gpu", "false"]);
} else if (process.env.ARTIFACT_NAME === "mac-arm64") {
await buildBinary("arm64", ["--gpu", "metal"]);
} else if (process.env.ARTIFACT_NAME === "linux-riscv64") {
await buildBinary("riscv64", ["--gpu", "false"]);
}

// move binaries to bins
Expand Down
4 changes: 4 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"optionalDependencies": {
"@node-llama-cpp/linux-arm64": "0.1.0",
"@node-llama-cpp/linux-armv7l": "0.1.0",
"@node-llama-cpp/linux-riscv64": "0.1.0",
"@node-llama-cpp/linux-x64": "0.1.0",
"@node-llama-cpp/linux-x64-cuda": "0.1.0",
"@node-llama-cpp/linux-x64-cuda-ext": "0.1.0",
Expand Down
1 change: 1 addition & 0 deletions packages/@node-llama-cpp/linux-riscv64/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/dist
21 changes: 21 additions & 0 deletions packages/@node-llama-cpp/linux-riscv64/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Gilad S.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 4 additions & 0 deletions packages/@node-llama-cpp/linux-riscv64/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# [`node-llama-cpp`](https://github.com/withcatai/node-llama-cpp)
This is a prebuilt binary package for [`node-llama-cpp`](https://github.com/withcatai/node-llama-cpp) for Linux riscv64.

Do not install this package directly.
39 changes: 39 additions & 0 deletions packages/@node-llama-cpp/linux-riscv64/package-lock.json

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

54 changes: 54 additions & 0 deletions packages/@node-llama-cpp/linux-riscv64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@node-llama-cpp/linux-riscv64",
"version": "0.1.0",
"description": "Prebuilt binary for node-llama-cpp for Linux riscv64",
"main": "dist/index.js",
"type": "module",
"files": [
"dist/",
"bins/",
"package.json",
"README.md",
"LICENSE"
],
"exports": {
".": {
"import": "./dist/index.js",
"node": "./dist/index.js",
"default": "./dist/index.js"
}
},
"engines": {
"node": ">=20.0.0"
},
"os": [
"linux"
],
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"scripts": {
"prebuild": "rimraf ./dist ./tsconfig.tsbuildinfo",
"build": "tsc --build tsconfig.json --force",
"prewatch": "rimraf ./dist ./tsconfig.tsbuildinfo",
"watch": "tsc --build tsconfig.json --watch --force",
"clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo"
},
"repository": {
"type": "git",
"url": "git+https://github.com/withcatai/node-llama-cpp.git"
},
"author": "Gilad S.",
"license": "MIT",
"preferUnplugged": true,
"bugs": {
"url": "https://github.com/withcatai/node-llama-cpp/issues"
},
"homepage": "https://node-llama-cpp.withcat.ai",
"devDependencies": {
"typescript": "^5.2.2"
}
}
14 changes: 14 additions & 0 deletions packages/@node-llama-cpp/linux-riscv64/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import path from "path";
import {fileURLToPath} from "url";
import fs from "node:fs/promises";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const binsDir = path.join(__dirname, "..", "bins");
const packageVersion: string = (JSON.parse(await fs.readFile(path.join(__dirname, "..", "package.json"), "utf8"))).version;

export function getBinsDir() {
return {
binsDir,
packageVersion
};
}
34 changes: 34 additions & 0 deletions packages/@node-llama-cpp/linux-riscv64/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"compilerOptions": {
"lib": ["es2022"],
"module": "node16",
"target": "es2022",
"esModuleInterop": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitOverride": true,
"removeComments": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"moduleResolution": "node16",
"resolveJsonModule": false,
"strictNullChecks": true,
"isolatedModules": true,
"noEmit": false,
"outDir": "./dist",
"strict": true,
"sourceMap": false,
"composite": false,
"declaration": false,
"stripInternal": true
},
"files": [
"./src/index.ts"
],
"include": [
"./src"
]
}
1 change: 1 addition & 0 deletions scripts/movePrebuiltBinariesToStandaloneModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith

await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("linux-arm64"), "@node-llama-cpp/linux-arm64");
await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("linux-armv7l"), "@node-llama-cpp/linux-armv7l");
await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("linux-riscv64"), "@node-llama-cpp/linux-riscv64");

await moveBinariesFallbackDirToStandaloneExtModule((folderName) => folderName.startsWith("win-x64-cuda"), "@node-llama-cpp/win-x64-cuda-ext");
await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("win-x64-cuda"), "@node-llama-cpp/win-x64-cuda");
Expand Down
7 changes: 6 additions & 1 deletion src/bindings/utils/compileLLamaCpp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
if (!cmakeCustomOptions.has("GGML_NATIVE") || isCmakeValueOff(cmakeCustomOptions.get("GGML_NATIVE"))) {
cmakeCustomOptions.set("GGML_NATIVE", "OFF");

if (buildOptions.arch === "x64" && !cmakeCustomOptions.has("GGML_CPU_ALL_VARIANTS")) {
if ((buildOptions.arch === "x64" || buildOptions.arch === "riscv64") &&
!cmakeCustomOptions.has("GGML_CPU_ALL_VARIANTS")
) {
cmakeCustomOptions.set("GGML_CPU_ALL_VARIANTS", "ON");
cmakeCustomOptions.set("GGML_BACKEND_DL", "ON");
} else if (!cmakeCustomOptions.has("GGML_BACKEND_DL"))
Expand Down Expand Up @@ -618,6 +620,9 @@ function getPrebuiltBinariesPackageDirectoryForBuildOptions(buildOptions: {
else if (buildOptions.arch === "arm")
// @ts-ignore
return getBinariesPathFromModules(() => import("@node-llama-cpp/linux-armv7l"));
else if (buildOptions.arch === "riscv64")
// @ts-ignore
return getBinariesPathFromModules(() => import("@node-llama-cpp/linux-riscv64"));
} else if (buildOptions.platform === "win") {
if (buildOptions.arch === "x64") {
if (buildOptions.gpu === "cuda")
Expand Down
9 changes: 7 additions & 2 deletions src/bindings/utils/detectGlibc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export async function detectGlibc({
"/usr/lib64",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib/aarch64-linux-gnu",
"/usr/lib/armv7l-linux-gnu"
"/usr/lib/armv7l-linux-gnu",
"/usr/lib/riscv64-linux-gnu"
];

const glibcFileNames = [
Expand All @@ -45,7 +46,11 @@ export async function detectGlibc({
"ld-linux-armv7l.so",
"ld-linux-armv7l.so.1",
"ld-linux-armv7l.so.2",
"ld-linux-armv7l.so.3" // for when the next version comes out
"ld-linux-armv7l.so.3", // for when the next version comes out
"ld-linux-riscv64-lp64d.so",
"ld-linux-riscv64-lp64d.so.1",
"ld-linux-riscv64-lp64d.so.2",
"ld-linux-riscv64-lp64d.so.3" // for when the next version comes out
];

const foundGlibC = await asyncEvery([
Expand Down
Loading