Skip to content
Draft
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
545 changes: 473 additions & 72 deletions .github/workflows/native_release.yml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ Optional Linux container build:
- Manual native build + release publish.
- `.github/workflows/auto_native_release.yml`
- Scheduled/manual dispatcher when upstream `llama.cpp` tag advances.
- Windows x64 CUDA release backends come from exact-tag upstream CUDA 12/13
assets, are verified against the same-run `ggml-base.dll`, and publish as
separate sidecars. Do not restore a source-built Windows CUDA matrix lane
without evidence that the sidecar contract cannot satisfy the release.

## Change Boundaries

Expand Down
11 changes: 11 additions & 0 deletions scripts/generate_assets_manifest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ infer_meta() {

# New naming convention: <libname>-<platform>-<arch>.<ext>
case "$stem" in
llamadart-native-windows-x64-cuda12-*)
platform="windows"; arch="x64"; backend="cuda"; module="backend-cuda12"; libid="ggml-cuda-12" ;;
llamadart-native-windows-x64-cuda13-*)
platform="windows"; arch="x64"; backend="cuda"; module="backend-cuda13"; libid="ggml-cuda-13" ;;
llamadart-native-apple-xcframework-*)
platform="apple"; arch="universal"; backend="core"; module="spm-xcframework"; libid="llamadart-native-apple-xcframework" ;;
*-windows-x64)
Expand Down Expand Up @@ -116,6 +120,13 @@ infer_meta() {
local id_no_lib
id_no_lib="${libid#lib}"

case "$module" in
backend-cuda12|backend-cuda13)
echo "$platform|$arch|$backend|$module"
return
;;
esac

case "$id_no_lib" in
llamadart-native-apple-xcframework)
backend="core"
Expand Down
79 changes: 79 additions & 0 deletions scripts/verify_release_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,76 @@ def verify_workflow_contract(errors: list[str]) -> None:
errors,
)

experiment_marker = "cuda-prebuilt-experiment"
require(
workflow.count(
"if: ${{ github.event.inputs.native_release_tag != "
f"'{experiment_marker}' }}"
)
== 5,
"the CUDA prebuilt experiment must skip every normal platform build job",
errors,
)
require(
f'if [ "$RELEASE_TAG" = "{experiment_marker}" ]' in workflow
and "is strictly non-publishing" in workflow,
"the CUDA prebuilt experiment marker must reject publishing dispatches",
errors,
)
require(
"windows-cuda-prebuilt-experiment:" in workflow
and "github.event.inputs.publish_release == 'false'" in workflow
and "Install exact GPU-less fatbin inspector" in workflow
and "CUDA_CUOBJDUMP_ARCHIVE_SHA256" in workflow
and "--cuobjdump $env:CUDA_CUOBJDUMP" in workflow
and "python tools/package_upstream_cuda.py" in workflow
and "python tools/verify_cuda_pack.py" in workflow
and "tools/smoke_windows_cuda_pack.py" in workflow
and "python @smokeArgs" in workflow
and "compression-level: 0" in workflow,
"the non-publishing Windows experiment must inspect fatbins, independently verify, loader-smoke, and upload precompressed CUDA packs",
errors,
)

windows_build = workflow.split(" build-windows:", 1)[1].split(
" package-windows-cuda:", 1
)[0]
cuda_packaging = workflow.split(" package-windows-cuda:", 1)[1].split(
" windows-cuda-prebuilt-experiment:", 1
)[0]
require(
"backend: cuda" not in windows_build
and "Install CUDA Toolkit" not in windows_build,
"Windows release builds must not compile the replaced CUDA backend lane",
errors,
)
require(
"needs: [resolve-tag, build-windows]" in cuda_packaging
and "name: native_windows_x64_vulkan" in cuda_packaging
and "--native-release-tag $env:NATIVE_RELEASE_TAG" in cuda_packaging
and "--core-dll $env:CUDA_PREBUILT_CORE_DLL" in cuda_packaging
and "name: windows_cuda_packs" in cuda_packaging,
"production CUDA sidecars must be verified against and follow the same-run Windows core",
errors,
)
require(
"package-windows-cuda]" in workflow
and 'cuda_pack_dir="artifacts/windows_cuda_packs"' in workflow
and 'test "$cuda_pack_count" = "2"' in workflow,
"release packaging must require and publish both verified Windows CUDA sidecars",
errors,
)


