Akahsizrr commited on
Commit
2dc27e3
·
verified ·
1 Parent(s): 2fb5bce

Upload fuse2_qlora_infer.py

Browse files
Files changed (1) hide show
  1. fuse2_qlora_infer.py +250 -0
fuse2_qlora_infer.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Quick local inference for the QLoRA-merged Fuse-2 model.
2
+
3
+ Loads the model in 4-bit NF4 (fits in 12 GB VRAM), applies the runtime
4
+ patches (expert scale, SwiGLU clamp, router stability), and generates
5
+ text for a given prompt.
6
+ """
7
+ import os
8
+ import sys
9
+ import gc
10
+ import json
11
+ import time
12
+ import torch
13
+ import torch.nn as nn
14
+ import torch.nn.functional as F
15
+
16
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
17
+ sys.stderr.reconfigure(encoding='utf-8', errors='replace')
18
+
19
+ sys.path.insert(0, "E:/fuse1/mini-deepseek-v4-flash-qlora")
20
+
21
+ MODEL_PATH = "E:/fuse1/mini-deepseek-v4-flash-qlora"
22
+ DEVICE = "cuda:0"
23
+ SWIGLU_LIMIT = 10.0
24
+ TARGET_STD = 0.025
25
+
26
+ torch.backends.cuda.matmul.allow_tf32 = True
27
+ torch.backends.cudnn.benchmark = True
28
+
29
+
30
+ def load_model_4bit():
31
+ from transformers import AutoConfig, AutoTokenizer
32
+ from accelerate import init_empty_weights
33
+ from safetensors import safe_open
34
+ import bitsandbytes as bnb
35
+ import fuse2_model_local
36
+
37
+ print(f"[load] Config from {MODEL_PATH}...")
38
+ config = AutoConfig.from_pretrained(MODEL_PATH, trust_remote_code=True)
39
+
40
+ with init_empty_weights():
41
+ model = fuse2_model_local.Fuse2ForCausalLM(config)
42
+
43
+ with open(f"{MODEL_PATH}/model.safetensors.index.json") as f:
44
+ index = json.load(f)
45
+ weight_map = index["weight_map"]
46
+ shards = sorted(set(weight_map.values()))
47
+ param_names = set(dict(model.named_parameters()).keys())
48
+ buffer_names = set(dict(model.named_buffers()).keys())
49
+
50
+ tok = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
51
+ if tok.pad_token_id is None:
52
+ tok.pad_token_id = tok.eos_token_id
53
+
54
+ quantize_suffixes = (
55
+ "q_proj.weight", "k_proj.weight", "v_proj.weight", "o_proj.weight",
56
+ "gate_proj.weight", "up_proj.weight", "down_proj.weight",
57
+ )
58
+
59
+ def should_quantize(key):
60
+ return any(key.endswith(s) for s in quantize_suffixes)
61
+
62
+ def navigate_to_parent(model, key):
63
+ parts = key.split(".")
64
+ obj = model
65
+ for p in parts[:-1]:
66
+ if p.isdigit():
67
+ obj = obj[int(p)]
68
+ else:
69
+ obj = getattr(obj, p)
70
+ return obj, parts[-1]
71
+
72
+ def find_linear_owner(model, key):
73
+ parts = key.split(".")
74
+ obj = model
75
+ for p in parts[:-2]:
76
+ if p.isdigit():
77
+ obj = obj[int(p)]
78
+ else:
79
+ obj = getattr(obj, p)
80
+ return obj, parts[-2]
81
+
82
+ print(f"[load] Loading weights as 4-bit NF4...")
83
+ total_4bit = 0
84
+ total_bf16 = 0
85
+ replaced_linears = {}
86
+
87
+ for shard_name in shards:
88
+ shard_path = os.path.join(MODEL_PATH, shard_name)
89
+ shard_keys = [k for k, v in weight_map.items() if v == shard_name]
90
+ print(f" {shard_name} — {len(shard_keys)} tensors...", flush=True)
91
+
92
+ with safe_open(shard_path, framework="pt", device="cpu") as f:
93
+ for key in shard_keys:
94
+ if key not in param_names and key not in buffer_names:
95
+ continue
96
+ tensor = f.get_tensor(key)
97
+
98
+ if should_quantize(key) and key in param_names:
99
+ owner, linear_attr = find_linear_owner(model, key)
100
+ linear_id = id(getattr(owner, linear_attr))
101
+ if linear_id not in replaced_linears:
102
+ old_linear = getattr(owner, linear_attr)
103
+ in_f = old_linear.in_features
104
+ out_f = old_linear.out_features
105
+ has_bias = old_linear.bias is not None
106
+ new_linear = bnb.nn.Linear4bit(
107
+ in_f, out_f, bias=has_bias,
108
+ quant_type="nf4", compute_dtype=torch.bfloat16,
109
+ device=DEVICE,
110
+ )
111
+ t = tensor.to(torch.bfloat16)
112
+ new_linear.weight = bnb.nn.Params4bit(
113
+ t, requires_grad=False, quant_type="nf4",
114
+ ).cuda(0)
115
+ if has_bias:
116
+ new_linear.bias = None
117
+ setattr(owner, linear_attr, new_linear)
118
+ replaced_linears[id(new_linear)] = new_linear
119
+ del old_linear, new_linear, t
120
+ total_4bit += 1
121
+ else:
122
+ parent, param_name = navigate_to_parent(model, key)
123
+ t = tensor.to(torch.bfloat16).to(DEVICE)
124
+ parent._parameters[param_name] = nn.Parameter(t, requires_grad=False)
125
+ del t
126
+ total_bf16 += 1
127
+ del tensor
128
+ if (total_4bit + total_bf16) % 200 == 0:
129
+ gc.collect()
130
+ torch.cuda.empty_cache()
131
+
132
+ gc.collect()
133
+ torch.cuda.empty_cache()
134
+ vram = torch.cuda.memory_allocated(0) / 1e9
135
+ print(f" {total_4bit}q+{total_bf16}b, VRAM: {vram:.1f} GB", flush=True)
136
+
137
+ # Fix tied embeddings
138
+ if model.lm_head.weight.device.type == 'meta':
139
+ model.lm_head.weight = nn.Parameter(
140
+ model.model.embed_tokens.weight.data.clone(), requires_grad=False
141
+ )
142
+
143
+ # Init coding_gate and coding_norm
144
+ from fuse2_model_local import Fuse2AugmentedLayer
145
+ for layer in model.model.layers:
146
+ if not isinstance(layer, Fuse2AugmentedLayer):
147
+ continue
148
+ if hasattr(layer, 'coding_gate') and layer.coding_gate.device.type == 'meta':
149
+ layer.coding_gate = nn.Parameter(torch.tensor(-2.0, device=DEVICE))
150
+ if hasattr(layer, 'coding_norm') and layer.coding_norm.weight.device.type == 'meta':
151
+ layer.coding_norm = nn.RMSNorm(layer.coding_norm.weight.shape[0], eps=1e-6).to(DEVICE)
152
+
153
+ # Patch experts (SwiGLU clamp)
154
+ patched = 0
155
+ for layer in model.model.layers:
156
+ if not isinstance(layer, Fuse2AugmentedLayer):
157
+ continue
158
+ experts = getattr(layer, "experts", None)
159
+ if experts is None:
160
+ continue
161
+ for expert in experts:
162
+ gp, up, dp = expert.gate_proj, expert.up_proj, expert.down_proj
163
+ def make_fwd(g, u, d, lim):
164
+ def forward(x):
165
+ gate = F.silu(g(x))
166
+ val = torch.clamp(gate * u(x), -lim, lim)
167
+ return d(val)
168
+ return forward
169
+ expert.forward = make_fwd(gp, up, dp, SWIGLU_LIMIT)
170
+ patched += 1
171
+ print(f"[patch] {patched} experts patched with SwiGLU clamp")
172
+
173
+ # Router stability
174
+ for layer in model.model.layers:
175
+ if not isinstance(layer, Fuse2AugmentedLayer):
176
+ continue
177
+ router = layer.router
178
+ top_k = router.top_k
179
+ gate = router.gate
180
+ def make_stable_fwd(g, tk):
181
+ def forward(hidden_states):
182
+ logits = g(hidden_states)
183
+ scores = F.softplus(logits)
184
+ scores = torch.clamp(scores, min=1e-6)
185
+ scores = scores.sqrt()
186
+ topk_weights, topk_indices = scores.topk(tk, dim=-1)
187
+ topk_weights = topk_weights / (topk_weights.sum(dim=-1, keepdim=True) + 1e-8)
188
+ return topk_weights, topk_indices, logits
189
+ return forward
190
+ router.forward = make_stable_fwd(gate, top_k)
191
+ print(f"[patch] Router stability applied")
192
+
193
+ model.set_coding_enabled(True)
194
+ model.to(DEVICE)
195
+ model.eval()
196
+
197
+ vram = torch.cuda.memory_allocated(0) / 1e9
198
+ print(f"[load] Ready, VRAM: {vram:.1f} GB")
199
+ return model, tok
200
+
201
+
202
+ def generate(model, tok, prompt, max_new_tokens=1024, temperature=0.6, repetition_penalty=1.1):
203
+ """Generate text from a prompt."""
204
+ # Format as chat
205
+ messages = [{"role": "user", "content": prompt}]
206
+ text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
207
+ print(f"[gen] Prompt: {prompt[:100]}...")
208
+ print(f"[gen] Max tokens: {max_new_tokens}, temp: {temperature}")
209
+ print(f"[gen] Generating...", flush=True)
210
+
211
+ inputs = tok(text, return_tensors="pt").to(DEVICE)
212
+ input_len = inputs["input_ids"].shape[1]
213
+
214
+ t0 = time.time()
215
+ with torch.no_grad():
216
+ outputs = model.generate(
217
+ **inputs,
218
+ max_new_tokens=max_new_tokens,
219
+ temperature=temperature,
220
+ do_sample=temperature > 0,
221
+ repetition_penalty=repetition_penalty,
222
+ pad_token_id=tok.pad_token_id,
223
+ use_cache=True,
224
+ )
225
+ t1 = time.time()
226
+
227
+ generated = tok.decode(outputs[0][input_len:], skip_special_tokens=True)
228
+ elapsed = t1 - t0
229
+ tok_count = outputs.shape[1] - input_len
230
+ tps = tok_count / elapsed
231
+
232
+ print(f"[gen] {tok_count} tokens in {elapsed:.1f}s ({tps:.1f} tok/s)")
233
+ return generated
234
+
235
+
236
+ if __name__ == "__main__":
237
+ prompt = sys.argv[1] if len(sys.argv) > 1 else "Write a Python fizzbuzz."
238
+
239
+ model, tok = load_model_4bit()
240
+
241
+ print("\n" + "=" * 60)
242
+ print("GENERATING")
243
+ print("=" * 60)
244
+
245
+ result = generate(model, tok, prompt, max_new_tokens=2048, temperature=0.6)
246
+
247
+ print("\n" + "=" * 60)
248
+ print("OUTPUT")
249
+ print("=" * 60)
250
+ print(result)