chat_jirack_14b.py
7.5 KB · 189 lines · python Raw
1 # ==============================================================================
2 # JiRack 14B Chat (DeepSeek-R1-Distill-Qwen-14B edition, extended tokenizer)
3 # COPYRIGHT (c) 2026 Konstantin Vladimirovich Grabko.
4 #
5 # Mirrors chat_jirack_32b.py, adjusted for the 14B checkpoint:
6 # * VOCAB_SIZE=152064, hidden=5120, 48 layers (official config.json)
7 # * Remember the MKL SIMD dispatch fix if you ever run this on CPU:
8 # export MKL_ENABLE_INSTRUCTIONS=AVX
9 # export MKL_DEBUG_CPU_TYPE=5
10 # (this CPU only exposes AVX, no AVX2/AVX512 -- MKL crashes with SIGILL
11 # otherwise). On GPU this is not needed.
12 # * 14B is moderately heavy: bf16 -> ~29.5GB just
13 # for weights) before loading on CUDA, or run on CPU with the env vars
14 # above (slow, and considerably slower per token than the 1.5B model).
15 # ==============================================================================
16
17 import os
18 import sys
19 import torch
20 from transformers import AutoTokenizer
21
22 sys.path.append(os.getcwd())
23 from JiRackTernaryUltra_14b import JiRackTransformer, JiRackConfig
24
25 # ========================= EDIT THESE =========================
26 MODEL_PATH = "/mnt/nfs_share/DeepSeek_14b/ds14b_checkpoint_migrated.pt"
27 TOKENIZER_DIR = "/mnt/nfs_share/DeepSeek_14b" # your extended tokenizer folder
28 NO_THINK = True # True = skip <think> reasoning, answer directly
29 # ================================================================
30
31
32 def load_model(model_path: str):
33 device = "cuda" if torch.cuda.is_available() else "cpu"
34 print(f"🚀 Загрузка модели на устройство: {device.upper()}")
35
36 config = JiRackConfig()
37 model = JiRackTransformer(config, use_checkpoint=False)
38
39 print(f"📥 Загрузка весов из {model_path}...")
40 try:
41 ckpt = torch.load(model_path, map_location="cpu", weights_only=False)
42 state_dict = ckpt["model"] if isinstance(ckpt, dict) and "model" in ckpt else ckpt
43
44 missing, unexpected = model.load_state_dict(state_dict, strict=False)
45 real_missing = [k for k in missing if not k.endswith("lambda_")]
46 if real_missing:
47 print(f"⚠️ Пропущено ключей: {len(real_missing)} -> {real_missing[:10]}")
48 if unexpected:
49 print(f"⚠️ Лишние ключи: {len(unexpected)} -> {unexpected[:10]}")
50 except Exception as e:
51 print(f"❌ Критическая ошибка при загрузке весов: {e}")
52 sys.exit(1)
53
54 model = model.to(dtype=torch.bfloat16, device=device).eval()
55 model.set_lambda(0.0) # full-precision fast path, no fake-quant at inference
56
57 if device == "cuda":
58 vram = torch.cuda.memory_allocated(0) / 1024**3
59 print(f"✅ VRAM занято: {vram:.1f} GB")
60 else:
61 print("⚠️ ВНИМАНИЕ: Запуск 14B на CPU будет ОЧЕНЬ медленным.")
62 print(" Проверь, что выставлены MKL_ENABLE_INSTRUCTIONS=AVX и")
63 print(" MKL_DEBUG_CPU_TYPE=5 перед запуском (см. комментарий в шапке файла).")
64
65 print("✅ Модель успешно загружена.")
66 return model, device
67
68
69 @torch.no_grad()
70 def generate_text(model, tokenizer, input_ids, stop_tokens, max_new_tokens=512, device="cuda"):
71 curr_ids = input_ids.to(device)
72 prompt_len = curr_ids.shape[1]
73 printed = ""
74
75 temperature = 0.6
76 top_p = 0.95
77 repetition_penalty = 1.15
78
79 print("JiRack: ", end="", flush=True)
80
81 for _ in range(max_new_tokens):
82 with torch.autocast(device_type=("cuda" if device == "cuda" else "cpu"), dtype=torch.bfloat16):
83 logits = model(curr_ids)
84 next_token_logits = logits[:, -1, :].float() / temperature
85
86 for token_id in set(curr_ids[0].tolist()):
87 if next_token_logits[0, token_id] < 0:
88 next_token_logits[0, token_id] *= repetition_penalty
89 else:
90 next_token_logits[0, token_id] /= repetition_penalty
91
92 sorted_logits, sorted_indices = torch.sort(next_token_logits, descending=True)
93 cumulative_probs = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1)
94 sorted_indices_to_remove = cumulative_probs > top_p
95 sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
96 sorted_indices_to_remove[..., 0] = 0
97
98 next_token_logits[0, sorted_indices[sorted_indices_to_remove]] = -float('Inf')
99 probs = torch.softmax(next_token_logits, dim=-1)
100 next_token = torch.multinomial(probs, num_samples=1)
101
102 curr_ids = torch.cat([curr_ids, next_token], dim=1)
103 # decode the whole generated tail each step and print only the new part;
104 # keeps multi-token UTF-8 chars (emoji etc.) intact instead of \ufffd
105 decoded = tokenizer.decode(curr_ids[0, prompt_len:], skip_special_tokens=True)
106 if not decoded.endswith("\ufffd"):
107 print(decoded[len(printed):], end="", flush=True)
108 printed = decoded
109
110 if next_token.item() in stop_tokens:
111 break
112
113 print("\n")
114 return curr_ids
115
116
117 def main():
118 if not os.path.exists(MODEL_PATH):
119 print(f"❌ Файл {MODEL_PATH} не найден!")
120 return
121
122 try:
123 tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_DIR)
124 except Exception as e:
125 print(f"❌ Ошибка токенайзера: {e}")
126 return
127
128 model, device = load_model(MODEL_PATH)
129
130 stop_tokens = set()
131 if tokenizer.eos_token_id is not None:
132 stop_tokens.add(tokenizer.eos_token_id)
133 for name in ("<|end_of_sentence|>", "<|endoftext|>", "<|im_end|>"):
134 tid = tokenizer.convert_tokens_to_ids(name)
135 if tid is not None and tid != tokenizer.unk_token_id:
136 stop_tokens.add(tid)
137
138 print("\n" + "=" * 80)
139 print("✅ JiRack 14B (DeepSeek-R1-Distill-Qwen, extended tokenizer) Ready")
140 print("=" * 80 + "\n")
141
142 history = []
143
144 while True:
145 try:
146 user_input = input("User: ")
147 if user_input.lower() in ["exit", "quit", "q"]:
148 break
149 if not user_input.strip():
150 continue
151
152 history.append({"role": "user", "content": user_input})
153
154 input_ids = tokenizer.apply_chat_template(
155 history,
156 add_generation_prompt=True,
157 return_tensors="pt",
158 return_dict=False,
159 )
160 # some transformers versions return a BatchEncoding here regardless;
161 # unwrap it defensively so we always end up with a plain tensor
162 if not torch.is_tensor(input_ids):
163 input_ids = input_ids["input_ids"]
164
165 # NO_THINK: chat_template ends with '<|Assistant|><think>\n'.
166 # Appending '</think>\n\n' makes the model skip reasoning and
167 # answer directly (standard trick for R1-distill models).
168 if NO_THINK:
169 close_ids = tokenizer.encode("</think>\n\n", add_special_tokens=False, return_tensors="pt")
170 input_ids = torch.cat([input_ids, close_ids], dim=1)
171
172 curr_ids = generate_text(model, tokenizer, input_ids, stop_tokens, device=device)
173
174 new_tokens = curr_ids[0, input_ids.shape[1]:]
175 reply = tokenizer.decode(new_tokens, skip_special_tokens=True)
176 history.append({"role": "assistant", "content": reply})
177
178 except KeyboardInterrupt:
179 print("\nStopped.")
180 break
181 except Exception:
182 import traceback
183 print("\n❌ Ошибка:")
184 traceback.print_exc()
185
186
187 if __name__ == "__main__":
188 main()
189