From 5a59ad5bec9eea48e77617fe6b71bcca341cd220 Mon Sep 17 00:00:00 2001 From: da03 Date: Mon, 21 Sep 2026 21:02:36 -0400 Subject: [PATCH 1/2] =?UTF-8?q?Support=20Python=203.8=E2=80=933.14=20and?= =?UTF-8?q?=20build=20missing=20runtime=20wheels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/runtime-wheels.yml | 161 ++++++++++++++++++ .github/workflows/test.yml | 47 ++++- docs/getting-started/installation.md | 22 ++- pyproject.toml | 4 +- .../audit_windows_dependencies.py | 57 +++++++ .../runtime-wheels/check_native_inference.py | 65 +++++++ scripts/runtime-wheels/linux-requirements.txt | 1 + scripts/runtime-wheels/linux.cmake | 12 ++ .../macos-intel-requirements.txt | 3 + scripts/runtime-wheels/macos-intel.cmake | 15 ++ scripts/runtime-wheels/native-fixtures.json | 46 +++++ .../runtime-wheels/prepare_native_fixtures.py | 59 +++++++ scripts/runtime-wheels/source.txt | 1 + .../runtime-wheels/windows-requirements.txt | 3 + scripts/runtime-wheels/windows-runtime.cmake | 3 + scripts/runtime-wheels/windows.cmake | 11 ++ 16 files changed, 504 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/runtime-wheels.yml create mode 100644 scripts/runtime-wheels/audit_windows_dependencies.py create mode 100644 scripts/runtime-wheels/check_native_inference.py create mode 100644 scripts/runtime-wheels/linux-requirements.txt create mode 100644 scripts/runtime-wheels/linux.cmake create mode 100644 scripts/runtime-wheels/macos-intel-requirements.txt create mode 100644 scripts/runtime-wheels/macos-intel.cmake create mode 100644 scripts/runtime-wheels/native-fixtures.json create mode 100644 scripts/runtime-wheels/prepare_native_fixtures.py create mode 100644 scripts/runtime-wheels/source.txt create mode 100644 scripts/runtime-wheels/windows-requirements.txt create mode 100644 scripts/runtime-wheels/windows-runtime.cmake create mode 100644 scripts/runtime-wheels/windows.cmake diff --git a/.github/workflows/runtime-wheels.yml b/.github/workflows/runtime-wheels.yml new file mode 100644 index 0000000..2c7d50b --- /dev/null +++ b/.github/workflows/runtime-wheels.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7138e6..86bd49d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 @@ -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 @@ -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())") diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 28afdc6..a05449d 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -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 @@ -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: diff --git a/pyproject.toml b/pyproject.toml index 2ecdec9..7abbd82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "programasweights" version = "0.4.9" 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" } ] @@ -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", ] diff --git a/scripts/runtime-wheels/audit_windows_dependencies.py b/scripts/runtime-wheels/audit_windows_dependencies.py new file mode 100644 index 0000000..000a8c8 --- /dev/null +++ b/scripts/runtime-wheels/audit_windows_dependencies.py @@ -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() diff --git a/scripts/runtime-wheels/check_native_inference.py b/scripts/runtime-wheels/check_native_inference.py new file mode 100644 index 0000000..ec9629a --- /dev/null +++ b/scripts/runtime-wheels/check_native_inference.py @@ -0,0 +1,65 @@ +"""Check offline CPU inference and native prefix-cache save/reload.""" + +import argparse +import json +import os +import sys +from pathlib import Path + + +def prohibit_network(event, args): + if event in ("socket.connect", "socket.getaddrinfo", "socket.sendto"): + raise AssertionError("Network use during offline inference: " + event) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("cache_dir", type=Path) + args = parser.parse_args() + os.environ["PAW_CACHE_DIR"] = str(args.cache_dir.resolve()) + os.environ["PAW_OFFLINE"] = "1" + os.environ["GGML_METAL_DEVICES"] = "0" + sys.addaudithook(prohibit_network) + import llama_cpp + import programasweights as paw + + events = [] + for name in ("llama_state_seq_save_file", "llama_state_seq_load_file"): + native = getattr(llama_cpp, name) + + def traced(*values, _native=native, _name=name): + result = _native(*values) + events.append((_name, int(result))) + return result + + setattr(llama_cpp, name, traced) + + cases = [ + ("Urgent: production server is down. Please investigate now.", "immediate"), + ("FYI: here is our monthly newsletter.", "wait"), + ] + manifest = Path(__file__).with_name("native-fixtures.json") + for fixture in json.loads(manifest.read_text(encoding="utf-8")): + program_id = fixture["program_id"] + prefix = args.cache_dir / "programs" / program_id / "prefix_kv_cache.bin" + prefix.unlink(missing_ok=True) + for state in ("cold", "reloaded"): + events.clear() + with paw.function(program_id, offline=True, n_gpu_layers=0) as fn: + assert fn._adapter is not None and fn._n_prefix > 0 + for text, expected in cases: + actual = fn(text) + assert actual == expected, (fixture["label"], state, text, actual) + assert prefix.stat().st_size > 0 + operation = "save" if state == "cold" else "load" + assert any( + name == "llama_state_seq_" + operation + "_file" and size > 0 + for name, size in events + ), events + if state == "reloaded": + assert not any(name == "llama_state_seq_save_file" for name, _ in events), events + print("Passed", fixture["label"], state, flush=True) + + +if __name__ == "__main__": + main() diff --git a/scripts/runtime-wheels/linux-requirements.txt b/scripts/runtime-wheels/linux-requirements.txt new file mode 100644 index 0000000..c952da1 --- /dev/null +++ b/scripts/runtime-wheels/linux-requirements.txt @@ -0,0 +1 @@ +scikit-build-core[pyproject]==1.0.3 diff --git a/scripts/runtime-wheels/linux.cmake b/scripts/runtime-wheels/linux.cmake new file mode 100644 index 0000000..d8f7cbd --- /dev/null +++ b/scripts/runtime-wheels/linux.cmake @@ -0,0 +1,12 @@ +foreach(feature IN ITEMS + GGML_SSE42 GGML_AVX GGML_AVX2 GGML_FMA GGML_F16C GGML_BMI2 + GGML_LLAMAFILE GGML_OPENMP GGML_CPU_REPACK) + set(${feature} ON CACHE BOOL "") +endforeach() + +foreach(feature IN ITEMS + GGML_NATIVE GGML_AVX512 GGML_AVX512_VBMI GGML_AVX512_VNNI + GGML_AVX512_BF16 GGML_AVX_VNNI GGML_AMX_TILE GGML_AMX_INT8 + GGML_AMX_BF16 GGML_BLAS GGML_CUDA) + set(${feature} OFF CACHE BOOL "") +endforeach() diff --git a/scripts/runtime-wheels/macos-intel-requirements.txt b/scripts/runtime-wheels/macos-intel-requirements.txt new file mode 100644 index 0000000..9d2c024 --- /dev/null +++ b/scripts/runtime-wheels/macos-intel-requirements.txt @@ -0,0 +1,3 @@ +scikit-build-core[pyproject]==1.0.3 +cmake==4.4.3 +ninja==1.13.2 diff --git a/scripts/runtime-wheels/macos-intel.cmake b/scripts/runtime-wheels/macos-intel.cmake new file mode 100644 index 0000000..e03015c --- /dev/null +++ b/scripts/runtime-wheels/macos-intel.cmake @@ -0,0 +1,15 @@ +set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "") +set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "") + +foreach(feature IN ITEMS GGML_METAL GGML_METAL_EMBED_LIBRARY GGML_ACCELERATE) + set(${feature} ON CACHE BOOL "") +endforeach() + +foreach(feature IN ITEMS + GGML_NATIVE GGML_BLAS GGML_OPENMP GGML_LLAMAFILE + GGML_SSE42 GGML_AVX GGML_AVX2 GGML_AVX_VNNI + GGML_AVX512 GGML_AVX512_VBMI GGML_AVX512_VNNI GGML_AVX512_BF16 + GGML_FMA GGML_F16C GGML_BMI2 + GGML_AMX_TILE GGML_AMX_INT8 GGML_AMX_BF16) + set(${feature} OFF CACHE BOOL "") +endforeach() diff --git a/scripts/runtime-wheels/native-fixtures.json b/scripts/runtime-wheels/native-fixtures.json new file mode 100644 index 0000000..6cf03d2 --- /dev/null +++ b/scripts/runtime-wheels/native-fixtures.json @@ -0,0 +1,46 @@ +[ + { + "label": "standard", + "program_id": "d67162f3ab9562fe2826", + "interpreter": "Qwen/Qwen3-0.6B", + "bundle": { + "url": "https://huggingface.co/programasweights/paw-programs/resolve/b3291dc7e71451fe961b1d88bb36210cefb35bc5/d67162f3ab9562fe2826.paw", + "sha256": "34fda627d1460161a37c436b0b6877be3336ab2db43b26dd5897e8444fa7c242", + "size_bytes": 22737730 + }, + "base": { + "url": "https://huggingface.co/programasweights/Qwen3-0.6B-GGUF-Q6_K/resolve/acb0cea5b5dc53c3ddb69092f778ff608a25e58f/qwen3-0.6b-q6_k.gguf", + "sha256": "9a16ed5cacba959e63b62e2b6840c3eca2b51c3c3e51d31367ef8e4aafeae33c", + "size_bytes": 622733120, + "file": "qwen3-0.6b-q6_k.gguf" + }, + "members": { + "prompt_template.txt": "37cdd0a5fe862d50e90e5b709774a0341459db839d63fa62b0a7c1f0a676d93e", + "pseudo_program.txt": "251dc8f8e5167554277bdbbff484c82df16fd4e8f8c68da1052e814804973cc7", + "adapter.gguf": "7aeaed6b69350f0c08302b09e50aa79519563d6a6f218abf618c115a2a65d854", + "meta.json": "75f3a700aef74ca15e4d1d59f731b5d7fb77dafae3ff3b42ebd0fa37fa59b136" + } + }, + { + "label": "compact", + "program_id": "3b4e38680509be51f0ff", + "interpreter": "gpt2", + "bundle": { + "url": "https://huggingface.co/programasweights/paw-programs/resolve/b3291dc7e71451fe961b1d88bb36210cefb35bc5/3b4e38680509be51f0ff.paw", + "sha256": "ca6f7c1b1bf60d5944f94319ef59e38f1acd3d528f51094b710b4f241d1c8663", + "size_bytes": 5317772 + }, + "base": { + "url": "https://huggingface.co/programasweights/GPT2-GGUF-Q8_0/resolve/2553387c94536808eae60ff34f54ee18cba79ec0/gpt2-q8_0.gguf", + "sha256": "0aa260efb2cce9def922e0546b88ad731cf1a68554db73fa2d4a0949cfa958c5", + "size_bytes": 139804832, + "file": "gpt2-q8_0.gguf" + }, + "members": { + "prompt_template.txt": "af4c05af81871736fd9ddd133ccbc7323e315ca4684e2257039825c7d3e5deac", + "pseudo_program.txt": "251dc8f8e5167554277bdbbff484c82df16fd4e8f8c68da1052e814804973cc7", + "adapter.gguf": "4b8cd3f0eba0409b9089f532cb601393a7e15026f2ac94769326d7388f7fe76a", + "meta.json": "206bc5b1adb756add94cc446521deb687036b2ae6a06ee987c62515bf778a7c6" + } + } +] diff --git a/scripts/runtime-wheels/prepare_native_fixtures.py b/scripts/runtime-wheels/prepare_native_fixtures.py new file mode 100644 index 0000000..873c8ca --- /dev/null +++ b/scripts/runtime-wheels/prepare_native_fixtures.py @@ -0,0 +1,59 @@ +"""Prepare hash-pinned public programs and models without calling PAW services.""" + +import argparse +import hashlib +import json +import shutil +from pathlib import Path +from urllib.request import urlopen +from zipfile import ZipFile + + +def sha256(path): + digest = hashlib.sha256() + with path.open("rb") as handle: + for block in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def download(asset, path): + path.parent.mkdir(parents=True, exist_ok=True) + if not path.exists(): + temporary = path.with_name(path.name + ".partial") + with urlopen(asset["url"], timeout=120) as response, temporary.open("wb") as output: + shutil.copyfileobj(response, output, 1024 * 1024) + assert temporary.stat().st_size == asset["size_bytes"], temporary + assert sha256(temporary) == asset["sha256"], temporary + temporary.replace(path) + assert path.stat().st_size == asset["size_bytes"], path + assert sha256(path) == asset["sha256"], path + return path + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("cache_dir", type=Path) + args = parser.parse_args() + manifest = Path(__file__).with_name("native-fixtures.json") + for fixture in json.loads(manifest.read_text(encoding="utf-8")): + program_id = fixture["program_id"] + bundle = download(fixture["bundle"], args.cache_dir / "bundles" / (program_id + ".paw")) + download(fixture["base"], args.cache_dir / "base_models" / fixture["base"]["file"]) + program_dir = args.cache_dir / "programs" / program_id + program_dir.mkdir(parents=True, exist_ok=True) + with ZipFile(bundle) as archive: + assert set(archive.namelist()) == set(fixture["members"]) + for name, expected in fixture["members"].items(): + assert Path(name).name == name + data = archive.read(name) + assert hashlib.sha256(data).hexdigest() == expected, name + (program_dir / name).write_bytes(data) + metadata = json.loads((program_dir / "meta.json").read_text(encoding="utf-8")) + assert metadata["program_id"] == program_id + assert metadata["interpreter"] == fixture["interpreter"] + print("Verified", fixture["label"], program_id, flush=True) + + +if __name__ == "__main__": + main() diff --git a/scripts/runtime-wheels/source.txt b/scripts/runtime-wheels/source.txt new file mode 100644 index 0000000..1f53c0c --- /dev/null +++ b/scripts/runtime-wheels/source.txt @@ -0,0 +1 @@ +https://files.pythonhosted.org/packages/d7/0f/b26ceec20294f5d592ac137ccc2f10e91fa1f35bb8555906b5718a625ac1/llama_cpp_python-0.3.19.tar.gz#sha256=6816e4a0a74ae9a356e7a0546e13147543f01f695c86a28d940e4b86519acf13 diff --git a/scripts/runtime-wheels/windows-requirements.txt b/scripts/runtime-wheels/windows-requirements.txt new file mode 100644 index 0000000..d264fba --- /dev/null +++ b/scripts/runtime-wheels/windows-requirements.txt @@ -0,0 +1,3 @@ +scikit-build-core[pyproject]==1.0.3 +cmake==4.4.3 +pefile==2024.8.26 diff --git a/scripts/runtime-wheels/windows-runtime.cmake b/scripts/runtime-wheels/windows-runtime.cmake new file mode 100644 index 0000000..ff3cacc --- /dev/null +++ b/scripts/runtime-wheels/windows-runtime.cmake @@ -0,0 +1,3 @@ +set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "${SKBUILD_PLATLIB_DIR}/llama_cpp/lib") +include(InstallRequiredSystemLibraries) diff --git a/scripts/runtime-wheels/windows.cmake b/scripts/runtime-wheels/windows.cmake new file mode 100644 index 0000000..6d0ce69 --- /dev/null +++ b/scripts/runtime-wheels/windows.cmake @@ -0,0 +1,11 @@ +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "") +set(GGML_NATIVE OFF CACHE BOOL "") +set(GGML_AVX2 ON CACHE BOOL "") +set(GGML_AVX512 OFF CACHE BOOL "") +set(GGML_AVX_VNNI OFF CACHE BOOL "") +set(GGML_CUDA OFF CACHE BOOL "") +set(GGML_BLAS OFF CACHE BOOL "") +set(GGML_OPENMP ON CACHE BOOL "") + +set(CMAKE_PROJECT_llama_cpp_INCLUDE + "${CMAKE_CURRENT_LIST_DIR}/windows-runtime.cmake" CACHE FILEPATH "") From 556448c8c0daac81e58c3699b3ff2b148e9f8b46 Mon Sep 17 00:00:00 2001 From: da03 Date: Mon, 21 Sep 2026 21:46:04 -0400 Subject: [PATCH 2/2] Prepare SDK 0.4.10 compatibility release --- CHANGELOG.md | 9 +++++++++ programasweights/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82feca5..418b748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/programasweights/__init__.py b/programasweights/__init__.py index 6ed5753..b9b05ae 100644 --- a/programasweights/__init__.py +++ b/programasweights/__init__.py @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 7abbd82..d4bcb33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ 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.8"