Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

360 lines
11 KiB
Python

"""Apply PolarQuant to a HuggingFace causal-LM checkpoint.
PolarQuant
Vicentino, Caio. *PolarQuant: Optimal Gaussian Weight Quantization via
Hadamard Rotation for LLM Compression*. arXiv:2603.29078, March 2026.
Reference: https://github.com/caiovicentino/eoq-quantization @
15a12160245d7d3015290c6c5b6dbb7f22094d5e.
Walks every ``nn.Linear`` whose weight tensor has at least ``--min-numel``
elements and replaces its ``weight`` with the PolarQuant round-trip
reconstruction (per-block L2 normalize -> WHT -> Lloyd-Max -> optional
1-bit QJL residual -> inverse). Saves the resulting model + a sidecar
``polarquant_artifacts.safetensors`` carrying the int8 codes + fp16
norms (the format a downstream INT4 inference kernel consumes).
PolarQuant is data-free: ``--calibration`` is accepted only for CLI
parity with the other quantizers.
"""
from __future__ import annotations
import argparse
import gc
import json
import logging
import sys
import time
from dataclasses import asdict, dataclass
from pathlib import Path
import torch
import torch.nn as nn
from safetensors.torch import save_file
from transformers import AutoConfig
_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
sys.path.insert(0, str(_HERE))
from _common import ( # noqa: E402
kernel_manifest_fragment,
load_model_and_tokenizer,
save_model,
)
from polarquant.polar_quant import ( # noqa: E402
PolarQuantResult,
polar_dequantize,
polar_quantize,
)
logger = logging.getLogger("polarquant_apply")
# Architectures we have explicitly verified against the standard
# ``self_attn.{q,k,v,o}_proj`` / ``mlp.{gate,up,down}_proj`` linear layout.
_KNOWN_GOOD_ARCH_SUBSTRINGS = ("gemma", "llama", "mistral", "phi3")
@dataclass(frozen=True)
class PolarQuantRecipe:
"""Knobs handed to PolarQuant for one model."""
bits: int = 4
block_size: int = 128
use_qjl: bool = True
min_numel: int = 4096
skip_lm_head: bool = True
skip_embedding: bool = True
dtype: str = "float16"
def to_json(self) -> dict[str, object]:
return {
**asdict(self),
"paper": "arXiv:2603.29078",
"upstream_commit": "15a12160245d7d3015290c6c5b6dbb7f22094d5e",
"upstream_repo": "https://github.com/caiovicentino/eoq-quantization",
}
def _iter_linears(
model: nn.Module,
*,
min_numel: int,
skip_lm_head: bool,
skip_embedding: bool,
) -> list[tuple[str, nn.Linear]]:
"""Linears we want to quantize, in deterministic order.
Skips small projections / MoE routers (``min_numel``), the LM head
(weight-tied on many causal-LM models), and embedding tables (lookups,
not multiplies).
"""
out: list[tuple[str, nn.Linear]] = []
for name, module in model.named_modules():
if not isinstance(module, nn.Linear):
continue
if skip_lm_head and name.endswith("lm_head"):
continue
if skip_embedding and "embed" in name:
continue
if module.weight.numel() < min_numel:
continue
out.append((name, module))
return out
def _quantize_linear_inplace(
layer: nn.Linear,
*,
bits: int,
block_size: int,
use_qjl: bool,
) -> tuple[PolarQuantResult, float]:
"""Run PolarQuant on one linear's weight, write the reconstruction back.
Returns ``(result, mse)`` where ``mse`` is the reconstruction MSE.
"""
weight = layer.weight.data
target_dtype = weight.dtype
target_device = weight.device
result = polar_quantize(
weight.detach().to(torch.float32),
bits=bits,
block_size=block_size,
use_qjl=use_qjl,
)
recon = polar_dequantize(result, device=target_device).to(target_dtype)
mse = (weight.float() - recon.float()).pow(2).mean().item()
with torch.no_grad():
layer.weight.data.copy_(recon)
del recon
return result, mse
def _save_artifacts(
artifacts: dict[str, PolarQuantResult],
*,
output_dir: Path,
) -> Path:
"""Persist int8 codes + fp16 norms (+ optional QJL bits) to a single
safetensors blob keyed by parameter name.
"""
flat: dict[str, torch.Tensor] = {}
for name, res in artifacts.items():
flat[f"{name}.codes"] = res.codes.detach().to(torch.int8).cpu().contiguous()
flat[f"{name}.norms"] = res.norms.detach().to(torch.float16).cpu().contiguous()
if res.use_qjl and res.qjl_signs is not None:
flat[f"{name}.qjl"] = res.qjl_signs.detach().to(torch.uint8).cpu().contiguous()
sidecar = output_dir / "polarquant_artifacts.safetensors"
sidecar.parent.mkdir(parents=True, exist_ok=True)
save_file(flat, str(sidecar))
return sidecar
def quantize_checkpoint(
*,
model_id_or_path: str,
output_dir: Path,
recipe: PolarQuantRecipe,
device: str = "auto",
save_artifacts: bool = True,
progress_every: int = 32,
) -> dict[str, object]:
"""Load -> PolarQuant every linear -> save. Returns a stats dict."""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
logger.info("Loading config for %s", model_id_or_path)
config = AutoConfig.from_pretrained(model_id_or_path, trust_remote_code=True)
arch_lc = (getattr(config, "model_type", "") or "").lower()
if not any(s in arch_lc for s in _KNOWN_GOOD_ARCH_SUBSTRINGS):
logger.warning(
"Architecture %r is not on the known-good list %s; PolarQuant "
"will proceed but the reconstruction quality has not been "
"verified for this model family in this repo.",
arch_lc,
_KNOWN_GOOD_ARCH_SUBSTRINGS,
)
dtype_map = {
"float16": torch.float16,
"bfloat16": torch.bfloat16,
"float32": torch.float32,
}
dtype = dtype_map[recipe.dtype]
device_map = device if device != "auto" else None
logger.info("Loading model weights (dtype=%s, device=%s)", recipe.dtype, device)
model, tokenizer = load_model_and_tokenizer(
model_id_or_path, device_map=device_map, dtype=dtype
)
if device == "auto" and torch.cuda.is_available():
model = model.to("cuda")
model.eval()
layers = _iter_linears(
model,
min_numel=recipe.min_numel,
skip_lm_head=recipe.skip_lm_head,
skip_embedding=recipe.skip_embedding,
)
logger.info("Found %d linear layers eligible for PolarQuant", len(layers))
artifacts: dict[str, PolarQuantResult] = {}
mses: list[float] = []
n_params_quantized = 0
t0 = time.perf_counter()
for i, (name, layer) in enumerate(layers, start=1):
n_params_quantized += layer.weight.numel()
result, mse = _quantize_linear_inplace(
layer,
bits=recipe.bits,
block_size=recipe.block_size,
use_qjl=recipe.use_qjl,
)
if save_artifacts:
artifacts[f"{name}.weight"] = result
else:
del result
mses.append(mse)
if i % progress_every == 0 or i == len(layers):
elapsed = time.perf_counter() - t0
avg_mse = sum(mses) / len(mses)
logger.info(
" [%4d/%d] %s numel=%d mse=%.3e avg_mse=%.3e elapsed=%.1fs",
i, len(layers), name, layer.weight.numel(), mse, avg_mse, elapsed,
)
# save_pretrained shards on CPU; move first so we don't allocate a
# multi-shard CUDA-resident state dict.
model.to("cpu")
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
logger.info("Saving quantized model to %s", output_dir)
save_model(model, tokenizer, output_dir)
sidecar_path: Path | None = None
if save_artifacts and artifacts:
sidecar_path = _save_artifacts(artifacts, output_dir=output_dir)
logger.info("Saved PolarQuant artifacts to %s", sidecar_path)
config_path = output_dir / "polarquant_config.json"
config_path.write_text(
json.dumps(
{
"source_model": model_id_or_path,
"recipe": recipe.to_json(),
"n_layers_quantized": len(layers),
"n_params_quantized": n_params_quantized,
"average_block_mse": sum(mses) / max(1, len(mses)),
"max_block_mse": max(mses) if mses else 0.0,
"elapsed_seconds": time.perf_counter() - t0,
"kernel_manifest": kernel_manifest_fragment("polarquant"),
},
indent=2,
),
encoding="utf-8",
)
return {
"output_dir": str(output_dir),
"n_layers": len(layers),
"n_params": n_params_quantized,
"average_mse": sum(mses) / max(1, len(mses)),
"sidecar": str(sidecar_path) if sidecar_path else None,
"config_path": str(config_path),
}
def _build_arg_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
description=(
"Apply PolarQuant (arXiv:2603.29078) to a HF causal-LM checkpoint."
),
)
p.add_argument("--model", required=True, help="HF repo id or local path.")
p.add_argument("--output", required=True, type=Path)
p.add_argument(
"--calibration",
type=Path,
default=None,
help="Accepted but unused (PolarQuant is data-free); validated for existence.",
)
p.add_argument("--calibration-samples", type=int, default=128)
p.add_argument("--bits", type=int, default=4, choices=[2, 3, 4, 5, 6])
p.add_argument("--block-size", type=int, default=128)
p.add_argument("--no-qjl", action="store_true")
p.add_argument("--min-numel", type=int, default=4096)
p.add_argument("--include-lm-head", action="store_true")
p.add_argument("--include-embedding", action="store_true")
p.add_argument(
"--dtype", default="float16", choices=["float16", "bfloat16", "float32"]
)
p.add_argument("--device", default="auto")
p.add_argument("--no-artifacts", action="store_true")
p.add_argument(
"--dry-run",
action="store_true",
help="Validate inputs and print the recipe; don't load or save weights.",
)
p.add_argument(
"--log-level",
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
)
return p
def main(argv: list[str] | None = None) -> int:
args = _build_arg_parser().parse_args(argv)
logging.basicConfig(
level=getattr(logging, args.log_level),
format="[%(asctime)s] %(name)s %(levelname)s: %(message)s",
datefmt="%H:%M:%S",
)
if args.calibration is not None and not args.calibration.exists():
raise FileNotFoundError(
f"--calibration path does not exist: {args.calibration}. "
"PolarQuant doesn't read it, but if you pass the flag it still "
"has to point at a real file."
)
recipe = PolarQuantRecipe(
bits=args.bits,
block_size=args.block_size,
use_qjl=not args.no_qjl,
min_numel=args.min_numel,
skip_lm_head=not args.include_lm_head,
skip_embedding=not args.include_embedding,
dtype=args.dtype,
)
if args.dry_run:
print(json.dumps({"recipe": recipe.to_json(), "model": args.model}, indent=2))
return 0
stats = quantize_checkpoint(
model_id_or_path=args.model,
output_dir=args.output,
recipe=recipe,
device=args.device,
save_artifacts=not args.no_artifacts,
)
print(json.dumps(stats, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())