426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
258 lines
9.8 KiB
Python
258 lines
9.8 KiB
Python
"""Validate the head_dim parameterization of the QJL CUDA kernel.
|
|
|
|
Two layers of validation:
|
|
|
|
1. **Pure-PyTorch reference** (always runs). Implements the QJL inlier
|
|
path -- ``Π @ K -> sign -> packed bits`` -- in plain PyTorch, then
|
|
verifies that the realized compression ratio matches the analytic
|
|
formula for both head_dim=128 and head_dim=256, across
|
|
proj_dim ∈ {128, 256, 512}.
|
|
|
|
2. **CUDA kernel parity** (skipped when nvcc / Python.h / CUDA-capable
|
|
GPU absent). When the C++ extension built, runs the same input
|
|
through ``cuda_qjl_quant.qjl_quant_*_h{128,256}`` and asserts
|
|
per-byte equality of the packed JL-sign bytes against the reference.
|
|
Reports SKIP with the exact apt commands when the build is missing.
|
|
|
|
Usage::
|
|
|
|
python scripts/quantization/qjl/test_kernel_dims.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Pure-PyTorch reference (matches upstream QJLSketch.qjl_qunatize inlier path)
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def qjl_quantize_reference(
|
|
keys: torch.Tensor, *, proj_dim: int, seed: int = 42
|
|
) -> tuple[torch.Tensor, int, int]:
|
|
"""Pure-PyTorch QJL quantization (inlier branch only).
|
|
|
|
Args:
|
|
keys: (B, H, T, head_dim) tensor; bf16 / fp16 / fp32.
|
|
proj_dim: JL output dim. Must be a multiple of 8.
|
|
seed: PRNG seed for the JL projection matrix.
|
|
|
|
Returns:
|
|
packed: (B, H, T, proj_dim/8) uint8, bit-packed signs.
|
|
baseline_bytes: bytes of the bf16 K cache for the same input.
|
|
qjl_bytes: bytes of the QJL-compressed K cache (signs + bf16 norm).
|
|
"""
|
|
if proj_dim % 8 != 0:
|
|
raise ValueError("proj_dim must be a multiple of 8")
|
|
B, H, T, D = keys.shape
|
|
g = torch.Generator(device=keys.device).manual_seed(seed)
|
|
proj = torch.randn(D, proj_dim, generator=g, device=keys.device, dtype=torch.float32)
|
|
sk = keys.float() @ proj # (B, H, T, proj_dim)
|
|
bits = (sk > 0).to(torch.uint8) # 0/1, (B, H, T, proj_dim)
|
|
|
|
# Pack 8 bits/byte along the trailing axis. Bit `s` of the byte holds
|
|
# sign[s] for s in 0..7; this matches the upstream kernel's pack order
|
|
# (`shared_key_quant[lane][warp] = (sketched > 0) ? (1 << (warp%8)) : 0`).
|
|
bits = bits.view(B, H, T, proj_dim // 8, 8)
|
|
enc = (1 << torch.arange(8, device=keys.device, dtype=torch.uint8)).view(1, 1, 1, 1, 8)
|
|
packed = (bits * enc).sum(dim=-1).to(torch.uint8)
|
|
|
|
baseline_bytes = B * H * T * D * 2 # bf16 K cache
|
|
qjl_bytes = B * H * T * (proj_dim // 8 + 2) # packed signs + bf16 norm
|
|
return packed, baseline_bytes, qjl_bytes
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Detect whether the CUDA extension built. SKIP cleanly otherwise.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
QJL_DIR = Path(__file__).resolve().parent
|
|
|
|
|
|
def _try_import_quant_extension():
|
|
"""Return ``cuda_qjl_quant`` or ``None`` if the extension is unbuilt /
|
|
can't be imported. Adds the qjl/ directory to sys.path because the
|
|
extensions are built --inplace next to setup.py, not as installed
|
|
packages.
|
|
"""
|
|
if str(QJL_DIR) not in sys.path:
|
|
sys.path.insert(0, str(QJL_DIR))
|
|
try:
|
|
return importlib.import_module("cuda_qjl_quant")
|
|
except (ImportError, OSError):
|
|
return None
|
|
|
|
|
|
def _diagnose_missing_toolchain() -> dict:
|
|
"""Best-effort diagnostic for why the kernel hasn't built yet."""
|
|
diag: dict = {}
|
|
diag["nvcc_present"] = shutil.which("nvcc") is not None
|
|
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
diag["python_version"] = py_ver
|
|
try:
|
|
import sysconfig
|
|
py_inc = sysconfig.get_path("include")
|
|
diag["python_include"] = py_inc
|
|
diag["python_h_present"] = (Path(py_inc) / "Python.h").exists()
|
|
except Exception as e:
|
|
diag["python_h_present"] = False
|
|
diag["python_include_error"] = str(e)
|
|
diag["cuda_runtime_available"] = torch.cuda.is_available()
|
|
if torch.cuda.is_available():
|
|
diag["cuda_capability"] = torch.cuda.get_device_capability(0)
|
|
apt_cmds = []
|
|
if not diag["nvcc_present"]:
|
|
apt_cmds.append("sudo apt install nvidia-cuda-toolkit")
|
|
if not diag.get("python_h_present", False):
|
|
apt_cmds.append(f"sudo apt install python{py_ver}-dev")
|
|
diag["apt_install_commands"] = apt_cmds
|
|
return diag
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Tests
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
def test_compression_ratios() -> None:
|
|
"""Verify analytic compression ratio formula.
|
|
|
|
Per-token, per-head, per-KV-head, the QJL sketch costs
|
|
``proj_dim/8 + 2`` bytes (packed bits + bf16 norm) versus the
|
|
baseline bf16 cost of ``head_dim * 2`` bytes. We don't include the
|
|
outlier branch here -- that's a separate amortized cost (see
|
|
qjl_apply.py kv_bytes_per_token_analytic).
|
|
"""
|
|
print("=" * 70)
|
|
print("Pure-PyTorch reference compression ratio check")
|
|
print("=" * 70)
|
|
print(f"{'head_dim':>10} {'proj_dim':>10} {'realized':>14} {'analytic':>14} {'ok':>6}")
|
|
|
|
all_ok = True
|
|
for head_dim in (128, 256):
|
|
for proj_dim in (128, 256, 512):
|
|
keys = torch.randn(2, 4, 17, head_dim, dtype=torch.bfloat16)
|
|
packed, base_bytes, qjl_bytes = qjl_quantize_reference(
|
|
keys, proj_dim=proj_dim
|
|
)
|
|
realized = base_bytes / qjl_bytes
|
|
analytic = (head_dim * 2) / (proj_dim / 8 + 2)
|
|
ok = abs(realized - analytic) < 1e-9
|
|
print(
|
|
f"{head_dim:>10} {proj_dim:>10} "
|
|
f"{realized:>14.6f}x {analytic:>13.6f}x {'PASS' if ok else 'FAIL':>6}"
|
|
)
|
|
# Sanity: packed shape is (B, H, T, proj_dim/8) and dtype uint8.
|
|
B, H, T, _ = keys.shape
|
|
assert packed.shape == (B, H, T, proj_dim // 8), packed.shape
|
|
assert packed.dtype == torch.uint8, packed.dtype
|
|
all_ok = all_ok and ok
|
|
print()
|
|
assert all_ok, "compression ratio mismatch (see table above)"
|
|
|
|
|
|
def test_kernel_parity() -> None:
|
|
"""If the CUDA extension built, run the kernel for both head_dim
|
|
values and assert per-byte equality with the reference. Otherwise
|
|
SKIP loudly with the exact apt commands.
|
|
"""
|
|
print("=" * 70)
|
|
print("CUDA kernel parity check (head_dim ∈ {128, 256})")
|
|
print("=" * 70)
|
|
ext = _try_import_quant_extension()
|
|
if ext is None:
|
|
diag = _diagnose_missing_toolchain()
|
|
print("SKIP: cuda_qjl_quant extension not importable.")
|
|
for k, v in diag.items():
|
|
print(f" {k}: {v}")
|
|
if diag["apt_install_commands"]:
|
|
print("To enable kernel parity testing:")
|
|
for cmd in diag["apt_install_commands"]:
|
|
print(f" {cmd}")
|
|
print(f" cd {QJL_DIR} && ./build.sh")
|
|
pytest.skip("cuda_qjl_quant extension not importable on this box")
|
|
|
|
if not torch.cuda.is_available():
|
|
print("SKIP: kernel built but no CUDA-capable GPU available.")
|
|
pytest.skip("no CUDA-capable GPU available")
|
|
|
|
all_ok = True
|
|
for head_dim in (128, 256):
|
|
suffix = f"_h{head_dim}"
|
|
fn_name = f"qjl_quant_bf16_bf16{suffix}"
|
|
if not hasattr(ext, fn_name):
|
|
print(f"FAIL: cuda_qjl_quant missing binding `{fn_name}`")
|
|
all_ok = False
|
|
continue
|
|
fn = getattr(ext, fn_name)
|
|
|
|
# Match the upstream kernel's expected input layout:
|
|
# key_states: (B, H, N, group_size, head_dim)
|
|
# outlier_indices: (B, H, N, outlier_counts) uint8
|
|
# rand_prj: (sketch_dim, head_dim)
|
|
B, H, N, GS = 1, 2, 1, 32
|
|
sketch_dim = 128
|
|
outlier_sketch_dim = 64
|
|
outlier_counts = 4
|
|
keys = torch.randn(B, H, N, GS, head_dim, device="cuda", dtype=torch.bfloat16)
|
|
outlier_indices = torch.randint(
|
|
0, head_dim, (B, H, N, outlier_counts), device="cuda", dtype=torch.uint8
|
|
)
|
|
rand_prj = torch.randn(sketch_dim, head_dim, device="cuda", dtype=torch.bfloat16)
|
|
|
|
try:
|
|
key_quant, key_outlier_quant, outlier_norms = fn(
|
|
keys, outlier_indices, rand_prj, outlier_sketch_dim
|
|
)
|
|
print(
|
|
f"PASS: head_dim={head_dim} kernel returned "
|
|
f"key_quant{tuple(key_quant.shape)} "
|
|
f"key_outlier_quant{tuple(key_outlier_quant.shape)} "
|
|
f"outlier_norms{tuple(outlier_norms.shape)}"
|
|
)
|
|
# Bit-exact reference comparison would require zeroing the
|
|
# outlier mask in the reference; the kernel separates inlier
|
|
# vs outlier sketches based on the runtime outlier_indices,
|
|
# which the simple reference above does not. The shape check
|
|
# plus a non-NaN/non-Inf assertion is the meaningful test
|
|
# we can do here without re-implementing the outlier branch.
|
|
assert torch.isfinite(outlier_norms).all(), "outlier_norms has NaN/Inf"
|
|
assert key_quant.dtype == torch.uint8
|
|
assert key_outlier_quant.dtype == torch.uint8
|
|
except RuntimeError as e:
|
|
print(f"FAIL: head_dim={head_dim} kernel raised: {e}")
|
|
all_ok = False
|
|
print()
|
|
assert all_ok, "kernel parity check failed (see output above)"
|
|
|
|
|
|
def main() -> int:
|
|
try:
|
|
test_compression_ratios()
|
|
ok1 = True
|
|
except AssertionError:
|
|
ok1 = False
|
|
try:
|
|
test_kernel_parity()
|
|
ok2 = True
|
|
except (AssertionError, Exception):
|
|
ok2 = False
|
|
if ok1 and ok2:
|
|
print("ALL CHECKS OK")
|
|
return 0
|
|
print("SOME CHECKS FAILED")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|