def verify_manifest_contract(errors: list[str]) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
assets = root / "assets"
assets.mkdir()
(assets / "libllamadart-linux-x64.so").write_bytes(b"native-test")
(assets / "llamadart-native-windows-x64-cuda13-native-test.tar.gz").write_bytes(
b"cuda-test"
)
output_json = root / "assets.json"
output_checksums = root / "SHA256SUMS"
env = os.environ.copy()
Expand Down Expand Up @@ -121,6 +184,22 @@ def verify_manifest_contract(errors: list[str]) -> None:
"manifest generation must continue to emit asset checksums",
errors,
)
cuda_artifacts = [
artifact
for artifact in manifest.get("artifacts", [])
if artifact.get("file")
== "llamadart-native-windows-x64-cuda13-native-test.tar.gz"
]
require(
len(cuda_artifacts) == 1
and cuda_artifacts[0].get("module") == "backend-cuda13"
and cuda_artifacts[0].get("platform") == "windows"
and cuda_artifacts[0].get("arch") == "x64"
and cuda_artifacts[0].get("backend") == "cuda"
and cuda_artifacts[0].get("size") == len(b"cuda-test"),
"assets.json must classify Windows CUDA sidecars as versioned CUDA backends",
errors,
)


def main() -> int:
Expand Down
188 changes: 188 additions & 0 deletions tools/cuda_pack_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Shared, GPU-less compatibility contracts for optional Windows CUDA packs."""

from __future__ import annotations

from dataclasses import dataclass
import hashlib
from pathlib import Path
import re
import subprocess
from typing import Any, Iterable, Mapping


class CudaContractError(RuntimeError):
"""Raised when CUDA device code or compatibility metadata is invalid."""


CUOBJDUMP_VERSION = "13.3.29"
CUOBJDUMP_SHA256 = "b6f56c1eb5edd046949f9c947e730a1bf0ed5beff6fc20f8ccafd8a1f5d2eff1"


@dataclass(frozen=True)
class CudaVariant:
cuda_version: str
cuda_major: int
minimum_compute_capability: int
minimum_driver_family: int
minimum_driver_api: int
ptx_architectures: frozenset[str]
sass_architectures: frozenset[str]


# These are the exact GGML_NATIVE=OFF defaults in llama.cpp b10453 for the two
# upstream Windows release variants. Treat changes as an intentional contract
# update instead of silently widening or narrowing supported hardware.
CUDA_VARIANTS: dict[str, CudaVariant] = {
"12.4": CudaVariant(
cuda_version="12.4",
cuda_major=12,
minimum_compute_capability=50,
minimum_driver_family=525,
minimum_driver_api=12000,
ptx_architectures=frozenset({"50", "61", "70", "75", "80", "90"}),
sass_architectures=frozenset({"86", "89"}),
),
"13.3": CudaVariant(
cuda_version="13.3",
cuda_major=13,
minimum_compute_capability=75,
minimum_driver_family=580,
minimum_driver_api=13000,
ptx_architectures=frozenset({"75", "80", "90"}),
sass_architectures=frozenset({"86", "89", "120a", "121a"}),
),
}


_ARCHITECTURE_PATTERN = re.compile(r"\bsm_([0-9]+[a-z]?)\b", re.IGNORECASE)


def file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()


def _run_cuobjdump(cuobjdump: Path, *arguments: str) -> str:
try:
result = subprocess.run(
[str(cuobjdump), *arguments],
check=False,
capture_output=True,
text=True,
timeout=120,
)
except (OSError, subprocess.TimeoutExpired) as error:
raise CudaContractError(f"Unable to run cuobjdump: {error}") from error
output = "\n".join(part for part in (result.stdout, result.stderr) if part)
if result.returncode != 0:
detail = output.strip() or f"exit code {result.returncode}"
raise CudaContractError(f"cuobjdump failed: {detail}")
return output


def parse_listed_architectures(output: str) -> frozenset[str]:
"""Return architecture suffixes from cuobjdump list output."""

return frozenset(match.lower() for match in _ARCHITECTURE_PATTERN.findall(output))


def inspect_device_code(
cuobjdump: Path,
backend: Path,
variant: CudaVariant,
) -> dict[str, Any]:
"""Inspect and strictly match the PTX/SASS fatbin contract."""

version_output = _run_cuobjdump(cuobjdump, "--version").strip()
if CUOBJDUMP_VERSION not in version_output:
raise CudaContractError(
f"Expected cuobjdump {CUOBJDUMP_VERSION}, got {version_output!r}"
)
inspector_sha256 = file_sha256(cuobjdump)
if inspector_sha256 != CUOBJDUMP_SHA256:
raise CudaContractError(
"cuobjdump executable digest differs from the pinned redistributable"
)
sass = parse_listed_architectures(
_run_cuobjdump(cuobjdump, "--list-elf", str(backend))
)
ptx = parse_listed_architectures(
_run_cuobjdump(cuobjdump, "--list-ptx", str(backend))
)
if sass != variant.sass_architectures:
raise CudaContractError(
f"CUDA {variant.cuda_version} SASS architectures differ: "
f"expected {sorted(variant.sass_architectures)}, got {sorted(sass)}"
)
if ptx != variant.ptx_architectures:
raise CudaContractError(
f"CUDA {variant.cuda_version} PTX architectures differ: "
f"expected {sorted(variant.ptx_architectures)}, got {sorted(ptx)}"
)
return {
"inspector": {
"name": "NVIDIA cuobjdump",
"sha256": inspector_sha256,
"version": version_output,
},
"ptx_architectures": sorted(ptx),
"sass_architectures": sorted(sass),
}


def validate_variant_metadata(manifest: Mapping[str, Any]) -> CudaVariant:
"""Validate compatibility and fatbin fields against the known variant."""

cuda_version = manifest.get("cuda_version")
variant = CUDA_VARIANTS.get(cuda_version)
if variant is None:
raise CudaContractError(f"Unsupported CUDA pack version: {cuda_version!r}")
if manifest.get("cuda_major") != variant.cuda_major:
raise CudaContractError(f"CUDA {cuda_version} major-version metadata differs")
compatibility = manifest.get("compatibility")
device_code = manifest.get("device_code")
expected_compatibility = {
"minimum_compute_capability": variant.minimum_compute_capability,
"minimum_driver_family": variant.minimum_driver_family,
"minimum_driver_api": variant.minimum_driver_api,
}
if compatibility != expected_compatibility:
raise CudaContractError(
f"CUDA {cuda_version} compatibility metadata differs from contract"
)
if not isinstance(device_code, Mapping):
raise CudaContractError(f"CUDA {cuda_version} device-code metadata is missing")
if set(device_code.get("ptx_architectures", [])) != set(
variant.ptx_architectures
):
raise CudaContractError(f"CUDA {cuda_version} PTX metadata differs")
if set(device_code.get("sass_architectures", [])) != set(
variant.sass_architectures
):
raise CudaContractError(f"CUDA {cuda_version} SASS metadata differs")
return variant


def select_cuda_pack(
manifests: Iterable[Mapping[str, Any]],
*,
compute_capability: int,
driver_family: int,
) -> Mapping[str, Any] | None:
"""Select the newest compatible pack from validated manifests."""

compatible: list[tuple[int, Mapping[str, Any]]] = []
for manifest in manifests:
variant = validate_variant_metadata(manifest)
if (
compute_capability >= variant.minimum_compute_capability
and driver_family >= variant.minimum_driver_family
):
compatible.append((variant.cuda_major, manifest))
if not compatible:
return None
return max(compatible, key=lambda item: item[0])[1]
Loading
Loading