Files
vllm-omni/baselines/llama_cpp/cmp_llamacpp.py
T

106 lines
4.1 KiB
Python
Raw Normal View History

"""Greedy-decode parity for llama.cpp against the same fp32 reference vLLM is
scored on.
The point of the comparison is that "4-bit" is not one thing: llama.cpp's
Q4_K_M and our `cpu_int4` spend a similar number of bits per weight but pick
different grids, so the speed comparison is only meaningful next to a quality
comparison run on identical prompts. llama-server takes a prompt as raw token
ids, so both engines see exactly the token sequence the reference did.
"""
import argparse, json, subprocess, time, urllib.request
import numpy as np
def post(url, payload, timeout=600):
req = urllib.request.Request(
url, data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--gguf", required=True)
ap.add_argument("--ref", default="ref.json")
ap.add_argument("--ref-logits", default="ref_logits.npy")
ap.add_argument("--bin", default="/data/zhoutaichang/embedding_infer/llama.cpp/build/bin/llama-server")
ap.add_argument("--cores", default="56-87")
ap.add_argument("--threads", type=int, default=32)
ap.add_argument("--port", type=int, default=18099)
ap.add_argument("--ctx", type=int, default=4096)
ap.add_argument("--out", default="parity_llamacpp.json")
a = ap.parse_args()
ref = json.load(open(a.ref))
ref_logits = np.load(a.ref_logits)
n_new = len(ref["new_ids"])
proc = subprocess.Popen(
["taskset", "-c", a.cores, a.bin, "-m", a.gguf, "--port", str(a.port),
"-t", str(a.threads), "-c", str(a.ctx), "--no-warmup", "--host", "127.0.0.1"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
url = f"http://127.0.0.1:{a.port}"
try:
for _ in range(180):
try:
urllib.request.urlopen(url + "/health", timeout=2)
break
except Exception:
time.sleep(1)
else:
raise RuntimeError("llama-server did not become healthy")
res = post(url + "/completion", {
"prompt": ref["prompt_ids"], "n_predict": n_new,
"temperature": 0.0, "top_k": 1, "n_probs": 20,
"cache_prompt": False, "post_sampling_probs": False,
})
finally:
proc.terminate()
try:
proc.wait(timeout=30)
except subprocess.TimeoutExpired:
proc.kill()
probs = res.get("completion_probabilities") or []
ids = [p["id"] for p in probs] if probs and "id" in probs[0] else []
n = min(n_new, len(ids)) if ids else 0
match = sum(int(x == y) for x, y in zip(ref["new_ids"][:n], ids[:n]))
rep = {
"engine": "llama.cpp", "gguf": a.gguf.split("/")[-1],
"ref_text": ref["text"], "llamacpp_text": res.get("content", ""),
"greedy_token_match": f"{match}/{n}" if n else "n/a",
"exact_match": bool(n) and ref["new_ids"][:n] == ids[:n],
"ref_top1": ref["top1"],
"llamacpp_first_token": ids[0] if ids else None,
"first_token_agree": bool(ids) and ids[0] == ref["top1"],
}
if probs:
import math
top = (probs[0].get("top_logprobs") or probs[0].get("top_probs")
or probs[0].get("probs") or [])
first = {}
for e in top:
if "id" not in e:
continue
first[int(e["id"])] = (float(e["prob"]) if "prob" in e
else math.exp(float(e["logprob"])))
ref_top = np.argsort(-ref_logits)[:20]
ref_p = np.exp(ref_logits - ref_logits.max())
ref_p /= ref_p.sum()
pairs = [(int(t), float(ref_p[t]), first[int(t)]) for t in ref_top if int(t) in first]
rep["top20_prob_overlap"] = f"{len(pairs)}/20"
if pairs:
d = [abs(r - v) for _, r, v in pairs]
rep["top20_prob_max_abs_diff"] = max(d)
rep["top20_prob_mean_abs_diff"] = sum(d) / len(d)
print(json.dumps(rep, indent=2, ensure_ascii=False))
with open(a.out, "w") as f:
json.dump(rep, f, indent=2, ensure_ascii=False)
if __name__ == "__main__":
main()