Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tests/test_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,28 @@ def test_kquant_is_not_forced_onto_a_row_length_that_cannot_hold_it():



def test_layer_access_goes_through_text_layers():
"""Loading a VL model is half the job; reaching its layers is the other half.

A vision-language model keeps its decoder under model.language_model, so `model.model.layers`
raises AttributeError even after the model loads fine. Fixing the LOADER alone moved the failure
two lines down -- pollard-probe loaded Qwen2-VL successfully and then died on
`len(model.model.layers)`, which looks like a different bug and is the same one.
"""
tools = pathlib.Path(__file__).resolve().parent.parent / "tools"
offenders = []
for f in sorted(tools.glob("pollard_*.py")):
for i, line in enumerate(f.read_text(encoding="utf-8").splitlines(), 1):
if "model.model.layers" not in line:
continue
if line.lstrip().startswith(("#", "print(", "sys.exit(")) or '"' in line.split("model.model.layers")[0][-2:]:
continue # a message ABOUT the path, not a use of it
offenders.append(f"{f.name}:{i}")
assert not offenders, ("these reach layers directly and break on a VL model: "
+ ", ".join(offenders))



def main():
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
fails = 0
Expand Down
11 changes: 6 additions & 5 deletions tools/pollard_abliterate.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import argparse, os, sys
import torch

from pollard_load import load_backbone, text_layers


# tiny BENIGN placeholder sets -- only so --selftest exercises the mechanism.
# NOT a refusal set; supply real contrast prompts via --harmful/--harmless.
Expand Down Expand Up @@ -112,7 +114,7 @@ def abliterate(model, r_hat, dev, strength=-1.0):
r = r_hat.to(dev).float()
a = float(strength)
edited = 0
layers = model.model.layers
layers = text_layers(model)
for blk in layers:
for lin in (blk.self_attn.o_proj, blk.mlp.down_proj):
W = lin.weight.data.float() # [D, in]
Expand Down Expand Up @@ -157,7 +159,6 @@ def main():
sys.exit("ERROR: pass --out for the abliterated model.")

from transformers import AutoTokenizer
from pollard_load import load_backbone
import pollard_workspace as ws
trc = ws.resolve_trust_remote_code(a.model, a.trust_remote_code)
dev = a.device if (a.device != "mps" or torch.backends.mps.is_available()) else "cpu"
Expand All @@ -183,11 +184,11 @@ def main():

