Bisect: base+adapter vs merged, to locate the escaped-newline defect
Browse files- bisect_adapter.py +79 -0
bisect_adapter.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# /// script
|
| 2 |
+
# requires-python = ">=3.10"
|
| 3 |
+
# dependencies = [
|
| 4 |
+
# "torch",
|
| 5 |
+
# "transformers[torch]",
|
| 6 |
+
# "peft",
|
| 7 |
+
# "huggingface_hub",
|
| 8 |
+
# "bitsandbytes",
|
| 9 |
+
# "accelerate",
|
| 10 |
+
# ]
|
| 11 |
+
# ///
|
| 12 |
+
"""Bisect: base+adapter (no merge) vs merged checkpoint.
|
| 13 |
+
|
| 14 |
+
If base+adapter emits real newlines, the MERGE is what breaks decoding.
|
| 15 |
+
If it also emits literal backslash-n, the ADAPTER (i.e. the training) is at fault.
|
| 16 |
+
"""
|
| 17 |
+
import os
|
| 18 |
+
import re
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from peft import PeftModel
|
| 23 |
+
from transformers import (AutoModelForCausalLM, AutoTokenizer,
|
| 24 |
+
BitsAndBytesConfig)
|
| 25 |
+
|
| 26 |
+
BASE = "unsloth/Qwen3-Coder-30B-A3B-Instruct"
|
| 27 |
+
ADAPTER = "Taimwe/securecoder-30b-pro-v2"
|
| 28 |
+
|
| 29 |
+
PROMPTS = [
|
| 30 |
+
"Write a Python function `def is_palindrome(s: str) -> bool:` that checks whether a string reads the same forwards and backwards, ignoring case and non-alphanumeric characters.",
|
| 31 |
+
"Write a Python function `def two_sum(nums: list[int], target: int) -> list[int]:` returning the indices of the two numbers adding to target, or an empty list.",
|
| 32 |
+
"Write a Python function `def flatten(nested: list) -> list:` that flattens an arbitrarily nested list.",
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
token = os.environ.get("HF_TOKEN")
|
| 36 |
+
tok = AutoTokenizer.from_pretrained(BASE, token=token)
|
| 37 |
+
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
|
| 38 |
+
bnb_4bit_compute_dtype=torch.bfloat16,
|
| 39 |
+
bnb_4bit_use_double_quant=True)
|
| 40 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 41 |
+
BASE, token=token, quantization_config=bnb,
|
| 42 |
+
dtype=torch.bfloat16, device_map="auto")
|
| 43 |
+
model = PeftModel.from_pretrained(model, ADAPTER, token=token)
|
| 44 |
+
model.eval()
|
| 45 |
+
|
| 46 |
+
ok = 0
|
| 47 |
+
for i, p in enumerate(PROMPTS, 1):
|
| 48 |
+
text = tok.apply_chat_template([{"role": "user", "content": p}],
|
| 49 |
+
tokenize=False, add_generation_prompt=True)
|
| 50 |
+
ids = tok(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
|
| 51 |
+
with torch.no_grad():
|
| 52 |
+
gen = model.generate(ids, max_new_tokens=1024, do_sample=False,
|
| 53 |
+
eos_token_id=tok.eos_token_id, pad_token_id=tok.eos_token_id)
|
| 54 |
+
new_ids = gen[0, ids.shape[-1]:]
|
| 55 |
+
reply = tok.decode(new_ids, skip_special_tokens=True)
|
| 56 |
+
|
| 57 |
+
has_real_nl = "\n" in reply
|
| 58 |
+
has_lit = "\\n" in reply
|
| 59 |
+
blocks = re.findall(r"```(?:python)?\s*\n(.*?)```", reply, re.S)
|
| 60 |
+
block = max(blocks, key=len) if blocks else None
|
| 61 |
+
if block is None:
|
| 62 |
+
s = reply.find("def ")
|
| 63 |
+
block = reply[s:] if s >= 0 else None
|
| 64 |
+
err = None
|
| 65 |
+
if block:
|
| 66 |
+
try:
|
| 67 |
+
compile(block, "<t>", "exec")
|
| 68 |
+
except Exception as exc:
|
| 69 |
+
err = "%s: %s" % (type(exc).__name__, exc)
|
| 70 |
+
if not err:
|
| 71 |
+
ok += 1
|
| 72 |
+
print("\n" + "=" * 72)
|
| 73 |
+
print("PROMPT %d new_tokens=%d fenced=%d real_nl=%s literal_backslash_n=%s"
|
| 74 |
+
% (i, len(new_ids), len(blocks), has_real_nl, has_lit))
|
| 75 |
+
print("COMPILE:", err or "OK")
|
| 76 |
+
print("-" * 72)
|
| 77 |
+
print(reply[:1200])
|
| 78 |
+
|
| 79 |
+
print("\n\nSUMMARY base+adapter: %d/%d compiled cleanly" % (ok, len(PROMPTS)))
|