File size: 5,775 Bytes
491cce7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | #!/usr/bin/env python3
"""
Sensitivity sweep: vary one tensor class tier, measure PPL.
Usage: python sensitivity_sweep.py --model M.gguf --imatrix I.gguf --class ffn_down --tiers Q3_K,IQ4_XS,Q4_K,Q5_K,Q6_K,Q8_0
"""
import argparse
import json
import os
import subprocess
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from model_reader import read_model
from imatrix_reader import read_imatrix, detect_tied_groups, build_importance_table
from config_generator import generate_flags, format_flags
from quantizer import run_dry_run, run_quantization
from constants import (
TIER_ORDER, get_tensor_class, strip_weight
)
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LLAMA_PPL = os.environ.get(
"LLAMA_PPL", "/home/wepiqx/llama.cpp/build/bin/llama-perplexity"
)
WIKITEXT_DATA = os.environ.get(
"WIKITEXT_DATA", "/mnt/Vsio/wikitext-2-raw/wiki.test.raw"
)
def parse_tiers(tier_str):
return [t.strip() for t in tier_str.split(",")]
def _tier_index(tier: str) -> int:
return TIER_ORDER.index(tier) if tier in TIER_ORDER else -1
def build_assignments(model, target_class, tier, base_tier="Q5_K"):
"""Manually build assignments: all at base_tier, target_class at tier."""
model_tensors = model.get("tensors", {})
all_names = set()
for tname in model_tensors:
norm = strip_weight(tname)
all_names.add(norm)
assignments = {}
for name in all_names:
ttype = name.split(".")[-1] if "." in name else name
cls = get_tensor_class(ttype)
if cls == target_class:
assignments[name] = tier
else:
assignments[name] = base_tier
# Special: norms/ssm_params always F16
for name in list(assignments.keys()):
ttype = name.split(".")[-1] if "." in name else name
cls = get_tensor_class(ttype)
if cls in ("norms", "ssm_params"):
assignments[name] = "F16"
# MTP special handling
if cls == "mtp":
assignments[name] = base_tier
# Embedding - keep at base_tier
if cls == "embd":
assignments[name] = base_tier
return assignments
def main():
parser = argparse.ArgumentParser(description="Sensitivity sweep for one tensor class")
parser.add_argument("--model", required=True)
parser.add_argument("--imatrix", required=True)
parser.add_argument("--class", dest="target_class", required=True,
choices=["gate", "attn_proj", "ffn_gate_up", "ffn_down", "mtp", "embd"])
parser.add_argument("--tiers", default="Q3_K,IQ4_XS,Q4_K,Q5_K,Q6_K,Q8_0")
parser.add_argument("--base-tier", default="Q5_K")
parser.add_argument("--output-dir", default=None,
help="Defaults to <repo>/output/sensitivity_sweep")
parser.add_argument("--skip-ppl", action="store_true", help="Only dry-run, skip PPL")
args = parser.parse_args()
tiers = parse_tiers(args.tiers)
if args.output_dir is None:
args.output_dir = os.path.join(REPO_ROOT, "output", "sensitivity_sweep")
os.makedirs(args.output_dir, exist_ok=True)
print(f"=== Sensitivity sweep: {args.target_class} ===")
print(f"Tiers: {tiers}")
print(f"Base tier for others: {args.base_tier}")
model = read_model(args.model)
imatrix = read_imatrix(args.imatrix)
tied_groups = detect_tied_groups(imatrix)
imp_table = build_importance_table(imatrix, model)
results = []
for tier in tiers:
print(f"\n--- Testing {args.target_class} = {tier} ---")
assignments = build_assignments(model, args.target_class, tier, args.base_tier)
base_type = "Q5_K_M"
flags = generate_flags(assignments, model, args.base_tier, 100000)
flags["imatrix"] = args.imatrix
# Dry run
dry_size = run_dry_run(flags, args.model)
print(f" Dry-run size: {dry_size:.0f} MiB")
if args.skip_ppl:
results.append({"tier": tier, "size": dry_size, "ppl": None})
continue
# Quantize (CPU only to avoid OOM)
out_path = os.path.join(args.output_dir, f"{args.target_class}_{tier}.gguf")
print(f" Quantizing to {out_path}...")
# Use CPU for quantization to avoid GPU OOM
success = run_quantization(flags, args.model, out_path)
if not success:
print(f" FAILED")
results.append({"tier": tier, "size": dry_size, "ppl": None, "error": "quant failed"})
continue
# PPL test (CPU)
print(f" Running PPL test (CPU)...")
cmd = [
LLAMA_PPL,
"-m", out_path,
"-f", WIKITEXT_DATA,
"-c", "1024", "-ngl", "0" # CPU only
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=1800)
ppl = None
for line in result.stdout.split("\n"):
if "Final estimate: PPL" in line:
try:
ppl = float(line.split("PPL = ")[1].split(" ")[0])
except:
pass
print(f" PPL: {ppl}")
results.append({"tier": tier, "size": dry_size, "ppl": ppl})
# Cleanup
if os.path.exists(out_path):
os.remove(out_path)
# Summary
print("\n=== RESULTS ===")
print(f"{'Tier':<10} {'Size (MiB)':<12} {'PPL':<10}")
for r in results:
print(f"{r['tier']:<10} {r['size']:<12.0f} {r['ppl'] if r['ppl'] else 'FAILED':<10}")
out_json = os.path.join(args.output_dir, f"sensitivity_{args.target_class}.json")
with open(out_json, "w") as f:
json.dump(results, f, indent=2)
print(f"\nSaved: {out_json}")
if __name__ == "__main__":
main() |