# measure how much of the direction lives in the writers before/after (sanity).
# hidden_states[j] is the OUTPUT of block j-1, so that block's o_proj produced it.
sj = min(max(j - 1, 0), len(model.model.layers) - 1)
o0 = model.model.layers[sj].self_attn.o_proj.weight.data.float()
sj = min(max(j - 1, 0), len(text_layers(model)) - 1)
o0 = text_layers(model)[sj].self_attn.o_proj.weight.data.float()
before = (r_hat.to(dev).float() @ o0).norm().item()
edited = abliterate(model, r_hat, dev, a.strength)
o1 = model.model.layers[sj].self_attn.o_proj.weight.data.float()
o1 = text_layers(model)[sj].self_attn.o_proj.weight.data.float()
after = (r_hat.to(dev).float() @ o1).norm().item()
want = "collapse to ~0" if a.strength <= -0.999 else f"scale by {1 + a.strength:.2f}x"
verb = "orthogonalized" if a.strength < 0 else "amplified"
Expand Down
4 changes: 2 additions & 2 deletions tools/pollard_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ def scan_outliers(source_dir, device, calib, rows, cols, thresh):
"""Find massive-activation input channels per layer on the fp16 source (the low-bit break predictor)."""
import torch
from transformers import AutoTokenizer
from pollard_load import load_backbone
from pollard_load import load_backbone, text_layers
tok = AutoTokenizer.from_pretrained(source_dir)
model = load_backbone(source_dir, torch.float16, device_map=device)
try:
layers = model.model.layers
layers = text_layers(model)
except AttributeError:
print(" (risk-scan: unsupported arch -- expected model.model.layers)"); return []
amax = {}
Expand Down
12 changes: 6 additions & 6 deletions tools/pollard_gptq.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def sequential_gptq(model, calib, dev, bits, groupsize, act_order, offload=False
offload=True keeps the whole model on CPU and moves ONE block to `dev` at a time --
this is what lets a 7B (15 GB) quantize on a 16 GB GPU: peak VRAM is one block +
its Hessians, never the whole model."""
layers = model.model.layers
layers = text_layers(model)
# --- capture the input to block 0 (+ the kwargs each block needs) for every sample.
# A forward-PRE-hook avoids replacing the layer (so model-level attribute access like
# `.attention_type` still works) and stops the pass right before block 0 runs.
Expand Down Expand Up @@ -367,7 +367,7 @@ def main():
a = ap.parse_args()

from transformers import AutoTokenizer
from pollard_load import load_backbone
from pollard_load import load_backbone, text_layers
dev = a.device if (a.device != "mps" or torch.backends.mps.is_available()) else "cpu"
mdev = "cpu" if a.offload else dev # where the model itself lives
print(f"== pollard-gptq :: {a.model} W{a.bits}g{a.groupsize} dev={dev}"
Expand Down Expand Up @@ -411,13 +411,13 @@ def hook(mod, inp, out):
def run(method):
if fp16_state is not None:
model.load_state_dict(fp16_state)
lins = linear_layers(model.model.layers) # only the transformer-block linears
lins = linear_layers(text_layers(model)) # only the transformer-block linears
t0 = time.time()
if method in ("gptq-seq", "gptq-seq-ao"):
rec = make_recipe("aggr" if a.recipe == "aggr" else "handmix", a.ablate) if a.recipe != "none" else None
sequential_gptq(model, calib, dev, a.bits, a.groupsize,
act_order=method.endswith("-ao"), offload=a.offload, qmode=a.qmode,
recipe=rec, nlayers=len(model.model.layers))
recipe=rec, nlayers=len(text_layers(model)))
elif method in ("gptq", "gptq-ao"):
Hs = collect_hessians(lins) # {n: (Hessian, token count)}
ao = (method == "gptq-ao")
Expand Down Expand Up @@ -453,9 +453,9 @@ def run(method):
SYM = {"int": float(a.bits), "ternary": 1.585, "binary": 1.0}
if a.recipe != "none" and method in ("gptq-seq", "gptq-seq-ao"):
rec = make_recipe("aggr" if a.recipe == "aggr" else "handmix", a.ablate)
nl = len(model.model.layers)
nl = len(text_layers(model))
qbits = 0; qw = 0 # quantized layer weights
for n, m in model.model.layers.named_modules():
for n, m in text_layers(model).named_modules():
if isinstance(m, nn.Linear):
# recover (layer_idx, subname) from the full module path
idx = int(n.split(".")[0]); sub = ".".join(n.split(".")[1:])
Expand Down
4 changes: 2 additions & 2 deletions tools/pollard_hf_smooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ def main():
import pollard_workspace as ws
trc = ws.resolve_trust_remote_code(a.model, a.trust_remote_code)
from transformers import AutoTokenizer
from pollard_load import load_backbone
from pollard_load import load_backbone, text_layers
tok = AutoTokenizer.from_pretrained(a.model, trust_remote_code=trc)
model = load_backbone(a.model, torch.float16, device_map=a.device, trust_remote_code=trc)
try:
layers = model.model.layers
layers = text_layers(model)
except AttributeError:
sys.exit("ERROR: expected a decoder with model.model.layers (Llama/Qwen-style). "
"Add the seam map for this architecture.")
Expand Down
4 changes: 2 additions & 2 deletions tools/pollard_lowbit.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def main():
a = ap.parse_args()

from transformers import AutoTokenizer
from pollard_load import load_backbone
from pollard_load import load_backbone, text_layers
print(f"== pollard-lowbit :: {a.model} bits={a.bits} keep={a.keep} levels={a.levels}", flush=True)
tok = AutoTokenizer.from_pretrained(a.model)
model = load_backbone(a.model, torch.float16, a.device, eval_mode=False)
Expand All @@ -165,7 +165,7 @@ def main():

ppl_fp16 = eval_ppl(model, test); print(f"fp16 PPL: {ppl_fp16:.4f}", flush=True)
import copy; state = copy.deepcopy(model.state_dict())
lins = linears(model.model.layers)
lins = linears(text_layers(model))
print("collecting per-channel importance ...", flush=True)
imp = per_channel_importance(model, calib, lins)

Expand Down
4 changes: 2 additions & 2 deletions tools/pollard_palette.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def main():
a = ap.parse_args()

from transformers import AutoTokenizer
from pollard_load import load_backbone
from pollard_load import load_backbone, text_layers
dev = a.device if (a.device != "mps" or torch.backends.mps.is_available()) else "cpu"
print(f"== pollard-palette :: {a.model} gs={a.groupsize} dev={dev}", flush=True)
tok = AutoTokenizer.from_pretrained(a.model)
Expand All @@ -122,7 +122,7 @@ def main():

ppl_fp16 = eval_ppl(model, test); print(f"fp16 PPL: {ppl_fp16:.4f}", flush=True)
state = copy.deepcopy(model.state_dict())
lins = linear_layers(model.model.layers)
lins = linear_layers(text_layers(model))
print(f"collecting Hessians for {len(lins)} tensors ...", flush=True)
Hs = collect_hessians(model, calib, lins, dev)
probe_calib = calib[:a.probe_chunks]
Expand Down
6 changes: 3 additions & 3 deletions tools/pollard_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def _kl_vs(model, chunks, ref_logp, dev):

def _linears(model, layer, group):
parent, names = GROUP_ATTR[group]
mod = getattr(model.model.layers[layer], parent)
mod = getattr(text_layers(model)[layer], parent)
return [getattr(mod, n) for n in names if hasattr(mod, n)]


Expand Down Expand Up @@ -143,13 +143,13 @@ def main():
a = ap.parse_args()

from transformers import AutoTokenizer
from pollard_load import load_backbone
from pollard_load import load_backbone, text_layers
dev = a.device if (a.device != "mps" or torch.backends.mps.is_available()) else "cpu"
groups = [g.strip() for g in a.groups.split(",") if g.strip()]
print(f"== pollard-probe :: {a.model} probe={a.probe_bits}bit dev={dev}", flush=True)
tok = AutoTokenizer.from_pretrained(a.model)
model = load_backbone(a.model, torch.float16, dev)
layers = len(model.model.layers)
layers = len(text_layers(model))
ch = _chunks(tok, open(a.eval, encoding="utf-8").read(), a.seqlen, a.chunks)

if a.stream:
Expand Down
Loading