eyad-silx commited on
Commit
e50bd42
·
verified ·
1 Parent(s): 3d5a84d

push artifacts/scripts/qat.py

Browse files
Files changed (1) hide show
  1. artifacts/scripts/qat.py +212 -0
artifacts/scripts/qat.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """QAT on the validated surrogate: per layer, straight-through ternary codes + block scales + norm
2
+ gains, distilled toward teacher trajectories with the eval's own metric (KL + hidden-state
3
+ distance). Writes a new GGUF at --out.
4
+
5
+ .venv/bin/python qat.py --teacher /root/models/teacher --king /root/models/king-ix \
6
+ --data /root/train_samples.json --out /root/models/chal-v2 [--layers all|7]
7
+ """
8
+ import argparse
9
+ import json
10
+ import shutil
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, "/root/ingot/src")
15
+ import numpy as np
16
+ import torch
17
+ from transformers import AutoModelForCausalLM
18
+
19
+ from common.gguf import read_header
20
+ from common.gguf.dequant import hadamard, rotation
21
+ from common.gguf.types import PQ2_0, TYPES
22
+ from model_dedup.gguf_checkpoint import GgufCheckpoint, canonical
23
+
24
+ ap = argparse.ArgumentParser()
25
+ ap.add_argument("--teacher", default="/root/models/teacher")
26
+ ap.add_argument("--king", default="/root/models/king-ix")
27
+ ap.add_argument("--data", default="/root/train_samples.json")
28
+ ap.add_argument("--out", default="/root/models/chal-v2")
29
+ ap.add_argument("--layers", default="7,8,15,16,23,24,31,32,39,40,47,48,55,56,63")
30
+ ap.add_argument("--steps", type=int, default=25)
31
+ ap.add_argument("--lr", type=float, default=2e-3)
32
+ ap.add_argument("--alpha", type=float, default=1.0)
33
+ ap.add_argument("--seed", type=int, default=0)
34
+ args = ap.parse_args()
35
+
36
+ torch.manual_seed(args.seed)
37
+ ck = GgufCheckpoint(args.king, "cpu")
38
+ g = ck.g
39
+ hd = ck._heads
40
+ block_size = int(g.kv["prism.hadamard.block_size"].value)
41
+ H = torch.from_numpy(hadamard(block_size)).float()
42
+
43
+ # ---- training data
44
+ records = json.loads(Path(args.data).read_text())
45
+ print("training samples:", len(records))
46
+
47
+ # ---- the surrogate model and the king's state dict
48
+ model = AutoModelForCausalLM.from_pretrained(args.teacher, torch_dtype=torch.bfloat16, device_map="cuda")
49
+ model.eval()
50
+ sd_king = torch.load("/dev/shm/king_hf.pt", map_location="cpu", weights_only=True)
51
+
52
+ sys.path.insert(0, "/root/ingot/src")
53
+ from eval_service import metrics # noqa: E402
54
+ from eval_service.config import LAYERS as CAP_LAYERS # noqa: E402
55
+
56
+ heads = metrics.heads({"teacher": Path(args.teacher), "king": Path(args.king)}, ["cuda:0"])["cuda:0"]
57
+ head_t, head_k = heads["teacher"], heads["king"]
58
+
59
+ # ---- gguf byte io
60
+ src_gguf = Path(args.king) / "Ingot-IX-27B-Q2.gguf"
61
+ out_dir = Path(args.out)
62
+ out_dir.mkdir(parents=True, exist_ok=True)
63
+ dst = out_dir / "chal-v2.gguf"
64
+ shutil.copy(src_gguf, dst)
65
+
66
+
67
+ def gguf_bytes(tname):
68
+ t = g.tensors[tname]
69
+ n = int(np.prod(t.shape))
70
+ size = n // TYPES[t.ggml_type].block_elems * TYPES[t.ggml_type].block_bytes
71
+ return np.memmap(dst, np.uint8, "r+", g.data_start + t.offset, (size,))
72
+
73
+
74
+ def read_blocks(tname):
75
+ t = g.tensors[tname]
76
+ kind = TYPES[t.ggml_type]
77
+ n = int(np.prod(t.shape))
78
+ size = n // kind.block_elems * kind.block_bytes
79
+ raw = np.memmap(dst, np.uint8, "r", g.data_start + t.offset, (size,))
80
+ blocks = raw.reshape(-1, 34)
81
+ d = blocks[:, :2].copy().view("<f2").astype(np.float32)
82
+ qs = blocks[:, 2:]
83
+ codes = np.stack([(qs >> (2 * k)) & 3 for k in range(4)], axis=-1).reshape(-1, 128)
84
+ return codes, d
85
+
86
+
87
+ def unrotate(w, tname):
88
+ """get()'s transform: HF-layout tensor from the stored one. w: (rows, width)."""
89
+ folded = rotation(g, tname)
90
+ if folded is not None:
91
+ block, signs = folded
92
+ rows, width = w.shape
93
+ w = (w.reshape(rows, width // block, block) @ H).reshape(rows, width)
94
+ w = w * torch.from_numpy(signs).float()
95
+ return ck._hf_order(tname, w, folded is not None)
96
+
97
+
98
+ def hf_name(tname):
99
+ c = canonical(tname)
100
+ return c if c.startswith(("lm_head", "norm.")) else "model." + c
101
+
102
+
103
+ def hf_module(hf):
104
+ parts = hf.split(".")
105
+ m = model
106
+ for p in parts[:-1]:
107
+ m = getattr(m, p)
108
+ return m
109
+
110
+
111
+ def layer_tensors(il):
112
+ out = []
113
+ for kind in ("attn_qkv", "attn_gate", "ssm_alpha", "ssm_beta", "ssm_out", "attn_q", "attn_k", "attn_v", "attn_output", "ffn_gate", "ffn_up", "ffn_down"):
114
+ tname = f"blk.{il}.{kind}.weight"
115
+ if tname in g.tensors and g.tensors[tname].ggml_type == PQ2_0:
116
+ out.append(tname)
117
+ return out
118
+
119
+
120
+ def train_layer(il):
121
+ torch.cuda.empty_cache()
122
+ print(f"--- layer {il}")
123
+ tensors = layer_tensors(il)
124
+ zs, ss = {}, {}
125
+ for tname in tensors:
126
+ codes, d = read_blocks(tname)
127
+ t = g.tensors[tname]
128
+ rows, width = int(np.prod(t.shape) // t.shape[0]), t.shape[0]
129
+ nblk = rows * width // 128
130
+ zs[tname] = torch.tensor(codes, dtype=torch.float32, device="cuda", requires_grad=True).reshape(nblk, 128)
131
+ ss[tname] = torch.tensor(d, dtype=torch.float32, device="cuda", requires_grad=True).reshape(nblk)
132
+ hooks = []
133
+ for tname in tensors:
134
+ hf = hf_name(tname)
135
+ module = hf_module(hf)
136
+ t = g.tensors[tname]
137
+ rows, width = int(np.prod(t.shape) // t.shape[0]), t.shape[0]
138
+
139
+ def make_pre(mod, tname=tname, rows=rows, width=width):
140
+ def pre(mod, inp):
141
+ z, s = zs[tname], ss[tname]
142
+ c = torch.clamp(torch.round(z), 0, 2)
143
+ W = (s * (c - 1.0)).reshape(rows, width)
144
+ mod._orig_weight = mod.weight
145
+ mod.weight = unrotate(W, tname).bfloat16()
146
+ return pre
147
+
148
+ def make_post(mod):
149
+ def post(mod, inp, out):
150
+ mod.weight = mod._orig_weight
151
+ return post
152
+
153
+ module.register_forward_pre_hook(make_pre(module))
154
+ module.register_forward_hook(make_post(module))
155
+ hooks.append(module)
156
+
157
+ opt = torch.optim.Adam(list(zs.values()) + list(ss.values()), lr=args.lr)
158
+ for step in range(args.steps):
159
+ torch.cuda.empty_cache()
160
+ total = torch.zeros((), device="cuda")
161
+ n = 0
162
+ for rec in records:
163
+ tokens = rec["prompt"] + rec["answer"]
164
+ if len(tokens) > 2600:
165
+ continue
166
+ first = len(rec["prompt"]) - 1
167
+ ids = torch.tensor([tokens], device="cuda")
168
+ out = model(input_ids=ids, output_hidden_states=True, use_cache=False)
169
+ hs = [out.hidden_states[l][0, first:].float() for l in CAP_LAYERS]
170
+ h_k = torch.stack(hs, 1) # (rows, 8, 5120)
171
+ st = metrics.load_states(rec["states"], tokens[:-1], first).float().to("cuda")
172
+ m = min(h_k.shape[0], st.shape[0])
173
+ h_k, st = h_k[:m], st[:m]
174
+ lp_t = head_t.logprobs(st[:, -1])
175
+ lp_k = head_k.logprobs(h_k[:, -1])
176
+ kl = (lp_t.exp() * (lp_t - lp_k)).sum(-1)
177
+ a = st[1:]
178
+ b = h_k[1:]
179
+ dist = ((b - a).norm(dim=-1) / a.norm(dim=-1)).mean(1)
180
+ loss = kl.mean() + args.alpha * dist.mean()
181
+ opt.zero_grad()
182
+ loss.backward()
183
+ torch.nn.utils.clip_grad_norm_(list(zs.values()) + list(ss.values()), 1.0)
184
+ opt.step()
185
+ total += loss.detach()
186
+ n += 1
187
+ del out, hs, h_k, st, lp_t, lp_k
188
+ print(f" step {step}: loss {total.item() / n:.5f} over {n} samples", flush=True)
189
+
190
+ for tname in tensors:
191
+ z, s = zs[tname].detach().cpu().numpy(), ss[tname].detach().cpu().numpy()
192
+ c = np.clip(np.round(z), 0, 2).astype(np.uint8)
193
+ rows, width = int(np.prod(g.tensors[tname].shape) // g.tensors[tname].shape[0]), g.tensors[tname].shape[0]
194
+ nblk = rows * width // 128
195
+ raw = gguf_bytes(tname).reshape(-1, 34)
196
+ raw[:, :2] = s.astype(np.float16).reshape(-1, 1).astype("<f2").view(np.uint8).reshape(-1, 2)
197
+ qs = np.zeros((nblk, 32), np.uint8)
198
+ for j in range(128):
199
+ qs[:, j // 4] |= (c[:, j] << (2 * (j % 4))).astype(np.uint8)
200
+ raw[:, 2:] = qs
201
+ print(f" wrote {tname}", flush=True)
202
+ for mod in hooks:
203
+ mod._forward_pre_hooks.clear()
204
+ mod._forward_hooks.clear()
205
+ del zs, ss
206
+ torch.cuda.empty_cache()
207
+
208
+
209
+ for il in (int(x) for x in args.layers.split(",") if x):
210
+ train_layer(il)
211
+
212
+ print("saved", dst)