Files
vllm-omni/benchmarks/edge_harness/experiments/capture_real_acts.py
T
tzhouamandClaude Opus 5 3834de3105 Rename the project to edge-infer, and complete the packaging audit
Renamed from vllm-embedding: nothing here is about embeddings, and the name had
come from the parent working directory. edge-infer says what it does. The
GitHub repository is renamed to match; nothing had been pushed yet.

The audit found the packaging was incomplete in two ways.

Scripts: 56 of them produced results that are published in this repo but were
not included, so the numbers could be read and not checked. Added under
benchmarks/edge_harness/experiments/ (one-off drivers: the AI Hub on-device
runs, the 4-bit GEMM microbenchmark, fidelity and parity checks, checkpoint
build and round-trip, activation capture, and the load generator used to prove
the contention guard actually refuses) and benchmarks/edge_harness/runs/ (the
24 shell drivers behind published grids). Both directories carry a README
saying plainly that they are a record of what was run rather than a supported
interface -- most hardcode absolute paths from the machine they ran on. The
supported entry points remain the tools one level up.

One broken relative link fixed (comparison.md is under docs/engines/, not
docs/analysis/). All other links and every script reference in the docs now
resolve inside the repo.

Secret scan: the token in ~/.qai_hub/client.ini is absent from the worktree and
from all 61 commits. Worth noting that an earlier version of my own check
reported a false positive, because `grep -rl ... | head` takes its exit status
from head, which always succeeds -- the same class of mistake as the measurement
errors this project keeps recording. The AI Hub scripts authenticate by reading
that file at runtime and contain no credential.

README reframed for the new name and the wider scope: the repository is the
engine changes, the harness, the raw results, the baselines and the analyses,
not just a patch set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1ax5wYLA6XbqEL1i8L9gZ
2026-09-14 14:21:07 +08:00

110 lines
4.9 KiB
Python

"""Capture real layer inputs for calibration and parity.
Quantisation ranges inferred from random tensors describe a distribution the
model never sees. This runs the reference on real text and records what
actually enters a sliding layer at several positions, together with the fp32
output of that layer, so the same tensors can calibrate the quantiser and
then judge it.
"""
import importlib.util, json, sys, types
from pathlib import Path
import numpy as np
import torch
ROOT = Path("/data/zhoutaichang/embedding_infer/vllm-omni")
HERE = Path("/data/zhoutaichang/embedding_infer/analysis/experiments/spark_edge")
def _load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
m = importlib.util.module_from_spec(spec)
sys.modules[name] = m
spec.loader.exec_module(m)
return m
for pkg in ("vllm_omni", "vllm_omni.edge"):
mod = types.ModuleType(pkg); mod.__path__ = []; sys.modules[pkg] = mod
_load("vllm_omni.edge.qnn_export", ROOT / "vllm_omni/edge/qnn_export.py")
se = _load("vllm_omni.edge.spark_export", ROOT / "vllm_omni/edge/spark_export.py")
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL = "XHToken/Spark-X2.5-1.7B"
SNAP = ("/data/zhoutaichang/.cache/huggingface/hub/models--XHToken--Spark-X2.5-1.7B/"
"snapshots/448e61eb392c00f2c403185c5b56d5e0665bfaab")
TEXTS = [
"The quick brown fox jumps over the lazy dog. " * 300,
"In a distant galaxy, engineers argued about memory bandwidth and cache lines. " * 200,
"def forward(self, x):\n return self.down_proj(self.act_fn(self.gate_proj(x)))\n" * 200,
"人工智能模型在移动设备上的推理速度取决于内存带宽和算子调度。" * 300,
]
C = 1024
tok = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
m = AutoModelForCausalLM.from_pretrained(MODEL, trust_remote_code=True,
dtype=torch.float32).eval()
hf = json.loads(Path(f"{SNAP}/config.json").read_text())
import os
WHICH = os.environ.get("CAPTURE", "sliding")
li = hf["layer_types"].index(se.SLIDING if WHICH == "sliding" else se.FULL)
cfg = se.config_from_spark(hf, slice(li, li + 1), include_lm_head=False,
cache_layout="ring" if WHICH == "sliding" else "roll")
step = se.SparkDecodeStep(cfg)
se.load_spark_weights(step, SNAP)
step.eval()
LT = se.SLIDING if WHICH == "sliding" else se.FULL
sw = cfg.cache_len(LT, C)
cos, sin = se._angles(C, cfg.rotary_dim(LT), cfg.rope_theta[LT], torch.float32)
samples = {k: [] for k in ("x", "cos_sw", "sin_sw", "mask_sw", "k_cache_0",
"v_cache_0", "hidden_out")}
for text in TEXTS:
ids = tok(text, add_special_tokens=False)["input_ids"][: C + 1]
assert len(ids) == C + 1
with torch.no_grad():
pre = m(torch.tensor([ids[:-1]]), use_cache=True)
cache = pre.past_key_values
try:
k, v = cache.layers[li].keys, cache.layers[li].values
except AttributeError:
k, v = cache.key_cache[li], cache.value_cache[li]
k, v = k[:, :, -sw:].float(), v[:, :, -sw:].float()
if k.shape[2] < sw: # pad the ring, mask it out
pad = sw - k.shape[2]
z = torch.zeros(k.shape[0], k.shape[1], pad, k.shape[3])
k, v = torch.cat([z, k], 2), torch.cat([z, v], 2)
mask = torch.zeros(1, 1, 1, sw + 1)
# Real hidden state entering this layer, not a random vector.
# Real hidden state entering this layer: run the prefix through the
# layers below it rather than assuming the embedding.
x = m.model.embedding(torch.tensor([[ids[-1]]])).float()
if li:
pos = torch.tensor([[C]])
hs = x
for j in range(li):
lt = hf["layer_types"][j]
prf = m.config.get_partial_rotary_factor(lt)
cj_, sj_ = __import__("importlib").import_module(
m.__class__.__module__).compute_rope_cos_sin(
pos[0], hf["head_dim"], m.config.get_rope_theta(lt),
partial_rotary_factor=prf)
hs = m.model.layers[j](hs, position_embeddings=(cj_, sj_),
past_key_values=cache, cache_position=pos[0])
hs = hs[0] if isinstance(hs, tuple) else hs
x = hs.float()
out = step(x, cos, sin, cos, sin, mask, mask, k, v)[0]
for name, arr in (("x", x), ("cos_sw", cos), ("sin_sw", sin),
("mask_sw", mask), ("k_cache_0", k), ("v_cache_0", v),
("hidden_out", out)):
samples[name].append(arr.numpy().astype(np.float32))
np.savez(HERE / f"real_acts_{WHICH}.npz",
**{k: np.stack(v) for k, v in samples.items()})
print("captured", len(TEXTS), "real samples for", WHICH, "layer", li)
for k, v in samples.items():
a = np.stack(v)
print(f" {k:<12} {str(a.shape):<26} absmax {np.abs(a).max():.3f}")