bbkdevops's picture
download
raw
7.41 kB
from __future__ import annotations
import argparse
import json
import random
import time
from collections import Counter
from pathlib import Path
from typing import Any
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, Dataset
ROOT = Path(r"D:\ad\tinymind\model\tinymind-apex")
ARTIFACTS = ROOT / "artifacts"
MAX_LEN = 512
def load_records() -> list[dict[str, Any]]:
path = ARTIFACTS / "records.jsonl"
return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
def label_for_tool(record: dict[str, Any]) -> str:
calls = record.get("tool_calls", [])
if not calls:
return "none"
first = calls[0]
name = first.get("name") or first.get("tool") or "none"
return name
def build_vocab(records: list[dict[str, Any]], vocab_size: int = 4096) -> dict[str, int]:
counter = Counter()
for record in records:
counter.update(record["text"])
vocab = {"<pad>": 0, "<unk>": 1}
for ch, _ in counter.most_common(vocab_size - len(vocab)):
if ch not in vocab:
vocab[ch] = len(vocab)
return vocab
class TinyMindDataset(Dataset):
def __init__(self, records: list[dict[str, Any]], vocab: dict[str, int], domains: dict[str, int], tools: dict[str, int]):
self.records = records
self.vocab = vocab
self.domains = domains
self.tools = tools
def __len__(self) -> int:
return len(self.records)
def __getitem__(self, idx: int):
record = self.records[idx]
text = record["text"][:MAX_LEN]
ids = [self.vocab.get(ch, 1) for ch in text]
if len(ids) < MAX_LEN:
ids += [0] * (MAX_LEN - len(ids))
risk = 1 if str(record.get("quality", {}).get("risk", "")).lower() == "high" else 0
return {
"x": torch.tensor(ids, dtype=torch.long),
"domain": torch.tensor(self.domains[record["domain"]], dtype=torch.long),
"tool": torch.tensor(self.tools[label_for_tool(record)], dtype=torch.long),
"risk": torch.tensor(risk, dtype=torch.float32),
}
class NeuralCore(nn.Module):
def __init__(self, vocab_size: int, domain_count: int, tool_count: int, emb: int = 96, hidden: int = 192):
super().__init__()
self.embedding = nn.Embedding(vocab_size, emb, padding_idx=0)
self.conv3 = nn.Conv1d(emb, hidden, kernel_size=3, padding=1)
self.conv5 = nn.Conv1d(emb, hidden, kernel_size=5, padding=2)
self.norm = nn.LayerNorm(hidden * 2)
self.dropout = nn.Dropout(0.15)
self.domain_head = nn.Linear(hidden * 2, domain_count)
self.tool_head = nn.Linear(hidden * 2, tool_count)
self.risk_head = nn.Linear(hidden * 2, 1)
def encode(self, x):
emb = self.embedding(x).transpose(1, 2)
h3 = torch.relu(self.conv3(emb)).amax(dim=2)
h5 = torch.relu(self.conv5(emb)).amax(dim=2)
h = torch.cat([h3, h5], dim=1)
h = self.dropout(self.norm(h))
return h
def forward(self, x):
h = self.encode(x)
return {
"domain": self.domain_head(h),
"tool": self.tool_head(h),
"risk": self.risk_head(h).squeeze(-1),
}
def evaluate(model: NeuralCore, loader: DataLoader, device: str) -> dict[str, float]:
model.eval()
total = 0
domain_ok = 0
tool_ok = 0
risk_ok = 0
with torch.no_grad():
for batch in loader:
x = batch["x"].to(device)
domain = batch["domain"].to(device)
tool = batch["tool"].to(device)
risk = batch["risk"].to(device)
out = model(x)
domain_ok += (out["domain"].argmax(dim=1) == domain).sum().item()
tool_ok += (out["tool"].argmax(dim=1) == tool).sum().item()
risk_pred = (torch.sigmoid(out["risk"]) >= 0.5).float()
risk_ok += (risk_pred == risk).sum().item()
total += x.shape[0]
return {
"domain_acc": round(domain_ok / max(total, 1), 4),
"tool_acc": round(tool_ok / max(total, 1), 4),
"risk_acc": round(risk_ok / max(total, 1), 4),
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=str(ROOT))
parser.add_argument("--epochs", type=int, default=8)
parser.add_argument("--batch-size", type=int, default=64)
args = parser.parse_args()
root = Path(args.root)
artifacts = root / "artifacts"
records = load_records()
random.Random(42).shuffle(records)
vocab = build_vocab(records)
domains = {name: i for i, name in enumerate(sorted({r["domain"] for r in records}))}
tools = {name: i for i, name in enumerate(sorted({label_for_tool(r) for r in records}))}
split = max(1, int(len(records) * 0.85))
train_records = records[:split]
val_records = records[split:]
train_ds = TinyMindDataset(train_records, vocab, domains, tools)
val_ds = TinyMindDataset(val_records, vocab, domains, tools)
train_loader = DataLoader(train_ds, batch_size=args.batch_size, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=args.batch_size)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = NeuralCore(len(vocab), len(domains), len(tools)).to(device)
opt = torch.optim.AdamW(model.parameters(), lr=2e-3, weight_decay=1e-3)
domain_loss = nn.CrossEntropyLoss()
tool_loss = nn.CrossEntropyLoss()
risk_loss = nn.BCEWithLogitsLoss()
history = []
for epoch in range(1, args.epochs + 1):
model.train()
total_loss = 0.0
for batch in train_loader:
x = batch["x"].to(device)
domain = batch["domain"].to(device)
tool = batch["tool"].to(device)
risk = batch["risk"].to(device)
out = model(x)
loss = domain_loss(out["domain"], domain) + tool_loss(out["tool"], tool) + risk_loss(out["risk"], risk)
opt.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), 1.0)
opt.step()
total_loss += loss.item()
metrics = evaluate(model, val_loader, device)
metrics["epoch"] = epoch
metrics["loss"] = round(total_loss / max(len(train_loader), 1), 4)
history.append(metrics)
print(json.dumps(metrics, ensure_ascii=False))
neural_dir = artifacts / "neural_core"
neural_dir.mkdir(parents=True, exist_ok=True)
torch.save(
{
"state_dict": model.cpu().state_dict(),
"vocab": vocab,
"domains": domains,
"tools": tools,
"max_len": MAX_LEN,
"config": {"emb": 96, "hidden": 192},
},
neural_dir / "tinymind_neural_core.pt",
)
manifest = {
"name": "tinymind-neural-core",
"type": "pytorch_char_cnn_multihead",
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"records": len(records),
"device": device,
"epochs": args.epochs,
"history": history,
"weights": str(neural_dir / "tinymind_neural_core.pt"),
}
(neural_dir / "manifest.json").write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
print(json.dumps(manifest, indent=2, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())

Xet Storage Details

Size:
7.41 kB
·
Xet hash:
2c766cc08d8d4ac82e1f5d8aecf98311e7fa0f6d009b05d68ccb43083c4ac71a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.