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
161 changes: 161 additions & 0 deletions .github/workflows/runtime-wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Runtime wheels

on:
workflow_dispatch:
push:
paths:
- .github/workflows/runtime-wheels.yml
- scripts/runtime-wheels/**

permissions:
contents: read

jobs:
macos-intel:
runs-on: macos-15-intel
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
env:
CMAKE_ARGS: -C "${{ github.workspace }}/scripts/runtime-wheels/macos-intel.cmake"
MACOSX_DEPLOYMENT_TARGET: "11.0"
ARCHFLAGS: -arch x86_64
CMAKE_BUILD_PARALLEL_LEVEL: "4"
CC: /usr/bin/clang
CXX: /usr/bin/clang++
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Install build tools
run: python -m pip install -r scripts/runtime-wheels/macos-intel-requirements.txt
- name: Build pinned upstream source
run: >-
python -m pip wheel --no-cache-dir --no-deps --no-build-isolation
-r scripts/runtime-wheels/source.txt --wheel-dir wheelhouse
- name: Verify installation and offline inference
shell: python
run: |
import pathlib, subprocess, sys, tempfile, venv
wheel, = pathlib.Path("wheelhouse").glob("*.whl")
tag = "cp%d%d" % sys.version_info[:2]
assert wheel.name == f"llama_cpp_python-0.3.19-{tag}-{tag}-macosx_11_0_x86_64.whl"
with tempfile.TemporaryDirectory() as root:
venv.create(root, with_pip=True)
python = str(pathlib.Path(root) / "bin/python")
fixtures = str(pathlib.Path(root) / "fixtures")
for args in (
["-m", "pip", "install", str(wheel.resolve()), "."],
["-m", "pip", "check"],
["-I", "-c", "import llama_cpp; print(llama_cpp.llama_print_system_info().decode())"],
["-I", "scripts/runtime-wheels/prepare_native_fixtures.py", fixtures],
["-I", "scripts/runtime-wheels/check_native_inference.py", fixtures],
):
subprocess.run([python, *args], check=True)
- uses: actions/upload-artifact@v4
with:
name: llama-cpp-macos-intel-py${{ matrix.python-version }}
path: wheelhouse/*.whl
if-no-files-found: error

windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.14"]
env:
CMAKE_GENERATOR: Visual Studio 17 2022
CMAKE_GENERATOR_PLATFORM: x64
CMAKE_ARGS: -C "${{ github.workspace }}/scripts/runtime-wheels/windows.cmake"
CMAKE_BUILD_PARALLEL_LEVEL: "4"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Install build tools
run: python -m pip install -r scripts/runtime-wheels/windows-requirements.txt
- name: Build pinned upstream source
run: >-
python -m pip wheel --no-cache-dir --no-deps --no-build-isolation
-r scripts/runtime-wheels/source.txt --wheel-dir wheelhouse
- name: Audit DLLs and verify offline inference
shell: python
run: |
import pathlib, subprocess, sys, tempfile, venv
wheel, = pathlib.Path("wheelhouse").glob("*.whl")
tag = "cp%d%d" % sys.version_info[:2]
assert wheel.name == f"llama_cpp_python-0.3.19-{tag}-{tag}-win_amd64.whl"
subprocess.run([
sys.executable, "-I", "scripts/runtime-wheels/audit_windows_dependencies.py",
str(wheel.resolve()),
], check=True)
with tempfile.TemporaryDirectory() as root:
venv.create(root, with_pip=True)
python = str(pathlib.Path(root) / "Scripts/python.exe")
fixtures = str(pathlib.Path(root) / "fixtures")
for args in (
["-m", "pip", "install", str(wheel.resolve()), "."],
["-m", "pip", "check"],
["-I", "-c", "import llama_cpp; print(llama_cpp.llama_print_system_info().decode())"],
["-I", "scripts/runtime-wheels/prepare_native_fixtures.py", fixtures],
["-I", "scripts/runtime-wheels/check_native_inference.py", fixtures],
):
subprocess.run([python, *args], check=True)
- uses: actions/upload-artifact@v4
with:
name: llama-cpp-windows-py${{ matrix.python-version }}
path: wheelhouse/*.whl
if-no-files-found: error

linux:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.8"
architecture: x64
- name: Build and repair the Python 3.8 wheel
run: |
docker run --rm --volume "$PWD:/io" --workdir /io \
--env CMAKE_GENERATOR="Unix Makefiles" \
--env CMAKE_ARGS="-C /io/scripts/runtime-wheels/linux.cmake" \
--env CMAKE_BUILD_PARALLEL_LEVEL=4 \
quay.io/pypa/manylinux2014_x86_64@sha256:f09bf382e4bcbd5b3cab130f8d0f12dbe474833f95f54182e80e8c0aaa269ff0 \
bash -euxo pipefail -c '
export PATH="/opt/python/cp38-cp38/bin:$PATH"
python -m pip install -r scripts/runtime-wheels/linux-requirements.txt
python -m pip wheel --no-cache-dir --no-deps --no-build-isolation \
-r scripts/runtime-wheels/source.txt --wheel-dir /tmp/raw-wheels
auditwheel repair --plat manylinux2014_x86_64 --only-plat \
--wheel-dir wheelhouse /tmp/raw-wheels/*.whl
'
- name: Verify installation and offline inference
shell: python
run: |
import pathlib, subprocess, tempfile, venv
wheel, = pathlib.Path("wheelhouse").glob("*.whl")
assert wheel.name == "llama_cpp_python-0.3.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
with tempfile.TemporaryDirectory() as root:
venv.create(root, with_pip=True)
python = str(pathlib.Path(root) / "bin/python")
fixtures = str(pathlib.Path(root) / "fixtures")
for args in (
["-m", "pip", "install", str(wheel.resolve()), "."],
["-m", "pip", "check"],
["-I", "-c", "import llama_cpp; print(llama_cpp.llama_print_system_info().decode())"],
["-I", "scripts/runtime-wheels/prepare_native_fixtures.py", fixtures],
["-I", "scripts/runtime-wheels/check_native_inference.py", fixtures],
):
subprocess.run([python, *args], check=True)
- uses: actions/upload-artifact@v4
with:
name: llama-cpp-linux-py3.8
path: wheelhouse/*.whl
if-no-files-found: error
47 changes: 44 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ jobs:
PY

test:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -104,7 +104,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down Expand Up @@ -134,3 +134,44 @@ jobs:
message = f"{case.get('name')}: {detail[-3500:]}"
message = message.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
print(f"::error::{message}")

native-install:
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
platform:
- {runner: ubuntu-24.04, arch: x64}
- {runner: windows-2022, arch: x64}
- {runner: macos-14, arch: arm64}
- {runner: macos-15-intel, arch: x64}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.platform.arch }}
- name: Install SDK wheel and load its native dependency
env:
EXPECTED_ARCH: ${{ matrix.platform.arch }}
shell: python
run: |
import os, platform, subprocess, sys, venv
from pathlib import Path
architectures = {"x64": {"x86_64", "amd64"}, "arm64": {"arm64", "aarch64"}}
assert platform.machine().lower() in architectures[os.environ["EXPECTED_ARCH"]]
root = Path(os.environ["RUNNER_TEMP"]) / "paw-native"
venv.create(root, with_pip=True)
python = root / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python")
def run(*args):
subprocess.run([str(python), *args], check=True)
run("-m", "pip", "install", "--upgrade", "pip")
run("-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(root / "dist"), ".")
wheel, = (root / "dist").glob("*.whl")
run("-m", "pip", "install", str(wheel), "--extra-index-url",
"https://pypi.programasweights.com/simple/", "--only-binary=llama-cpp-python")
run("-m", "pip", "check")
run("-I", "-c", "import programasweights, llama_cpp; "
"print(programasweights.__version__, llama_cpp.__version__); "
"print(llama_cpp.llama_print_system_info().decode())")
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.4.10 (2026-09-21)

- Support Python 3.8 through 3.14.
- Add prebuilt Intel Mac runtimes with Metal acceleration to the PAW
package index.
- Add Python 3.8 runtimes for Apple Silicon, Linux, and Windows, and
Python 3.14 runtimes for Windows.
- Bundle required Visual C++ and OpenMP libraries in the new Windows runtimes.

## 0.4.9 (2026-09-21)

- Lead quickstarts and examples with compiling your own functions, then
Expand Down
22 changes: 20 additions & 2 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Install the ProgramAsWeights SDK from PyPI:
pip install programasweights --extra-index-url https://pypi.programasweights.com/simple/
```

The `--extra-index-url` flag provides pre-built binaries for `llama-cpp-python`, making installation fast (~10 seconds). Without it, the C++ backend compiles from source (~5 minutes).
The extra index provides prebuilt binaries for fast installation. If no matching binary is available, pip builds the runtime from source.

## Requirements

- **Python:** 3.9 through 3.13.
- **Python:** 3.8 through 3.14.
- **Local runtime:** `llama-cpp-python` is installed with the package.

GPU offload defaults to all available layers (`n_gpu_layers=-1`). Set
Expand All @@ -19,6 +19,24 @@ GPU offload defaults to all available layers (`n_gpu_layers=-1`). Set
The same installation supports [remote inference](../api-reference/python-sdk.md#remote-inference)
with `paw.function(..., remote=True)`, without downloading local model assets.

## Apple Silicon Macs

Use ARM64 Python for native performance and Metal acceleration.
Check your Python architecture:

```bash
python -c "import platform; print(platform.machine())"
```

It should print `arm64`. If it prints `x86_64`, Python is running
under Rosetta. With Conda, create a native environment:

```bash
conda create -n paw-arm64 --platform osx-arm64 python=3.12 pip
conda activate paw-arm64
python -m pip install programasweights --extra-index-url https://pypi.programasweights.com/simple/
```

## Anaconda on Linux: OpenMP / libgomp errors

If the build fails with `libgomp`-related errors, disable OpenMP:
Expand Down
2 changes: 1 addition & 1 deletion programasweights/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from importlib.metadata import version as _meta_version
__version__ = _meta_version("programasweights")
except Exception:
__version__ = "0.4.9"
__version__ = "0.4.10"

from ._output import ProgressCallback, ProgressEvent, report_progress
from .cache import CachedProgram
Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ build-backend = "hatchling.build"

[project]
name = "programasweights"
version = "0.4.9"
version = "0.4.10"
description = "Compile natural language specifications into neural programs that run locally via llama.cpp."
readme = "PYPI_README.md"
requires-python = ">=3.9"
requires-python = ">=3.8"
authors = [
{ name = "ProgramAsWeights", email = "support@programasweights.com" }
]
Expand All @@ -19,11 +19,13 @@ classifiers = [
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development :: Libraries :: Python Modules",
]
Expand Down
57 changes: 57 additions & 0 deletions scripts/runtime-wheels/audit_windows_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Check that a Windows runtime wheel bundles its non-system DLL dependencies."""

import argparse
import json
from pathlib import PurePosixPath
import zipfile

import pefile

# Windows 10+ components. MSVC and OpenMP redistributables are not OS libraries.
SYSTEM_DLLS = {"advapi32.dll", "kernel32.dll", "ntdll.dll", "ucrtbase.dll"}
IMPORT_TABLES = ("IMPORT", "DELAY_IMPORT")
REQUIRED_DLLS = {"llama.dll", "ggml.dll", "ggml-base.dll", "ggml-cpu.dll", "mtmd.dll"}


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("wheel")
args = parser.parse_args()
errors, report = [], {}
with zipfile.ZipFile(args.wheel) as wheel:
dlls = [name for name in wheel.namelist() if name.lower().endswith(".dll")]
bundled = {
PurePosixPath(name).name.lower()
for name in dlls
if str(PurePosixPath(name).parent) == "llama_cpp/lib"
}
for missing in sorted(REQUIRED_DLLS - bundled):
errors.append("Missing llama_cpp/lib/" + missing)
for name in sorted(dlls):
with pefile.PE(data=wheel.read(name), fast_load=True) as binary:
if binary.FILE_HEADER.Machine != pefile.MACHINE_TYPE["IMAGE_FILE_MACHINE_AMD64"]:
errors.append(name + ": not AMD64")
binary.parse_data_directories(directories=[
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_" + table]
for table in IMPORT_TABLES
])
imports = {
table.lower(): sorted({entry.dll.decode("ascii").lower()
for entry in getattr(binary, "DIRECTORY_ENTRY_" + table, [])})
for table in IMPORT_TABLES
}
for warning in binary.get_warnings():
errors.append(name + ": " + warning)
for dependencies in imports.values():
for dependency in dependencies:
if (dependency not in bundled and dependency not in SYSTEM_DLLS
and not dependency.startswith(("api-ms-win-", "ext-ms-win-"))):
errors.append(name + ": missing llama_cpp/lib/" + dependency)
report[name] = imports
print(json.dumps({"dlls": report, "errors": errors}, indent=2))
if errors:
raise SystemExit("Windows DLL dependency audit failed")


if __name__ == "__main__":
main()
Loading
Loading