Guest User

Untitled

a guest
Jul 10th, 2026
40
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 7.93 KB | Source Code | 0 0
  1. #!/usr/bin/env bash
  2. # bench-pascal-mmvq.sh -- paired A/B benchmark for the Pascal DP4A mmvq nwarps change.
  3. #
  4. # Builds BASELINE (HEAD~1) and PR (HEAD) into separate binaries, then runs
  5. # llama-bench alternating between them. Consumer Pascal can't lock clocks, so we
  6. # report the PAIRED per-iteration delta, which cancels thermal/boost drift.
  7. # Decode (tg) is the mmvq path this PR changes; prefill (pp) is the untouched MMQ
  8. # path, kept as a no-regression control (expected ~0%) with a -ub batch sweep.
  9. #
  10. #   ./bench-pascal-mmvq.sh                     # ITERS=6 REPS=10 defaults
  11. #   SKIP_BUILD=1 ./bench-pascal-mmvq.sh        # reuse existing builds
  12. #
  13. # reproducing: to reproduce, edit only the two blocks flagged "reproducing:" below
  14. # (MODELS paths, and CUDA_HOST_CC iff your build hits a CUDA/host-GCC mismatch).
  15. set -euo pipefail
  16.  
  17. REPO="${REPO:-$(cd "$(dirname "$0")" && pwd)}"
  18. BASELINE_REF="${BASELINE_REF:-HEAD~1}"          # "before" commit
  19. PR_REF="${PR_REF:-HEAD}"                         # "after" commit
  20.  
  21. # reproducing: point these at your own GGUFs (or set MODEL_DIR=/path). "LABEL|path".
  22. MDL="${MODEL_DIR:-$REPO/models/dl}"
  23. MODELS=(
  24.   "Q4_0|$MDL/Llama-3.2-3B-Instruct-Q4_0.gguf"
  25.   "Q4_K_M|$MDL/Llama-3.2-3B-Instruct-Q4_K_M.gguf"
  26.   "Q5_K_M|$MDL/Llama-3.2-3B-Instruct-Q5_K_M.gguf"
  27.   "Q8_0|$MDL/Llama-3.2-3B-Instruct-Q8_0.gguf"
  28.   "Q6_K|$MDL/Llama-3.2-3B-Instruct-Q6_K.gguf"
  29. )
  30.  
  31. ITERS="${ITERS:-6}"; REPS="${REPS:-10}"; NGEN="${NGEN:-128}"; NGL="${NGL:-99}"; COOLDOWN="${COOLDOWN:-3}"
  32.  
  33. # "cfgname|llama-bench args" per invocation. tg*=decode (mmvq, the win); pp*=prefill
  34. # (MMQ control, ~0%). reproducing: trim pp rows / shrink -p/-ub if VRAM-limited.
  35. BENCH=(
  36.   "tg128|-p 0 -n $NGEN -ub 512"
  37.   "pp512|-p 512 -n 0 -ub 512"
  38.   "pp512_ub128|-p 512 -n 0 -ub 128"
  39.   "pp2048|-p 2048 -n 0 -ub 512"
  40. )
  41.  
  42. CUDA_ARCH="${CUDA_ARCH:-61}"                      # sm_61 = GTX 10xx/P40/P4; 60 for P100
  43. # reproducing: leave empty normally. Set to a host GCC <= 11 only if the build fails
  44. # with "parameter packs not expanded with '...'" (CUDA <= 11.5 vs new host GCC).
  45. CUDA_HOST_CC="${CUDA_HOST_CC:-}"
  46. CMAKE_FLAGS="${CMAKE_FLAGS:--DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_ARCHITECTURES=$CUDA_ARCH}"
  47. [[ -n "$CUDA_HOST_CC" ]] && CMAKE_FLAGS="$CMAKE_FLAGS -DCMAKE_CUDA_HOST_COMPILER=$CUDA_HOST_CC"
  48.  
  49. WORK="$REPO/.bench-work"; RESULTS="$WORK/results"
  50. BASE_TREE="$WORK/baseline-src"; BASE_BUILD="$WORK/build-baseline"; PR_BUILD="$REPO/build-pr"
  51. BASE_BIN="$BASE_BUILD/bin/llama-bench"; PR_BIN="$PR_BUILD/bin/llama-bench"
  52.  
  53. log() { printf '\033[1;36m[bench]\033[0m %s\n' "$*"; }
  54.  
  55. build_all() {
  56.   [[ "${SKIP_BUILD:-0}" == "1" ]] && { log "SKIP_BUILD=1, reusing binaries"; return; }
  57.   cd "$REPO"
  58.   log "Building PR ($PR_REF)"
  59.   cmake -B "$PR_BUILD" $CMAKE_FLAGS -S "$REPO" >/dev/null
  60.   cmake --build "$PR_BUILD" --target llama-bench -j"$(nproc)" >/dev/null
  61.   log "Building baseline ($BASELINE_REF) in a worktree"
  62.   git worktree remove --force "$BASE_TREE" 2>/dev/null || true
  63.   git worktree add --detach "$BASE_TREE" "$BASELINE_REF" >/dev/null
  64.   cmake -B "$BASE_BUILD" $CMAKE_FLAGS -S "$BASE_TREE" >/dev/null
  65.   cmake --build "$BASE_BUILD" --target llama-bench -j"$(nproc)" >/dev/null
  66. }
  67.  
  68. # run <bin> <model> <extra_args> <out.json>
  69. run() {
  70.   # shellcheck disable=SC2086  -- $3 is a deliberately word-split arg list
  71.   "$1" -m "$2" $3 -ngl "$NGL" -r "$REPS" -o json > "$4" 2>/dev/null
  72. }
  73.  
  74. main() {
  75.   local have=0; for e in "${MODELS[@]}"; do [[ -f "${e#*|}" ]] && have=1; done
  76.   [[ "$have" == "1" ]] || { echo "ERROR: no MODELS paths exist; edit MODELS or set MODEL_DIR."; exit 1; }
  77.   rm -rf "$RESULTS"; mkdir -p "$RESULTS"              # always override previous run
  78.   build_all
  79.   [[ -x "$BASE_BIN" && -x "$PR_BIN" ]] || { echo "missing binaries; run without SKIP_BUILD"; exit 1; }
  80.  
  81.   log "GPU: $(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || echo unknown)"
  82.   log "ITERS=$ITERS REPS=$REPS NGEN=$NGEN NGL=$NGL"
  83.   log "Warmup..."; run "$PR_BIN" "${MODELS[0]#*|}" "-p 0 -n $NGEN -ub 512" "$RESULTS/warmup.json" || true
  84.  
  85.   for ((i=1; i<=ITERS; i++)); do
  86.     for e in "${MODELS[@]}"; do
  87.       label="${e%%|*}"; path="${e#*|}"; safe="${label//[^A-Za-z0-9]/_}"
  88.       [[ -f "$path" ]] || { echo "  !! skipping missing model: $path"; continue; }
  89.       (( i % 2 == 1 )) && order=(baseline pr) || order=(pr baseline)   # swap so neither build always runs hotter
  90.       for cfg in "${BENCH[@]}"; do
  91.         cname="${cfg%%|*}"; cargs="${cfg#*|}"; csafe="${cname//[^A-Za-z0-9]/_}"
  92.         for b in "${order[@]}"; do                    # A/B adjacent per (model,cfg) for tight drift cancellation
  93.           bin="$BASE_BIN"; [[ "$b" == "pr" ]] && bin="$PR_BIN"
  94.           log "iter $i/$ITERS  $label  $cname  [$b]"
  95.           run "$bin" "$path" "$cargs" "$RESULTS/${b}__${safe}__${csafe}__${i}.json"
  96.           sleep "$COOLDOWN"
  97.         done
  98.       done
  99.     done
  100.   done
  101.  
  102.   log "Aggregating..."
  103.   RESULTS="$RESULTS" BENCH_CFGS="$(printf '%s\n' "${BENCH[@]%%|*}")" python3 - "${MODELS[@]}" <<'PY'
  104. import json, glob, os, sys, math, statistics as st
  105.  
  106. results = os.environ["RESULTS"]
  107. labels  = [a.split("|", 1)[0] for a in sys.argv[1:]]
  108. cfgs    = os.environ["BENCH_CFGS"].split()
  109. san     = lambda s: "".join(c if c.isalnum() else "_" for c in s)
  110.  
  111. def samples(build, safe, csafe):                        # {iter: avg_ts}, for pairing
  112.     out = {}
  113.     for f in sorted(glob.glob(f"{results}/{build}__{safe}__{csafe}__*.json")):
  114.         try: out[int(f.rsplit("__", 1)[1].split(".")[0])] = float(json.load(open(f))[0]["avg_ts"])
  115.         except Exception: pass
  116.     return out
  117.  
  118. def row(label, cfg, safe, csafe):
  119.     bd, pd = samples("baseline", safe, csafe), samples("pr", safe, csafe)
  120.     if not bd or not pd: return None
  121.     bm, pm = st.mean(list(bd.values())), st.mean(list(pd.values()))
  122.     diffs = [(pd[i]-bd[i])/bd[i]*100.0 for i in sorted(set(bd) & set(pd))]   # per-iter paired Δ%
  123.     n, up = len(diffs), sum(x > 0 for x in diffs)
  124.     dsd = st.stdev(diffs) if n > 1 else float("nan")
  125.     tval = st.mean(diffs)/(dsd/math.sqrt(n)) if n > 1 and dsd else (float("inf") if n > 1 else float("nan"))
  126.     return (label, cfg, bm, pm, (pm-bm)/bm*100.0, dsd, tval, f"{up}/{n}")
  127.  
  128. decode, prefill, ratios = [], [], []
  129. for cfg in cfgs:
  130.     dest = decode if cfg.lower().startswith("tg") else prefill
  131.     for label in labels:
  132.         r = row(label, cfg, san(label), san(cfg))
  133.         if r: dest.append(r); (ratios.append(r[3]/r[2]) if dest is decode else None)
  134.  
  135. def table(title, rows, note):
  136.     W = 100; print("\n" + "="*W); print("  " + title); print("="*W)
  137.     print(f"{'quant':<9}{'config':<13}{'baseline':>11}{'PR t/s':>11}{'mean Δ':>9}{'paired σ%':>11}{'t':>8}{'pairs':>7}")
  138.     print("-"*W)
  139.     for l, c, bm, pm, d, sd, t, p in rows:
  140.         print(f"{l:<9}{c:<13}{bm:>11.2f}{pm:>11.2f}{d:>+8.2f}%{sd:>10.2f}%{t:>8.2f}{p:>7}")
  141.     print("-"*W); print(note)
  142.  
  143. table("DECODE (tg) : BASELINE vs PR  -- the mmvq path this PR changes", decode,
  144.       "pairs = iters where PR>baseline. t = paired t-statistic of per-iter Δ% (drift-cancelling).")
  145. geo = math.exp(sum(map(math.log, ratios))/len(ratios)) if ratios else None
  146. if geo: print(f"  CUMULATIVE decode (geomean): {(geo-1)*100:+.2f}%")
  147. if prefill:
  148.     table("PREFILL (pp) : BASELINE vs PR  -- untouched MMQ path (control, expect ~0%)", prefill,
  149.           "No-regression / batch-coverage control; this change is scoped to decode.")
  150.  
  151. def md(title, rows):
  152.     print(f"\n**{title}**\n")
  153.     print("| quant | config | baseline t/s | patched t/s | mean Δ | paired σ% | t | pairs up |")
  154.     print("|-------|--------|--------------|-------------|--------|-----------|---|----------|")
  155.     for l, c, bm, pm, d, sd, t, p in rows:
  156.         print(f"| {l} | {c} | {bm:.2f} | {pm:.2f} | {d:+.2f}% | {sd:.2f}% | {t:.2f} | {p} |")
  157.  
  158. md("Decode (tg) — mmvq path this PR changes", decode)
  159. if geo: print(f"\nGeomean decode speedup: **{(geo-1)*100:+.2f}%**")
  160. if prefill: md("Prefill (pp) — MMQ control (expected ~0%, no regression)", prefill)
  161. PY
  162. }
  163.  
  164. main "$@"
  165.  
Tags: BASH
Advertisement
Add Comment
Please, Sign In to add comment