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
251 lines
11 KiB
Python
251 lines
11 KiB
Python
"""Python dispatch layer for the vendored QJL CUDA extensions.
|
|
|
|
The CUDA kernels are compiled per (head_dim in {128, 256}) instantiation --
|
|
upstream hard-coded EMB_DIM=128, while Gemma-era validation needs a
|
|
256-dim specialization. The wrappers below pick the right
|
|
specialization at call-time based on the input tensor shape, so
|
|
downstream callers (e.g. ``LlamaAttention_QJL``) never have to know.
|
|
|
|
The public entry-point names (``qjl_quant``, ``qjl_score``,
|
|
``qjl_gqa_score``) and their argument signatures are byte-identical to
|
|
upstream. Only the dispatch internals changed.
|
|
|
|
Both module-level import of ``cuda_qjl_*`` and per-call dispatch are
|
|
deferred to runtime so this module imports cleanly on a host where the
|
|
CUDA extension has not been built. Callers can still use
|
|
``qjl_quantize_pytorch`` for the inlier branch without nvcc.
|
|
"""
|
|
|
|
import importlib
|
|
|
|
import torch
|
|
|
|
|
|
# Compiled head_dim specializations. Add new sizes here after rebuilding
|
|
# the kernel with a matching template instantiation in csrc/*.cu.
|
|
_SUPPORTED_EMB_DIMS = (128, 256)
|
|
|
|
|
|
_KERNEL_CACHE: dict[str, object] = {}
|
|
|
|
|
|
def _load_kernel(name: str):
|
|
"""Lazy import of a compiled CUDA extension. Raises ImportError when
|
|
the build hasn't run; callers should catch and fall back to the
|
|
pure-PyTorch reference path.
|
|
"""
|
|
cached = _KERNEL_CACHE.get(name)
|
|
if cached is not None:
|
|
return cached
|
|
mod = importlib.import_module(name)
|
|
_KERNEL_CACHE[name] = mod
|
|
return mod
|
|
|
|
|
|
def _emb_dim_suffix(emb_dim: int) -> str:
|
|
if emb_dim not in _SUPPORTED_EMB_DIMS:
|
|
raise NotImplementedError(
|
|
f"QJL kernel: head_dim={emb_dim} is not a compiled specialization. "
|
|
f"Supported: {_SUPPORTED_EMB_DIMS}. To add a new size, instantiate "
|
|
"the templates in csrc/qjl_quant_kernel.cu, csrc/qjl_score_kernel.cu, "
|
|
"and csrc/qjl_gqa_score_kernel.cu, then rebuild via setup.py."
|
|
)
|
|
return f"_h{emb_dim}"
|
|
|
|
|
|
def qjl_quant(key_states, outlier_indices, rand_prj, outlier_sketch_dim):
|
|
# key_states: (B, H, N, group_size, head_dim)
|
|
cuda_qjl_quant = _load_kernel("cuda_qjl_quant")
|
|
emb_dim = key_states.shape[-1]
|
|
suffix = _emb_dim_suffix(emb_dim)
|
|
|
|
key_dtype = key_states.dtype
|
|
rand_dtype = rand_prj.dtype
|
|
|
|
if key_dtype == torch.half and rand_dtype == torch.half:
|
|
fn = getattr(cuda_qjl_quant, f"qjl_quant_half_half{suffix}")
|
|
elif key_dtype == torch.half and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_quant, f"qjl_quant_half_float{suffix}")
|
|
elif key_dtype == torch.float and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_quant, f"qjl_quant_float_float{suffix}")
|
|
elif key_dtype == torch.bfloat16 and rand_dtype == torch.bfloat16:
|
|
fn = getattr(cuda_qjl_quant, f"qjl_quant_bf16_bf16{suffix}")
|
|
elif key_dtype == torch.bfloat16 and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_quant, f"qjl_quant_bf16_float{suffix}")
|
|
else:
|
|
raise TypeError(
|
|
f"Unsupported data types for QJL quantization: "
|
|
f"key_dtype={key_dtype}, rand_dtype={rand_dtype}"
|
|
)
|
|
return fn(key_states, outlier_indices, rand_prj, outlier_sketch_dim)
|
|
|
|
|
|
def qjl_score(key_quant, key_outlier_quant, key_norm, key_outlier_norm, outlier_indices, query_sketch, query_states, rand_prj):
|
|
# query_states: (B, H, N, head_dim)
|
|
cuda_qjl_score = _load_kernel("cuda_qjl_score")
|
|
emb_dim = query_states.shape[-1]
|
|
suffix = _emb_dim_suffix(emb_dim)
|
|
|
|
query_dtype = query_states.dtype
|
|
rand_dtype = rand_prj.dtype
|
|
|
|
if query_dtype == torch.half and rand_dtype == torch.half:
|
|
fn = getattr(cuda_qjl_score, f"qjl_score_cuda_half_half{suffix}")
|
|
elif query_dtype == torch.half and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_score, f"qjl_score_cuda_half_float{suffix}")
|
|
elif query_dtype == torch.float and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_score, f"qjl_score_cuda_float_float{suffix}")
|
|
elif query_dtype == torch.bfloat16 and rand_dtype == torch.bfloat16:
|
|
fn = getattr(cuda_qjl_score, f"qjl_score_cuda_bf16_bf16{suffix}")
|
|
elif query_dtype == torch.bfloat16 and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_score, f"qjl_score_cuda_bf16_float{suffix}")
|
|
else:
|
|
raise TypeError(
|
|
f"Unsupported data types for QJL score calculation: "
|
|
f"query_dtype={query_dtype}, rand_dtype={rand_dtype}"
|
|
)
|
|
return fn(key_quant, key_outlier_quant, key_norm, key_outlier_norm, outlier_indices, query_sketch, query_states, rand_prj)
|
|
|
|
|
|
def qjl_gqa_score(key_quant, key_outlier_quant, key_norm, key_outlier_norm, outlier_indices, query_sketch, query_states, rand_prj):
|
|
# query_states: (B, H_q, N, head_dim)
|
|
cuda_qjl_gqa_score = _load_kernel("cuda_qjl_gqa_score")
|
|
emb_dim = query_states.shape[-1]
|
|
suffix = _emb_dim_suffix(emb_dim)
|
|
|
|
query_dtype = query_states.dtype
|
|
rand_dtype = rand_prj.dtype
|
|
|
|
if query_dtype == torch.half and rand_dtype == torch.half:
|
|
fn = getattr(cuda_qjl_gqa_score, f"qjl_gqa_score_cuda_half_half{suffix}")
|
|
elif query_dtype == torch.half and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_gqa_score, f"qjl_gqa_score_cuda_half_float{suffix}")
|
|
elif query_dtype == torch.float and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_gqa_score, f"qjl_gqa_score_cuda_float_float{suffix}")
|
|
elif query_dtype == torch.bfloat16 and rand_dtype == torch.bfloat16:
|
|
fn = getattr(cuda_qjl_gqa_score, f"qjl_gqa_score_cuda_bf16_bf16{suffix}")
|
|
elif query_dtype == torch.bfloat16 and rand_dtype == torch.float:
|
|
fn = getattr(cuda_qjl_gqa_score, f"qjl_gqa_score_cuda_bf16_float{suffix}")
|
|
else:
|
|
raise TypeError(
|
|
f"Unsupported data types for QJL GQA score calculation: "
|
|
f"query_dtype={query_dtype}, rand_dtype={rand_dtype}"
|
|
)
|
|
return fn(key_quant, key_outlier_quant, key_norm, key_outlier_norm, outlier_indices, query_sketch, query_states, rand_prj)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pure-PyTorch reference path
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Used when the CUDA extension is not built. Mirrors the inlier branch of
|
|
# upstream ``QJLSketch.qjl_qunatize`` (sign-quantize after a JL projection,
|
|
# then bit-pack along the trailing axis). The outlier branch is approximated
|
|
# by zeroing the requested outlier coordinates before the projection so the
|
|
# downstream score path does not double-count them.
|
|
#
|
|
# The intent is correctness-of-shape, not throughput. This is a fallback
|
|
# for hosts that lack ``nvcc`` / ``python<X>-dev``; on a real serving host
|
|
# the user is expected to have built the CUDA extension.
|
|
|
|
|
|
def qjl_quantize_pytorch(
|
|
key_states: torch.Tensor,
|
|
outlier_indices: torch.Tensor | None,
|
|
rand_prj: torch.Tensor,
|
|
outlier_sketch_dim: int,
|
|
):
|
|
"""Pure-PyTorch QJL inlier-branch quantization.
|
|
|
|
Args:
|
|
key_states: (B, H, N, group_size, head_dim) bf16/fp16/fp32
|
|
tensor of grouped K activations.
|
|
outlier_indices: (B, H, N, outlier_count) uint8 tensor of the
|
|
per-group outlier coordinate indices to mask
|
|
out before projection. Pass ``None`` to skip
|
|
the masking (no outliers).
|
|
rand_prj: (sketch_dim, head_dim) JL projection matrix.
|
|
outlier_sketch_dim: kept for API parity with the CUDA wrapper;
|
|
currently unused by the inlier-only fallback.
|
|
|
|
Returns:
|
|
Tuple ``(key_quant, key_outlier_quant, key_outliers_norm)`` shaped
|
|
analogously to the CUDA path so the caller can substitute either
|
|
backend without changing downstream code:
|
|
|
|
key_quant: (B, H, N, group_size, sketch_dim/8) uint8
|
|
key_outlier_quant: (B, H, N, group_size, outlier_sketch_dim/8) uint8
|
|
-- zero-filled by this inlier-only
|
|
pure-PyTorch fallback.
|
|
key_outliers_norm: (B, H, N, outlier_count) float32 -- the
|
|
per-coordinate L2 norm across the group.
|
|
"""
|
|
if rand_prj.dim() != 2:
|
|
raise ValueError(
|
|
f"rand_prj must be (sketch_dim, head_dim); got shape {tuple(rand_prj.shape)}"
|
|
)
|
|
sketch_dim, head_dim = rand_prj.shape
|
|
if sketch_dim % 8 != 0:
|
|
raise ValueError(f"sketch_dim must be a multiple of 8; got {sketch_dim}")
|
|
if outlier_sketch_dim % 8 != 0:
|
|
raise ValueError(
|
|
f"outlier_sketch_dim must be a multiple of 8; got {outlier_sketch_dim}"
|
|
)
|
|
if key_states.shape[-1] != head_dim:
|
|
raise ValueError(
|
|
f"key_states.shape[-1]={key_states.shape[-1]} does not match "
|
|
f"rand_prj head_dim={head_dim}"
|
|
)
|
|
|
|
B, H, N, group_size, _ = key_states.shape
|
|
|
|
keys = key_states
|
|
if outlier_indices is not None:
|
|
if outlier_indices.shape[:3] != (B, H, N):
|
|
raise ValueError(
|
|
f"outlier_indices.shape[:3]={tuple(outlier_indices.shape[:3])} "
|
|
f"does not match key_states.shape[:3]={(B, H, N)}"
|
|
)
|
|
outlier_count = outlier_indices.shape[-1]
|
|
# Build a (B, H, N, head_dim) boolean mask of inlier coordinates.
|
|
idx = outlier_indices.long()
|
|
mask = torch.ones(B, H, N, head_dim, device=keys.device, dtype=keys.dtype)
|
|
mask.scatter_(-1, idx, 0.0)
|
|
# Zero out outlier coords before projecting (inlier branch only).
|
|
keys = keys * mask.unsqueeze(-2)
|
|
else:
|
|
outlier_count = 0
|
|
|
|
# Project: (..., group_size, head_dim) @ (head_dim, sketch_dim) ->
|
|
# (..., group_size, sketch_dim).
|
|
proj_t = rand_prj.transpose(0, 1).to(keys.dtype)
|
|
sketched = torch.matmul(keys, proj_t)
|
|
|
|
# Sign quantize and pack to uint8 along the trailing axis.
|
|
bits = (sketched > 0).to(torch.uint8)
|
|
bits = bits.view(B, H, N, group_size, sketch_dim // 8, 8)
|
|
enc = (
|
|
1 << torch.arange(8, device=keys.device, dtype=torch.uint8)
|
|
).view(1, 1, 1, 1, 1, 8)
|
|
key_quant = (bits * enc).sum(dim=-1).to(torch.uint8)
|
|
|
|
# This pure-PyTorch reference is inlier-only. Return zero-filled outlier
|
|
# tensors with the right shape so callers can still slot in.
|
|
key_outlier_quant = torch.zeros(
|
|
B, H, N, group_size, outlier_sketch_dim // 8,
|
|
device=keys.device, dtype=torch.uint8,
|
|
)
|
|
if outlier_count > 0:
|
|
# Per-coord L2 norm across the group axis (matches the CUDA path's
|
|
# `outlier_norms` semantics for downstream score reconstruction).
|
|
outlier_vals = torch.gather(
|
|
key_states, -1, idx.unsqueeze(-2).expand(B, H, N, group_size, outlier_count),
|
|
)
|
|
key_outliers_norm = outlier_vals.float().norm(dim=-2)
|
|
else:
|
|
key_outliers_norm = torch.zeros(
|
|
B, H, N, 0, device=keys.device, dtype=torch.float32,
|
|
)
|
|
|
|
return key_quant, key_outlier_quant, key_outliers_norm
|