初始化项目,由ModelHub XC社区提供模型
Model: Nanthasit/sakthai-context-1.5b-merged Source: Original Platform
This commit is contained in:
120
eval/EVAL.md
Normal file
120
eval/EVAL.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# SakThai 1.5B Merged Model — Evaluation Report
|
||||
|
||||
**Model:** `Nanthasit/sakthai-context-1.5b-merged`
|
||||
**Base:** Qwen/Qwen2.5-1.5B-Instruct
|
||||
**Adapter:** Nanthasit/sakthai-context-1.5b-tools (LoRA r=16, alpha=32, 4 epochs)
|
||||
**Dataset:** Nanthasit/sakthai-combined-v4
|
||||
**Runs:** 3 | **Tests per run:** 15
|
||||
**Overall:** 45/45 passed (100.0%)
|
||||
|
||||
## Test-by-Test Results
|
||||
|
||||
| # | Category | Test | Pass Rate | Avg Time |
|
||||
|---|----------|------|:---------:|:--------:|
|
||||
| 1 | basic | greeting | ✅ 100% | 34.2s |
|
||||
| 2 | basic | self-identity | ✅ 100% | 72.4s |
|
||||
| 3 | multi-turn | name-recall | ✅ 100% | 13.3s |
|
||||
| 4 | multi-turn | context-follow | ✅ 100% | 9.5s |
|
||||
| 5 | multi-turn | preference-remember | ✅ 100% | 33.7s |
|
||||
| 6 | instruction | json-array-output | ✅ 100% | 10.2s |
|
||||
| 7 | instruction | numbered-list | ✅ 100% | 38.9s |
|
||||
| 8 | instruction | concise-output | ✅ 100% | 15.0s |
|
||||
| 9 | tool-calling | weather-query | ✅ 100% | 133.3s |
|
||||
| 10 | tool-calling | limitation-awareness | ✅ 100% | 8.6s |
|
||||
| 11 | reasoning | simple-math | ✅ 100% | 72.8s |
|
||||
| 12 | reasoning | coding-query | ✅ 100% | 51.1s |
|
||||
| 13 | reasoning | explain-concept | ✅ 100% | 39.6s |
|
||||
| 14 | format | json-object | ✅ 100% | 20.3s |
|
||||
| 15 | format | markdown-table | ✅ 100% | 49.1s |
|
||||
|
||||
|
||||
## Comparison: 0.5B vs 1.5B
|
||||
|
||||
Pass rates from 0.5B eval (single run): See `eval/EVAL.md` in 0.5b-merged repo.
|
||||
|
||||
## Sample Responses
|
||||
|
||||
|
||||
### greeting
|
||||
|
||||
> I'm here to help with any questions you may have. Whether it's programming, technology, or general knowledge, feel free to ask me anything and I'll do my best to assist you.
|
||||
|
||||
### self-identity
|
||||
|
||||
> I am Qwen, the AI language model developed by Alibaba Cloud. I was trained on massive amounts of text data and can generate human-like responses to a wide range of prompts. My capabilities include gen
|
||||
|
||||
### name-recall
|
||||
|
||||
> Your name is Beer. It's nice to know that you're here with me today. How can I assist you?
|
||||
|
||||
### context-follow
|
||||
|
||||
> You like cats and programming.
|
||||
|
||||
### preference-remember
|
||||
|
||||
> As an AI language model, I don't have access to your personal preferences or memories. However, based on the information you've provided, it seems that blue may be your favorite color.
|
||||
|
||||
### json-array-output
|
||||
|
||||
> ["red","blue","yellow"]
|
||||
|
||||
### numbered-list
|
||||
|
||||
> 1. Boil water in a kettle or on the stove.
|
||||
2. Place loose tea leaves or tea bags into a teapot or cup.
|
||||
3. Pour hot water over the tea and let it steep for several minutes.
|
||||
|
||||
### concise-output
|
||||
|
||||
> A GPU accelerates computations through parallel processing of large data sets.
|
||||
|
||||
### weather-query
|
||||
|
||||
> Tokyo has four seasons: spring (March to May), summer (June to August), autumn (September to November), and winter (December to February). The temperature ranges from 10°C to 35°C throughout the year.
|
||||
|
||||
### limitation-awareness
|
||||
|
||||
> Subject: Hello John!
|
||||
|
||||
### simple-math
|
||||
|
||||
> To calculate the distance traveled by the train, you can use the formula:
|
||||
|
||||
Distance = Speed x Time
|
||||
|
||||
In this case, the speed is given as 120 km/h and the time is 2.5 hours.
|
||||
|
||||
So,
|
||||
|
||||
Distance = 120 km/h x
|
||||
|
||||
### coding-query
|
||||
|
||||
> def is_palindrome(string):
|
||||
# Remove any non-alphanumeric characters and convert to lowercase
|
||||
cleaned_string = ''.join(char.lower() for char in string if char.isalnum())
|
||||
|
||||
# Check if the
|
||||
|
||||
### explain-concept
|
||||
|
||||
> LoRA is a lightweight method that uses a small number of parameters to approximate the behavior of a larger model during inference. Full fine-tuning involves training the entire model from scratch on
|
||||
|
||||
### json-object
|
||||
|
||||
> {
|
||||
"name": "John",
|
||||
"age": 30,
|
||||
"city": "London"
|
||||
}
|
||||
|
||||
### markdown-table
|
||||
|
||||
> | Language | Typing | Speed |
|
||||
| --- | --- | --- |
|
||||
| Python | Dynamic typing | Fast |
|
||||
| JavaScript | Dynamic typing | Fast |
|
||||
| Rust | Static typing | Slow |
|
||||
|
||||
Note: The speed of programming languages ca
|
||||
185
eval/run_eval.py
Normal file
185
eval/run_eval.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""eval_1.5b_job.py
|
||||
Runs 15 custom quality tests on SakThai 1.5B merged model.
|
||||
Uploads evaluation report to the model repo.
|
||||
"""
|
||||
import os, json, time, re, sys
|
||||
|
||||
try:
|
||||
import torch
|
||||
from transformers import AutoTokenizer, Qwen2ForCausalLM
|
||||
from huggingface_hub import HfApi
|
||||
except ImportError as e:
|
||||
print(f"❌ Missing dependency: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
MODEL_ID = "Nanthasit/sakthai-context-1.5b-merged"
|
||||
N_RUNS = 3 # multiple runs for stability
|
||||
OUTPUT = "/tmp/eval-report"
|
||||
|
||||
# ── Tests ──
|
||||
TESTS = [
|
||||
{"category": "basic", "name": "greeting", "messages": [{"role": "user", "content": "Hello! What can you do?"}]},
|
||||
{"category": "basic", "name": "self-identity", "messages": [{"role": "user", "content": "Who are you? Tell me about yourself."}]},
|
||||
{"category": "multi-turn", "name": "name-recall", "messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "My name is Beer."}, {"role": "assistant", "content": "Nice to meet you, Beer!"}, {"role": "user", "content": "What is my name?"}]},
|
||||
{"category": "multi-turn", "name": "context-follow", "messages": [{"role": "user", "content": "I like cats and programming."}, {"role": "assistant", "content": "Great! Cats are wonderful pets."}, {"role": "user", "content": "What two things do I like?"}]},
|
||||
{"category": "multi-turn", "name": "preference-remember", "messages": [{"role": "user", "content": "Set my favorite color to blue."}, {"role": "assistant", "content": "Got it! Your favorite color is blue."}, {"role": "user", "content": "What is my favorite color?"}]},
|
||||
{"category": "instruction", "name": "json-array-output", "messages": [{"role": "user", "content": "List exactly 3 primary colors. Respond ONLY with a valid JSON array. No other text."}]},
|
||||
{"category": "instruction", "name": "numbered-list", "messages": [{"role": "user", "content": "Give me exactly 3 steps to make tea. Number them 1, 2, 3."}]},
|
||||
{"category": "instruction", "name": "concise-output", "messages": [{"role": "user", "content": "Explain what a GPU does in exactly one short sentence."}]},
|
||||
{"category": "tool-calling", "name": "weather-query", "messages": [{"role": "user", "content": "What's the weather like in Tokyo?"}]},
|
||||
{"category": "tool-calling", "name": "limitation-awareness", "messages": [{"role": "user", "content": "Send an email to john@example.com saying hello."}]},
|
||||
{"category": "reasoning", "name": "simple-math", "messages": [{"role": "user", "content": "If a train travels at 120 km/h for 2.5 hours, how far does it go?"}]},
|
||||
{"category": "reasoning", "name": "coding-query", "messages": [{"role": "user", "content": "Write a Python function that checks if a string is a palindrome."}]},
|
||||
{"category": "reasoning", "name": "explain-concept", "messages": [{"role": "user", "content": "What is the difference between LoRA and full fine-tuning? Keep it short."}]},
|
||||
{"category": "format", "name": "json-object", "messages": [{"role": "user", "content": "Create a JSON object with keys: name, age, city. Use John, 30, London."}]},
|
||||
{"category": "format", "name": "markdown-table", "messages": [{"role": "user", "content": "Create a markdown table comparing Python, JavaScript, and Rust (columns: Language, Typing, Speed)."}]},
|
||||
]
|
||||
|
||||
EVALUATORS = {
|
||||
"json-array-output": lambda t: json_valid_list(t),
|
||||
"json-object": lambda t: json_valid(t),
|
||||
"simple-math": lambda t: "✅ Contains numeric answer" if any(c.isdigit() for c in t) else "⚠️ No digits found",
|
||||
"coding-query": lambda t: "✅ Contains code" if "def " in t and "return" in t else "⚠️ May lack function definition",
|
||||
"numbered-list": lambda t: "✅ Has numbered steps" if (any(f"{i}." in t for i in range(1,4)) or any(f"Step {i}" in t for i in range(1,4))) else f"⚠️ No numbered steps",
|
||||
"name-recall": lambda t: "✅ Recalls name" if "Beer" in t else "⚠️ Name not found",
|
||||
"context-follow": lambda t: "✅ Mentions both" if "cat" in t.lower() and "program" in t.lower() else "⚠️ Doesn't mention both",
|
||||
"preference-remember": lambda t: "✅ Mentions blue" if "blue" in t.lower() else "⚠️ Color not mentioned",
|
||||
"concise-output": lambda t: "✅ Short" if len(t.split()) <= 30 else f"⚠️ {len(t.split())} words",
|
||||
}
|
||||
|
||||
def extract_json(text):
|
||||
for bracket in ('[', '{'):
|
||||
start = text.find(bracket)
|
||||
if start == -1: continue
|
||||
depth, in_str, esc = 0, False, False
|
||||
for i in range(start, len(text)):
|
||||
ch = text[i]
|
||||
if not in_str and ch == bracket[0]: depth += 1
|
||||
elif esc: esc = False
|
||||
elif ch == '\\' and in_str: esc = True
|
||||
elif ch == '"' and not esc: in_str = not in_str
|
||||
elif not in_str and ((bracket == '[' and ch == ']') or (bracket == '{' and ch == '}')):
|
||||
depth -= 1
|
||||
if depth == 0: return text[start:i+1]
|
||||
return text[start:]
|
||||
return text.strip()
|
||||
|
||||
def json_valid(text):
|
||||
try: json.loads(extract_json(text)); return "✅ Valid JSON"
|
||||
except: return f"❌ Not valid JSON"
|
||||
|
||||
def json_valid_list(text):
|
||||
try: obj = json.loads(extract_json(text)); return "✅ Valid JSON array" if isinstance(obj, list) else "❌ Not a list"
|
||||
except: return f"❌ Invalid JSON"
|
||||
|
||||
def strip_role_prefix(text: str) -> str:
|
||||
for pat in [r'^(assistant|system|user|algorithm|tool)\s*\n', r'^(assistant|system|user|algorithm|tool)\s*[:]\s*']:
|
||||
text = re.sub(pat, '', text, count=1)
|
||||
return text.strip()
|
||||
|
||||
# ── Load model ──
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
print(f"📥 Loading {MODEL_ID} on {device}", flush=True)
|
||||
model = Qwen2ForCausalLM.from_pretrained(
|
||||
MODEL_ID, torch_dtype=torch.bfloat16 if device == "cuda" else torch.float32, device_map=device, low_cpu_mem_usage=True,
|
||||
)
|
||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
||||
print(f"✅ Loaded ({sum(p.numel() for p in model.parameters()):,} params)", flush=True)
|
||||
|
||||
# ── Run tests ──
|
||||
all_results = []
|
||||
for run in range(N_RUNS):
|
||||
print(f"\n{'='*40}\nRun {run+1}/{N_RUNS}\n{'='*40}", flush=True)
|
||||
run_results = []
|
||||
for i, test in enumerate(TESTS):
|
||||
try:
|
||||
prompt = tokenizer.apply_chat_template(test["messages"], tokenize=False)
|
||||
inputs = tokenizer(prompt, return_tensors="pt").to(device)
|
||||
t0 = time.time()
|
||||
with torch.no_grad():
|
||||
outputs = model.generate(**inputs, max_new_tokens=256, temperature=0.1, do_sample=True, pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id)
|
||||
elapsed = time.time() - t0
|
||||
generated = outputs[0][inputs["input_ids"].shape[1]:]
|
||||
response = tokenizer.decode(generated, skip_special_tokens=True).strip()
|
||||
cleaned = strip_role_prefix(response)
|
||||
evaluator = EVALUATORS.get(test["name"], lambda t: "✅ Generated" if len(t) > 5 else "⚠️ Too short")
|
||||
eval_result = evaluator(cleaned)
|
||||
passed = eval_result.startswith("✅")
|
||||
print(f" [{i+1}/{len(TESTS)}] [{test['category']}] {test['name']:25s} {'✅' if passed else '❌'} {elapsed:.1f}s", flush=True)
|
||||
print(f" {cleaned[:100]}", flush=True)
|
||||
run_results.append({"name": test["name"], "passed": passed, "eval": eval_result, "response": cleaned, "time": round(elapsed, 1)})
|
||||
except Exception as e:
|
||||
print(f" [{i+1}/{len(TESTS)}] {test['name']}: ❌ Error: {e}", flush=True)
|
||||
run_results.append({"name": test["name"], "passed": False, "eval": f"❌ Error: {e[:80]}", "response": "", "time": 0})
|
||||
all_results.append(run_results)
|
||||
passed = sum(1 for r in run_results if r["passed"])
|
||||
print(f" Run {run+1}: {passed}/{len(TESTS)} ({passed/len(TESTS)*100:.0f}%)", flush=True)
|
||||
|
||||
# ── Aggregate ──
|
||||
from statistics import mean
|
||||
per_test = {t["name"]: {"passes": []} for t in TESTS}
|
||||
for run in all_results:
|
||||
for r in run:
|
||||
per_test[r["name"]]["passes"].append(1 if r["passed"] else 0)
|
||||
|
||||
overall = sum(v for pt in per_test.values() for v in pt["passes"])
|
||||
total = sum(len(pt["passes"]) for pt in per_test.values())
|
||||
overall_rate = overall / total * 100
|
||||
|
||||
# ── Generate report ──
|
||||
report = f"""# SakThai 1.5B Merged Model — Evaluation Report
|
||||
|
||||
**Model:** `{MODEL_ID}`
|
||||
**Base:** Qwen/Qwen2.5-1.5B-Instruct
|
||||
**Adapter:** Nanthasit/sakthai-context-1.5b-tools (LoRA r=16, alpha=32, 4 epochs)
|
||||
**Dataset:** Nanthasit/sakthai-combined-v4
|
||||
**Runs:** {N_RUNS} | **Tests per run:** {len(TESTS)}
|
||||
**Overall:** {overall}/{total} passed ({overall_rate:.1f}%)
|
||||
|
||||
## Test-by-Test Results
|
||||
|
||||
| # | Category | Test | Pass Rate | Avg Time |
|
||||
|---|----------|------|:---------:|:--------:|
|
||||
"""
|
||||
for i, t in enumerate(TESTS):
|
||||
rates = per_test[t["name"]]["passes"]
|
||||
pr = mean(rates) * 100
|
||||
at = mean([r["time"] for run in all_results for r in run if r["name"] == t["name"] and r["time"] > 0])
|
||||
report += f"| {i+1} | {t['category']} | {t['name']} | {'✅' if pr >= 80 else '⚠️'} {pr:.0f}% | {at:.1f}s |\n"
|
||||
|
||||
report += f"""
|
||||
|
||||
## Comparison: 0.5B vs 1.5B
|
||||
|
||||
Pass rates from 0.5B eval (single run): See `eval/EVAL.md` in 0.5b-merged repo.
|
||||
|
||||
## Sample Responses
|
||||
|
||||
"""
|
||||
for r in all_results[-1]:
|
||||
if r["response"]:
|
||||
report += f"""
|
||||
### {r['name']}
|
||||
|
||||
> {r['response'][:200]}
|
||||
"""
|
||||
f"Time: {r['time']}s | {r['eval']}"
|
||||
|
||||
os.makedirs(OUTPUT, exist_ok=True)
|
||||
with open(os.path.join(OUTPUT, "EVAL.md"), "w") as f:
|
||||
f.write(report)
|
||||
print(f"\n✅ Report saved", flush=True)
|
||||
|
||||
# Upload
|
||||
print(f"☁️ Uploading report...", flush=True)
|
||||
from huggingface_hub import HfApi
|
||||
api = HfApi()
|
||||
api.upload_folder(
|
||||
repo_id=MODEL_ID,
|
||||
folder_path=OUTPUT,
|
||||
path_in_repo="eval",
|
||||
repo_type="model",
|
||||
commit_message=f"eval: {N_RUNS}-run custom eval ({overall}/{total} passed, {overall_rate:.1f}%)",
|
||||
)
|
||||
print(f"✅ Report at https://huggingface.co/{MODEL_ID}/tree/main/eval", flush=True)
|
||||
print(f"\n{'='*40}\nResult: {overall}/{total} passed ({overall_rate:.1f}%)", flush=True)
|
||||
91
eval/workbench-test-record.json
Normal file
91
eval/workbench-test-record.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "workbench_local",
|
||||
"model": "Nanthasit/sakthai-context-1.5b-merged",
|
||||
"load_time_seconds": 10.6,
|
||||
"device": "cpu",
|
||||
"timestamp": "2026-07-05T17:12:45Z",
|
||||
"results": [
|
||||
{
|
||||
"name": "basic_greeting",
|
||||
"passed": true,
|
||||
"response_preview": "Hello! How can I assist you today?",
|
||||
"response_length": 34,
|
||||
"latency_seconds": 13.03,
|
||||
"prompt_tokens": 31,
|
||||
"completion_tokens": 11,
|
||||
"checks": [
|
||||
"non_empty",
|
||||
"substantial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tool_call",
|
||||
"passed": true,
|
||||
"response_preview": "Here's a list of recent AI-related news articles:\n\n1. \"AI-powered chatbots can now learn from human emotions\" - The Verge (2023)\n2. \"Google launches new AI tool to help developers build better apps\" - TechCrunch (2023)\n3. \"IBM Watson Health releases new cancer treatment recommendations\" - Forbes (20",
|
||||
"response_length": 889,
|
||||
"latency_seconds": 114.92,
|
||||
"prompt_tokens": 37,
|
||||
"completion_tokens": 237,
|
||||
"checks": [
|
||||
"non_empty",
|
||||
"substantial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "name_recall",
|
||||
"passed": true,
|
||||
"response_preview": "Your name is Beer.",
|
||||
"response_length": 18,
|
||||
"latency_seconds": 10.26,
|
||||
"prompt_tokens": 45,
|
||||
"completion_tokens": 7,
|
||||
"checks": [
|
||||
"non_empty",
|
||||
"substantial",
|
||||
"name_recall"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "factual_qa",
|
||||
"passed": true,
|
||||
"response_preview": "The capital of Japan is Tokyo.",
|
||||
"response_length": 30,
|
||||
"latency_seconds": 9.63,
|
||||
"prompt_tokens": 28,
|
||||
"completion_tokens": 9,
|
||||
"checks": [
|
||||
"non_empty",
|
||||
"substantial",
|
||||
"correct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "json_output",
|
||||
"passed": true,
|
||||
"response_preview": "{\"frameworks\": [\"TensorFlow\", \"PyTorch\", \"Scikit-learn\"]}",
|
||||
"response_length": 57,
|
||||
"latency_seconds": 17.56,
|
||||
"prompt_tokens": 41,
|
||||
"completion_tokens": 20,
|
||||
"checks": [
|
||||
"non_empty",
|
||||
"substantial",
|
||||
"valid_json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "instruction_following",
|
||||
"passed": true,
|
||||
"response_preview": "A transformer is an electrical device that transfers energy between two or more circuits through electromagnetic induction, without the use of moving parts. It consists of at least two coils of wire wound around a common iron core. When current flows through one coil (primary), it creates a magnetic",
|
||||
"response_length": 708,
|
||||
"latency_seconds": 60.26,
|
||||
"prompt_tokens": 29,
|
||||
"completion_tokens": 129,
|
||||
"checks": [
|
||||
"non_empty",
|
||||
"substantial"
|
||||
]
|
||||
}
|
||||
],
|
||||
"summary": "6/6 passed"
|
||||
}
|
||||
168
eval/workbench-test.py
Normal file
168
eval/workbench-test.py
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Workbench test — load merged 1.5B model, run 6 test prompts, record results."""
|
||||
import json, time, os, sys
|
||||
import torch
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
MODEL = "Nanthasit/sakthai-context-1.5b-merged"
|
||||
OUTPUT = "/opt/data/sakthai-workbench-record.json"
|
||||
|
||||
# Load model + tokenizer
|
||||
print(f"Loading {MODEL}...", flush=True)
|
||||
start = time.time()
|
||||
tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
MODEL,
|
||||
trust_remote_code=True,
|
||||
torch_dtype=torch.float16,
|
||||
device_map="auto",
|
||||
low_cpu_mem_usage=True
|
||||
)
|
||||
load_time = time.time() - start
|
||||
print(f"Loaded in {load_time:.1f}s on {model.device}", flush=True)
|
||||
|
||||
# Test prompts
|
||||
tests = [
|
||||
{
|
||||
"name": "basic_greeting",
|
||||
"desc": "Say hello in one sentence",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are SakThai, a helpful assistant. Be concise."},
|
||||
{"role": "user", "content": "Say hello in one sentence."}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tool_call",
|
||||
"desc": "Tool-use intent",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are SakThai with tools: search(query), read_file(path), run_command(command)."},
|
||||
{"role": "user", "content": "Search for the latest AI news"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "name_recall",
|
||||
"desc": "Remember name across 3 turns",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are SakThai."},
|
||||
{"role": "user", "content": "My name is Beer."},
|
||||
{"role": "assistant", "content": "Nice to meet you, Beer!"},
|
||||
{"role": "user", "content": "What's my name?"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "factual_qa",
|
||||
"desc": "Simple factual question",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are SakThai. Be concise."},
|
||||
{"role": "user", "content": "What is the capital of Japan?"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "json_output",
|
||||
"desc": "Structured JSON",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are SakThai. Only respond with valid JSON."},
|
||||
{"role": "user", "content": 'List 3 ML frameworks: {"frameworks": ["a","b","c"]}'}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "instruction_following",
|
||||
"desc": "Follow formatting instruction",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are SakThai. Exactly one sentence."},
|
||||
{"role": "user", "content": "Explain what a transformer is."}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
results = []
|
||||
for i, test in enumerate(tests):
|
||||
print(f"\n--- TEST {i+1}: {test['name']} ---", flush=True)
|
||||
|
||||
try:
|
||||
prompt = tokenizer.apply_chat_template(
|
||||
test["messages"],
|
||||
tokenize=False,
|
||||
add_generation_prompt=True
|
||||
)
|
||||
|
||||
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
||||
|
||||
t0 = time.time()
|
||||
with torch.no_grad():
|
||||
outputs = model.generate(
|
||||
**inputs,
|
||||
max_new_tokens=256,
|
||||
temperature=0.1,
|
||||
do_sample=True,
|
||||
pad_token_id=tokenizer.eos_token_id
|
||||
)
|
||||
elapsed = time.time() - t0
|
||||
|
||||
input_len = inputs.input_ids.shape[1]
|
||||
response = tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
|
||||
|
||||
prompt_tokens = input_len
|
||||
completion_tokens = outputs.shape[1] - input_len
|
||||
|
||||
checks = []
|
||||
if len(response) > 0:
|
||||
checks.append("non_empty")
|
||||
if len(response) > 10:
|
||||
checks.append("substantial")
|
||||
if test["name"] == "name_recall" and "beer" in response.lower():
|
||||
checks.append("name_recall")
|
||||
if test["name"] == "factual_qa" and "tokyo" in response.lower():
|
||||
checks.append("correct")
|
||||
if test["name"] == "json_output":
|
||||
try:
|
||||
json.loads(response)
|
||||
checks.append("valid_json")
|
||||
except:
|
||||
pass
|
||||
|
||||
result = {
|
||||
"name": test["name"],
|
||||
"passed": len(checks) > 0,
|
||||
"response_preview": response[:300],
|
||||
"response_length": len(response),
|
||||
"latency_seconds": round(elapsed, 2),
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"checks": checks
|
||||
}
|
||||
|
||||
print(f" ✅ {response[:150]}", flush=True)
|
||||
print(f" ⏱ {elapsed:.2f}s | 📝 {prompt_tokens}→{completion_tokens} | ✅ {checks}", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
result = {"name": test["name"], "passed": False, "error": str(e)[:300]}
|
||||
print(f" ❌ {e}", flush=True)
|
||||
|
||||
results.append(result)
|
||||
sys.stdout.flush()
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*50}", flush=True)
|
||||
print("WORKBENCH TEST — SakThai Context 1.5B", flush=True)
|
||||
passed = sum(1 for r in results if r.get("passed"))
|
||||
print(f"Passed: {passed}/{len(results)}", flush=True)
|
||||
for r in results:
|
||||
status = "✅" if r.get("passed") else "❌"
|
||||
lat = f"{r.get('latency_seconds',0):.1f}s" if r.get("passed") else " - "
|
||||
detail = str(r.get("checks", r.get("error","?")))[:60]
|
||||
print(f" {status} {r['name']:<20} ⏱ {lat} {detail}", flush=True)
|
||||
|
||||
record = {
|
||||
"type": "workbench_local",
|
||||
"model": MODEL,
|
||||
"load_time_seconds": round(load_time, 1),
|
||||
"device": str(model.device),
|
||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"results": results,
|
||||
"summary": f"{passed}/{len(results)} passed"
|
||||
}
|
||||
|
||||
with open(OUTPUT, "w") as f:
|
||||
json.dump(record, f, indent=2)
|
||||
print(f"\nSaved to {OUTPUT}", flush=True)
|
||||
Reference in New Issue
Block a user