HelloSun's picture
新增 upstream baseline 對照、I/O 稽核、全層驗證與實驗環境
587a9df verified
Raw History Blame Contribute Delete
4.49 kB
#!/usr/bin/env python3
import argparse, json, os, re, resource, subprocess, sys, threading, time
PAGE = resource.getpagesize()
def evict(path):
fd = os.open(path, os.O_RDONLY)
try:
sz = os.fstat(fd).st_size
step = 1 << 30
off = 0
while off < sz:
n = min(step, sz - off)
os.posix_fadvise(fd, off, n, os.POSIX_FADV_DONTNEED)
off += n
finally:
os.close(fd)
def read_status(pid):
d = {}
try:
with open(f"/proc/{pid}/status") as f:
for line in f:
k, _, v = line.partition(":")
d[k.strip()] = v.strip()
except OSError:
return None
return d
def kb(s):
if s is None:
return 0
return int(s.split()[0]) * 1024
def swap_used():
with open("/proc/meminfo") as f:
for line in f:
if line.startswith("SwapTotal:"):
tot = int(line.split()[1])
elif line.startswith("SwapFree:"):
free = int(line.split()[1])
return (tot - free) * 1024
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--model", required=True)
ap.add_argument("--bin", required=True)
ap.add_argument("--levels-mb", type=int, nargs="+", required=True)
ap.add_argument("--threads", type=int, default=8)
ap.add_argument("--ctx", type=int, default=2048)
ap.add_argument("--ubatch", type=int, default=128)
ap.add_argument("--n-predict", type=int, default=48)
ap.add_argument("--prompt", default="Write a detailed paragraph about mixture-of-experts routing.")
ap.add_argument("--out-json", required=True)
ap.add_argument("--evidence-dir", required=True)
args = ap.parse_args()
os.makedirs(args.evidence_dir, exist_ok=True)
records = []
for mb in args.levels_mb:
lim = mb * 1024 * 1024
evict(args.model)
swap0 = swap_used()
cmd = ["prlimit", f"--data={lim}", "--nofile=65536", args.bin,
"-m", args.model, "-c", str(args.ctx), "-b", str(args.ubatch),
"-t", str(args.threads), "-n", str(args.n_predict), "--temp", "0",
"-no-cnv", "--no-jinja", "-p", args.prompt]
t0 = time.time()
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
hwm_anon = 0
hwm_rss = 0
hwm_data = 0
peak_read = 0
infinity = 0
while p.poll() is None:
st = read_status(p.pid)
if st:
hwm_anon = max(hwm_anon, kb(st.get("RssAnon")))
hwm_rss = max(hwm_rss, kb(st.get("VmRSS")))
hwm_data = max(hwm_data, kb(st.get("VmData")))
try:
with open(f"/proc/{p.pid}/io") as f:
for line in f:
if line.startswith("read_bytes:"):
peak_read = max(peak_read, int(line.split()[1]))
except OSError:
pass
time.sleep(0.2)
out, err = p.communicate()
dt = time.time() - t0
text = (out + b"\n" + err).decode("utf-8", "replace")
log = os.path.join(args.evidence_dir, f"upstream_{mb}m.log")
open(log, "w").write(text)
decode = prefill = None
for ln in text.replace("\r", "\n").splitlines():
m = re.search(r"(prompt eval|eval) time =.*?([\d.]+)\s*tokens per second", ln)
if m:
if m.group(1) == "prompt eval":
prefill = float(m.group(2))
else:
decode = float(m.group(2))
fail = p.returncode != 0 or decode is None
rec = {
"level_mb": mb,
"limit_bytes": lim,
"returncode": p.returncode,
"seconds": round(dt, 2),
"anon_hwm_gib": round(hwm_anon / (1 << 30), 3),
"rss_hwm_gib": round(hwm_rss / (1 << 30), 3),
"vmdata_hwm_gib": round(hwm_data / (1 << 30), 3),
"read_bytes_gib": round(peak_read / (1 << 30), 3),
"swap_delta_gib": round(max(0, swap_used() - swap0) / (1 << 30), 3),
"decode_tps": decode,
"prefill_tps": prefill,
"result": "FAIL" if fail else "PASS",
"log": log,
}
records.append(rec)
print(json.dumps(rec), flush=True)
json.dump(records, open(args.out_json, "w"), indent=2)
print("wrote", args.out_json)
if __name__ == "__main__":
main()