xbruce22 commited on
Commit
8a1cd7d
·
verified ·
1 Parent(s): 969b018

Upload chat.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. chat.py +148 -0
chat.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Interactive chat with the fine-tuned Gemma4-E2B reasoning model (base +
2
+ LoRA adapter) — streaming output.
3
+
4
+ Repo: xbruce22/gemma-4-e2b-reasoning-lora
5
+ Base: unsloth/gemma-4-E2B-it
6
+
7
+ Auto-detects the accelerator (CUDA / Intel XPU / CPU). Loads the base model,
8
+ applies this LoRA adapter, merges it for fast inference, and runs a
9
+ multi-turn chat using the Gemma4 chat template with thinking ON — the model
10
+ emits a <|channel>thought ... <channel|> reasoning block (concise bullets,
11
+ as it was trained) before the final answer.
12
+
13
+ Install:
14
+ pip install torch transformers peft
15
+
16
+ Run:
17
+ python chat.py
18
+ python chat.py --repo xbruce22/gemma-4-e2b-reasoning-lora
19
+ python chat.py --device cpu # force CPU
20
+
21
+ In-chat commands:
22
+ /q quit /reset clear history
23
+ /raw toggle raw output (show <|channel>/<channel|>/<turn|> markers)
24
+ /think toggle thinking on/off (default ON)
25
+ """
26
+ import argparse
27
+ import torch
28
+ from transformers import AutoModelForCausalLM, AutoProcessor, TextStreamer
29
+ from peft import PeftModel
30
+
31
+ DEFAULT_REPO = "xbruce22/gemma-4-e2b-reasoning-lora"
32
+ BASE_MODEL = "unsloth/gemma-4-E2B-it"
33
+
34
+ # Build special-token strings from chr() so this source file never contains
35
+ # literal angle-bracket markers (avoids editor/toolchain mangling).
36
+ CHAN_OPEN = chr(60) + "|channel>thought" + chr(10)
37
+ CHAN_CLOSE = chr(60) + "channel|" + chr(62)
38
+ TURN_END = chr(60) + "turn|" + chr(62)
39
+ THINK = chr(60) + "|think|" + chr(62)
40
+
41
+
42
+ def pick_device():
43
+ if torch.cuda.is_available():
44
+ return "cuda", torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
45
+ if hasattr(torch, "xpu") and torch.xpu.is_available():
46
+ return "xpu", torch.bfloat16
47
+ return "cpu", torch.float32
48
+
49
+
50
+ def clean_display(text):
51
+ if CHAN_OPEN in text and CHAN_CLOSE in text:
52
+ _, _, rest = text.partition(CHAN_OPEN)
53
+ thought, _, answer = rest.partition(CHAN_CLOSE)
54
+ return ("\n── thinking ──\n" + thought.strip() +
55
+ "\n── answer ──\n" + answer.strip())
56
+ for m in (TURN_END, THINK):
57
+ text = text.replace(m, "")
58
+ return text.strip()
59
+
60
+
61
+ def main():
62
+ ap = argparse.ArgumentParser()
63
+ ap.add_argument("--repo", default=DEFAULT_REPO,
64
+ help="HF repo id of the LoRA adapter")
65
+ ap.add_argument("--device", default=None,
66
+ help="force device: cuda | xpu | cpu")
67
+ args = ap.parse_args()
68
+
69
+ device, dtype = pick_device() if args.device is None else (args.device, torch.float32)
70
+ print(f"device={device} dtype={dtype}")
71
+
72
+ print("Loading processor...")
73
+ processor = AutoProcessor.from_pretrained(BASE_MODEL)
74
+ tokenizer = processor.tokenizer
75
+ if tokenizer.pad_token_id is None:
76
+ tokenizer.pad_token = tokenizer.eos_token
77
+
78
+ print(f"Loading base model {BASE_MODEL} ...")
79
+ base = AutoModelForCausalLM.from_pretrained(BASE_MODEL, dtype=dtype)
80
+ base = base.to(device)
81
+ base.config.use_cache = True
82
+ base.eval()
83
+
84
+ print(f"Applying + merging LoRA adapter {args.repo} ...")
85
+ model = PeftModel.from_pretrained(base, args.repo)
86
+ model = model.merge_and_unload()
87
+ model.eval()
88
+ print("Ready.\n")
89
+
90
+ show_raw = False
91
+ thinking = True
92
+ messages = [{"role": "system", "content": "You are a helpful, concise assistant."}]
93
+
94
+ print(f"Chat ready. /q quit · /reset · /raw · /think "
95
+ f"(thinking={'ON' if thinking else 'OFF'})\n")
96
+
97
+ while True:
98
+ try:
99
+ user = input("you> ").strip()
100
+ except (EOFError, KeyboardInterrupt):
101
+ print("\nbye."); break
102
+ if not user:
103
+ continue
104
+ if user == "/q":
105
+ print("bye."); break
106
+ if user == "/reset":
107
+ messages = [{"role": "system", "content": "You are a helpful, concise assistant."}]
108
+ print("(reset)\n"); continue
109
+ if user == "/raw":
110
+ show_raw = not show_raw
111
+ print(f"(display={'raw' if show_raw else 'clean'})\n"); continue
112
+ if user == "/think":
113
+ thinking = not thinking
114
+ print(f"(thinking={'ON' if thinking else 'OFF'})\n"); continue
115
+
116
+ messages.append({"role": "user", "content": user})
117
+ try:
118
+ text = processor.apply_chat_template(
119
+ messages, tokenize=False, add_generation_prompt=True,
120
+ enable_thinking=thinking)
121
+ except TypeError:
122
+ text = processor.apply_chat_template(
123
+ messages, tokenize=False, add_generation_prompt=True)
124
+
125
+ inputs = processor(text=[text], return_tensors="pt").to(device)
126
+ for k in list(inputs.keys()):
127
+ if "token_type" in k or "pixel" in k or "audio" in k:
128
+ inputs.pop(k)
129
+
130
+ print("model> ", end="", flush=True)
131
+ streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
132
+ with torch.inference_mode():
133
+ out_ids = model.generate(
134
+ **inputs, max_new_tokens=2048, do_sample=True,
135
+ temperature=1.0, top_p=0.95, top_k=64,
136
+ pad_token_id=tokenizer.pad_token_id, streamer=streamer)
137
+ gen_ids = out_ids[0][inputs["input_ids"].shape[1]:]
138
+ gen_text = tokenizer.decode(gen_ids, skip_special_tokens=False)
139
+ messages.append({"role": "assistant",
140
+ "content": tokenizer.decode(gen_ids, skip_special_tokens=True)})
141
+ print()
142
+ if show_raw:
143
+ print("--- raw ---"); print(gen_text); print("--- end raw ---")
144
+ print()
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()