Files
anyllm-proxy/crates/optimizer/scripts/export_llmlingua2.py
T
whit3rabbitandClaude Opus 4.8 d155bfd148 feat: RTK tool-output compression and opt-in prompt optimizer
RTK (anyllm_rtk crate): command-aware filtering of tool-result text
(test/build/git/log output) via a catalog of 55 declarative filters ported
from OmniRoute (MIT). IO-free, deterministic, prompt-cache safe (cache_control
blocks preserved byte-for-byte). Wired into the Anthropic passthrough (stream +
non-stream) and OpenAI-translate paths, gated per-model via RTK_MODELS.
RTK_COMPRESS env / admin toggle, rtk_compress/rtk_models runtime config.

Optimizer (anyllm_optimize_* crates): opt-in Frozen-Frontier Extractive
Compression of long client-sent conversation history for OpenAI Chat
Completions, the Anthropic translate path, and the Anthropic passthrough path
(client history only, never proxy tool-loop turns). OPTIMIZER_MODE=off|shadow|
live env / admin toggle; live places a cache_control breakpoint at the frontier
over raw bytes. Optional LLMLingua-2 ONNX scorer behind the optimizer-onnx
feature (model fetched on demand, never bundled). New optimizer_* metrics
counters and GET/POST /admin/api/optimizer/model endpoints.

Both features expose runtime config + Settings UI controls and fail open on any
error. Adds workspace members, CI lint/test for the optimizer-onnx feature, and
gitignore rules for downloaded ONNX artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 19:55:33 -05:00

153 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""
Export + int8-quantize the LLMLingua-2 token-importance model to ONNX.
Model: microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank
(~110M params, a mBERT token-classification head: per-subtoken
binary "preserve" vs "discard" logits). This is the "-small" tier
referenced in ALGO.md §6; XLM-R-large is an optional accuracy tier
behind the same trait, not exported here.
This script is offline tooling only, run manually (or in a release job),
NOT part of `cargo build`. Its output (the .onnx file) is never committed
to the repo — see CLAUDE.md "Ship as a hash-pinned downloaded artifact
(MODEL_* env / config), NOT a bundled blob.". Instead:
1. Run this script to produce a quantized .onnx file and its sha256.
2. Upload that file to wherever the deploy target's MODEL_URL points
(e.g. object storage, a GitHub release asset).
3. Set MODEL_SHA256 (and MODEL_URL) and run the `optimize-model` CLI
(crates/optimizer/optimize-cli/src/model_fetch.rs) to download +
sha256-verify the artifact into MODEL_CACHE_DIR/<sha256>/. The scorer
never auto-downloads; `LlmLingua2Scorer::from_files` loads that pair.
Usage:
python crates/optimizer/scripts/export_llmlingua2.py
python crates/optimizer/scripts/export_llmlingua2.py --output /tmp/llmlingua2.onnx
Requires (not part of the Rust workspace's deps; install in a venv):
pip install torch transformers onnx onnxruntime
Output:
Writes an int8-quantized ONNX file to --output (default:
crates/optimizer/artifacts/llmlingua2-bert-base-multilingual-int8.onnx,
gitignored) and prints its sha256 to stdout as:
MODEL_SHA256=<hex>
That value is what gets pinned into MODEL_SHA256 / PolicyVersion.
"""
import argparse
import hashlib
import sys
from pathlib import Path
MODEL_ID = "microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank"
DEFAULT_OUTPUT = (
Path(__file__).resolve().parent.parent
/ "artifacts"
/ "llmlingua2-bert-base-multilingual-int8.onnx"
)
# CLS/SEP included; matches optimize-scorer's `max_seq` (ALGO §6).
MAX_SEQ_LEN = 512
OPSET = 17
def export_fp32(model_id: str, fp32_path: Path) -> None:
"""Trace the HF token-classification model and export it to ONNX (fp32)."""
import torch
from transformers import AutoModelForTokenClassification, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForTokenClassification.from_pretrained(model_id)
model.eval()
dummy = tokenizer(
"export dummy input for onnx tracing",
return_tensors="pt",
padding="max_length",
truncation=True,
max_length=MAX_SEQ_LEN,
)
fp32_path.parent.mkdir(parents=True, exist_ok=True)
torch.onnx.export(
model,
(dummy["input_ids"], dummy["attention_mask"], dummy["token_type_ids"]),
str(fp32_path),
input_names=["input_ids", "attention_mask", "token_type_ids"],
output_names=["logits"],
dynamic_axes={
"input_ids": {0: "batch", 1: "sequence"},
"attention_mask": {0: "batch", 1: "sequence"},
"token_type_ids": {0: "batch", 1: "sequence"},
"logits": {0: "batch", 1: "sequence"},
},
opset_version=OPSET,
)
# Tokenizer files (vocab.txt / tokenizer.json / tokenizer_config.json) are
# loaded at runtime by the Rust `tokenizers` crate; ship them alongside the
# .onnx artifact under the same MODEL_URL prefix.
tokenizer.save_pretrained(str(fp32_path.parent / "tokenizer"))
def quantize_int8(fp32_path: Path, int8_path: Path) -> None:
"""Dynamic int8 quantization (weights only; matches ort's CPU EP)."""
from onnxruntime.quantization import QuantType, quantize_dynamic
int8_path.parent.mkdir(parents=True, exist_ok=True)
quantize_dynamic(
model_input=str(fp32_path),
model_output=str(int8_path),
weight_type=QuantType.QInt8,
)
def sha256_of(path: Path) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--output",
type=Path,
default=DEFAULT_OUTPUT,
help=f"path for the quantized .onnx artifact (default: {DEFAULT_OUTPUT})",
)
parser.add_argument(
"--model-id",
default=MODEL_ID,
help=f"HF model id to export (default: {MODEL_ID})",
)
parser.add_argument(
"--keep-fp32",
action="store_true",
help="keep the intermediate fp32 .onnx file (default: deleted after quantization)",
)
args = parser.parse_args()
fp32_path = args.output.with_name(args.output.stem + ".fp32.onnx")
print(f"[1/3] exporting {args.model_id} to ONNX (fp32) -> {fp32_path}")
export_fp32(args.model_id, fp32_path)
print(f"[2/3] int8-quantizing -> {args.output}")
quantize_int8(fp32_path, args.output)
if not args.keep_fp32:
fp32_path.unlink(missing_ok=True)
digest = sha256_of(args.output)
print(f"[3/3] done: {args.output} ({args.output.stat().st_size} bytes)")
# Machine-parseable line: pin this into MODEL_SHA256 (see optimize-scorer docs).
print(f"MODEL_SHA256={digest}")
return 0
if __name__ == "__main__":
sys.exit(main())