Views
No views yet

pip install "transformers>=4.57.0" accelerate1# ============================================================
2# TEXT-ONLY INFERENCE example
3# ============================================================
4
5import torch
6from transformers import AutoTokenizer, AutoModelForCausalLM
7
8MODEL_ID = "aisingapore/Qwen-SEA-LION-v4.5-27B-IT"
9
10# ── Load tokenizer ──
11tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
12
13# ── Load model in bfloat16 across all available GPUs ──
14# attn_implementation="sdpa" is safer for the hybrid DeltaNet arch;
15# flash_attention_2 compatibility depends on your transformers version
16model = AutoModelForCausalLM.from_pretrained(
17 MODEL_ID,
18 torch_dtype=torch.bfloat16,
19 device_map="auto",
20 attn_implementation="sdpa", # use sdpa for DeltaNet hybrid layers
21)
22
23# ── Message: text-only, same Malay query from original snippet ──
24messages = [
25 {
26 "role": "user",
27 "content": "Tolong carikan flat 4-bilik dekat Tampines, bajet bawah $500,000. "
28 "Nak tahu juga berapa anggaran pinjaman bulanan."
29 }
30]
31
32# ── Apply chat template — text-only, thinking disabled ──
33# enable_thinking=False → instruct/non-thinking mode
34# Qwen3.6 does NOT support /no_think soft switch unlike Qwen3
35text = tokenizer.apply_chat_template(
36 messages,
37 tokenize=False,
38 add_generation_prompt=True,
39 enable_thinking=False, # hard-disable CoT thinking blocks
40)
41
42# ── Tokenize ──
43inputs = tokenizer(text, return_tensors="pt").to(model.device)
44
45# ── Generate — non-thinking mode params ──
46# presence_penalty=1.5 is important for Qwen3.6 non-thinking mode
47# to suppress repetition; not available in model.generate() directly,
48# so use do_sample=True with the temperature/top_p/top_k trio
49generated_ids = model.generate(
50 **inputs,
51 max_new_tokens=512,
52 do_sample=True,
53 temperature=0.7, # non-thinking instruct mode
54 top_p=0.80,
55 top_k=20,
56 # Note: presence_penalty requires vLLM/SGLang for full effect;
57 # in transformers use repetition_penalty as a proxy
58 repetition_penalty=1.1,
59)
60
61# ── Decode only newly generated tokens ──
62output_ids = generated_ids[0][inputs["input_ids"].shape[1]:]
63response = tokenizer.decode(output_ids, skip_special_tokens=True).strip()
64print(response)1# ============================================================
2# TOOL CALLING (Transformers, local)
3# ============================================================
4
5import torch
6from transformers import AutoTokenizer, AutoModelForCausalLM
7
8MODEL_ID = "aisingapore/Qwen-SEA-LION-v4.5-27B-IT"
9
10tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
11
12model = AutoModelForCausalLM.from_pretrained(
13 MODEL_ID,
14 torch_dtype=torch.bfloat16,
15 device_map="auto",
16 attn_implementation="sdpa",
17)
18
19messages = [
20 {
21 "role": "user",
22 "content": "Tolong carikan flat 4-bilik dekat Tampines, bajet bawah $500,000. "
23 "Nak tahu juga berapa anggaran pinjaman bulanan."
24 }
25]
26
27tools = [
28 {
29 "type": "function",
30 "function": {
31 "name": "search_hdb_listings",
32 "description": "Search for HDB flats available for sale",
33 "parameters": {
34 "type": "object",
35 "properties": {
36 "location": {
37 "type": "string",
38 "description": "Town or area name"
39 },
40 "flat_type": {
41 "type": "string",
42 "description": "Flat type e.g. 3-room, 4-room, 5-room"
43 },
44 "max_price": {
45 "type": "number",
46 "description": "Maximum price in SGD"
47 }
48 },
49 "required": ["location", "flat_type"]
50 }
51 }
52 },
53 {
54 "type": "function",
55 "function": {
56 "name": "calculate_mortgage",
57 "description": "Calculate estimated monthly mortgage payment",
58 "parameters": {
59 "type": "object",
60 "properties": {
61 "loan_amount": {
62 "type": "number",
63 "description": "Loan amount in SGD"
64 },
65 "interest_rate": {
66 "type": "number",
67 "description": "Annual interest rate as percentage"
68 },
69 "loan_tenure_years": {
70 "type": "integer",
71 "description": "Loan period in years"
72 }
73 },
74 "required": ["loan_amount"]
75 }
76 }
77 }
78]
79
80# ============================================================
81# apply_chat_template returns BatchEncoding with keys:
82# input_ids, attention_mask (and sometimes token_type_ids)
83# ============================================================
84
85inputs = tokenizer.apply_chat_template(
86 messages,
87 tools=tools,
88 return_tensors="pt",
89 return_dict=True, # ← returns BatchEncoding dict with attention_mask
90 add_generation_prompt=True,
91 enable_thinking=False, # disable CoT for structured tool call output
92).to(model.device)
93
94# ── Unpack BatchEncoding dict with ** — fixes the AttributeError ──
95generated_ids = model.generate(
96 **inputs, # ← unpack: passes input_ids + attention_mask
97 max_new_tokens=512,
98 do_sample=False,
99)
100
101# ── Decode only new tokens — slice off the prompt portion ──
102output_ids = generated_ids[0][inputs["input_ids"].shape[1]:]
103response = tokenizer.decode(output_ids, skip_special_tokens=True).strip()
104print(response)
1051# ============================================================
2# NO-VLLM AGENTIC LOOP
3# ============================================================
4
5import os
6import json
7import re
8import torch
9from transformers import AutoTokenizer, AutoModelForCausalLM
10from dotenv import load_dotenv
11
12load_dotenv()
13
14MODEL_ID = "aisingapore/Qwen-SEA-LION-v4.5-27B-IT"
15
16print("Loading tokenizer...")
17tokenizer = AutoTokenizer.from_pretrained(
18 MODEL_ID,
19 token=os.getenv("HF_TOKEN"),
20)
21
22print("Loading model across GPUs...")
23model = AutoModelForCausalLM.from_pretrained(
24 MODEL_ID,
25 token=os.getenv("HF_TOKEN"),
26 torch_dtype=torch.bfloat16,
27 device_map="auto",
28 attn_implementation="sdpa",
29)
30
31device_info = getattr(model, "hf_device_map", None) or str(model.device)
32print(f"Model loaded. Device: {device_info}")
33
34TOOLS = [
35 {
36 "type": "function",
37 "function": {
38 "name": "search_hdb_listings",
39 "description": "Search for HDB flats available for sale",
40 "parameters": {
41 "type": "object",
42 "properties": {
43 "location": {"type": "string", "description": "Town or area name"},
44 "flat_type": {"type": "string", "description": "e.g. 4-room"},
45 "max_price": {"type": "number", "description": "Max price in SGD"},
46 },
47 "required": ["location", "flat_type"],
48 },
49 },
50 },
51 {
52 "type": "function",
53 "function": {
54 "name": "calculate_mortgage",
55 "description": "Calculate estimated monthly mortgage payment",
56 "parameters": {
57 "type": "object",
58 "properties": {
59 "loan_amount": {"type": "number", "description": "Loan amount SGD"},
60 "interest_rate": {"type": "number", "description": "Annual rate %"},
61 "loan_tenure_years": {"type": "integer", "description": "Loan years"},
62 },
63 "required": ["loan_amount"],
64 },
65 },
66 },
67]
68
69def execute_tool(name: str, arguments: dict) -> str:
70 """Mock tool executor — replace with real API calls."""
71 if name == "search_hdb_listings":
72 return json.dumps({
73 "listings": [
74 {
75 "address": "Blk 472 Tampines St 43",
76 "flat_type": arguments.get("flat_type"),
77 "resale_price": 488000,
78 "floor_area_sqm": 93,
79 "remaining_lease": "67 years",
80 },
81 {
82 "address": "Blk 512 Tampines Ave 4",
83 "flat_type": arguments.get("flat_type"),
84 "resale_price": 475000,
85 "floor_area_sqm": 89,
86 "remaining_lease": "62 years",
87 },
88 ]
89 })
90 elif name == "calculate_mortgage":
91 principal = arguments["loan_amount"]
92 r = (arguments.get("interest_rate", 2.6) / 100) / 12
93 n = arguments.get("loan_tenure_years", 25) * 12
94 monthly = principal * (r * (1 + r) ** n) / ((1 + r) ** n - 1)
95 return json.dumps({
96 "loan_amount": principal,
97 "monthly_repayment_sgd": round(monthly, 2),
98 })
99 return json.dumps({"error": f"Unknown tool: {name}"})
100
101def generate_response(messages: list) -> str:
102 """
103 Single model.generate() call.
104 Returns the raw decoded string (may contain tool call JSON).
105 """
106 # ── Render chat template to string first ──
107 text = tokenizer.apply_chat_template(
108 messages,
109 tools=TOOLS,
110 tokenize=False,
111 add_generation_prompt=True,
112 enable_thinking=False, # no blocks for tool calling
113 )
114
115 # ── Tokenize separately ──
116 inputs = tokenizer(text, return_tensors="pt").to(model.device)
117
118 # ── Generate ──
119 with torch.no_grad(): # saves memory during inference
120 generated_ids = model.generate(
121 **inputs,
122 max_new_tokens=512,
123 do_sample=False, # greedy for deterministic tool JSON
124 )
125
126 # ── Decode new tokens only ──
127 output_ids = generated_ids[0][inputs["input_ids"].shape[1]:]
128 return tokenizer.decode(output_ids, skip_special_tokens=True).strip()
129
130def parse_tool_calls(response_text: str) -> list:
131 """
132 Parse Hermes-style tool call JSON from model output.
133 Qwen3.6 emits tool calls wrapped in ... tags.
134 Returns list of {"name": ..., "arguments": {...}} dicts.
135 Falls back to empty list if no tool calls found.
136 """
137 import re
138 tool_calls = []
139
140 # ── Match {...} blocks ──
141 pattern = r"(.*?)"
142 matches = re.findall(pattern, response_text, re.DOTALL)
143
144 for match in matches:
145 try:
146 call = json.loads(match.strip())
147 tool_calls.append(call)
148 except json.JSONDecodeError:
149 print(f" [WARN] Could not parse tool call JSON: {match[:100]}")
150
151 return tool_calls
152
153def run_agent(user_query: str, max_steps: int = 10) -> str:
154 """
155 Transformers-native agentic loop — no vLLM or API server needed.
156
157 Loop:
158 1. Generate response
159 2. Parse tool calls from output
160 3. Execute tools, append results
161 4. Repeat until no tool calls in response
162 """
163 messages = [
164 {
165 "role": "system",
166 "content": (
167 "You are a helpful Singapore housing assistant. "
168 "Always call the relevant tools to get accurate data before answering. "
169 "Give a clear, concise summary after gathering all information."
170 ),
171 },
172 {"role": "user", "content": user_query},
173 ]
174
175 print(f"\n{'='*60}")
176 print(f"USER: {user_query}")
177 print(f"{'='*60}")
178
179 for step in range(max_steps):
180 print(f"\n[Step {step + 1}] Generating...")
181
182 response_text = generate_response(messages)
183 print(f" Raw output: {response_text[:200]}...")
184
185 # ── Try to parse tool calls from the response ──
186 tool_calls = parse_tool_calls(response_text)
187
188 if tool_calls:
189 print(f" → Found {len(tool_calls)} tool call(s)")
190
191 # ── Append assistant turn with raw response ──
192 messages.append({
193 "role": "assistant",
194 "content": response_text,
195 })
196
197 # ── Execute each tool and append results ──
198 for call in tool_calls:
199 fn_name = call.get("name", "")
200 fn_args = call.get("arguments", {})
201
202 # ── arguments may be a string or dict depending on model output ──
203 if isinstance(fn_args, str):
204 fn_args = json.loads(fn_args)
205
206 print(f" • {fn_name}({json.dumps(fn_args, ensure_ascii=False)})")
207 result = execute_tool(fn_name, fn_args)
208 print(f" ↳ {result[:150]}")
209
210 # ── Append tool result as tool role message ──
211 messages.append({
212 "role": "tool",
213 "name": fn_name,
214 "content": result,
215 })
216
217 continue # loop back for next generation
218
219 # ── No tool calls — this is the final answer ──
220 print(f"\n{'='*60}")
221 print(f"AGENT FINAL ANSWER:\n{response_text}")
222 print(f"{'='*60}\n")
223 return response_text
224
225 return "[Agent stopped: exceeded maximum steps]"
226
227# ── Run examples ──
228if __name__ == "__main__":
229
230 run_agent(
231 "Tolong carikan flat 4-bilik dekat Tampines, bajet bawah $500,000. "
232 "Nak tahu juga berapa anggaran pinjaman bulanan."
233 )============================================================
AGENT FINAL ANSWER:
Tampines
4-room
500000
============================================================| Task | Metric |
|---|---|
| Sentiment Analysis | Accuracy |
| Extractive QA (ID, VI, TH, TA) | ChrF++ |
| MCQ-QA (TL, MY, MS) | Accuracy |
| Metaphor | Accuracy |
| Abstractive Summarisation | Rouge-L |
| Translations | MetricX-24 score (with reference) |
| Causal Reasoning | Accuracy |
| Natural Language Inference | Accuracy |
| LINDSEA | Accuracy |
| Global MMLU Lite | Accuracy |
| ThaiExam | Accuracy |
| Kalahi | Accuracy |
| SEA-IFEval | Accuracy |
| SEA-MTBench | Win rate against a reference |

| GPU Chip | Model Size (GB) | VRAM Required (GB) | Time to First Token (s) | Tokens per Second |
|---|---|---|---|---|
| H200 | 34.4 GB | 51.1 GiB | 0.0512 | 69.9005 |
| H100 | 34.4 GB | 51.1 GiB | 0.326 | 49.03 |
Additional Remarks:
- TTFT and Tokens per Second: measured with vLLM on localhost and concurrency = 1.
- Offload all layers to GPU, Context Length 8192
- Reported results are the median (p50) values, calculated across 10 requests.
- Input size 4K, output 1K