Upload 15_scugnizz_chat_gui_agent_v2_fixed.py with huggingface_hub
Browse files
15_scugnizz_chat_gui_agent_v2_fixed.py
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# /// script
|
| 4 |
+
# dependencies = ["torch","datasets","transformers","huggingface_hub","numpy","hf_xet"]
|
| 5 |
+
# ///
|
| 6 |
+
# SFT ScugnizzLLM-1.3B PCS from 1B-token pretrain checkpoint.
|
| 7 |
+
|
| 8 |
+
import argparse, json, math, random, time, sys, os, re
|
| 9 |
+
from contextlib import nullcontext
|
| 10 |
+
from dataclasses import dataclass, asdict
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
import torch.nn.functional as F
|
| 17 |
+
from datasets import load_dataset
|
| 18 |
+
from huggingface_hub import HfApi, upload_folder, hf_hub_download
|
| 19 |
+
from transformers import GPT2TokenizerFast
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# ==========================================================
|
| 23 |
+
# MODEL
|
| 24 |
+
# ==========================================================
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class GPTConfig:
|
| 28 |
+
vocab_size: int
|
| 29 |
+
block_size: int
|
| 30 |
+
n_layer: int
|
| 31 |
+
n_head: int
|
| 32 |
+
n_embd: int
|
| 33 |
+
dropout: float = 0.1
|
| 34 |
+
bias: bool = False
|
| 35 |
+
pcs_a: float = 0.8309193524478643
|
| 36 |
+
pcs_b: float = 0.0
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class PCS(nn.Module):
|
| 40 |
+
def __init__(self, a=0.8309193524478643, b=0.0):
|
| 41 |
+
super().__init__()
|
| 42 |
+
self.a = float(a)
|
| 43 |
+
self.b = float(b)
|
| 44 |
+
|
| 45 |
+
def forward(self, x):
|
| 46 |
+
return x * torch.sin(self.a * x) + self.b * torch.cos(x)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class Attn(nn.Module):
|
| 50 |
+
def __init__(self, c):
|
| 51 |
+
super().__init__()
|
| 52 |
+
assert c.n_embd % c.n_head == 0
|
| 53 |
+
self.n_head = c.n_head
|
| 54 |
+
self.head_dim = c.n_embd // c.n_head
|
| 55 |
+
self.dropout = c.dropout
|
| 56 |
+
self.qkv = nn.Linear(c.n_embd, 3 * c.n_embd, bias=c.bias)
|
| 57 |
+
self.proj = nn.Linear(c.n_embd, c.n_embd, bias=c.bias)
|
| 58 |
+
self.drop = nn.Dropout(c.dropout)
|
| 59 |
+
|
| 60 |
+
def forward(self, x):
|
| 61 |
+
B, T, C = x.shape
|
| 62 |
+
q, k, v = self.qkv(x).split(C, dim=2)
|
| 63 |
+
q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
|
| 64 |
+
k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
|
| 65 |
+
v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
|
| 66 |
+
y = F.scaled_dot_product_attention(
|
| 67 |
+
q, k, v,
|
| 68 |
+
dropout_p=self.dropout if self.training else 0.0,
|
| 69 |
+
is_causal=True,
|
| 70 |
+
)
|
| 71 |
+
y = y.transpose(1, 2).contiguous().view(B, T, C)
|
| 72 |
+
return self.drop(self.proj(y))
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class MLP(nn.Module):
|
| 76 |
+
def __init__(self, c):
|
| 77 |
+
super().__init__()
|
| 78 |
+
self.fc1 = nn.Linear(c.n_embd, 4 * c.n_embd, bias=c.bias)
|
| 79 |
+
self.act = PCS(c.pcs_a, c.pcs_b)
|
| 80 |
+
self.fc2 = nn.Linear(4 * c.n_embd, c.n_embd, bias=c.bias)
|
| 81 |
+
self.drop = nn.Dropout(c.dropout)
|
| 82 |
+
|
| 83 |
+
def forward(self, x):
|
| 84 |
+
return self.drop(self.fc2(self.act(self.fc1(x))))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class Block(nn.Module):
|
| 88 |
+
def __init__(self, c):
|
| 89 |
+
super().__init__()
|
| 90 |
+
self.ln1 = nn.LayerNorm(c.n_embd)
|
| 91 |
+
self.attn = Attn(c)
|
| 92 |
+
self.ln2 = nn.LayerNorm(c.n_embd)
|
| 93 |
+
self.mlp = MLP(c)
|
| 94 |
+
|
| 95 |
+
def forward(self, x):
|
| 96 |
+
x = x + self.attn(self.ln1(x))
|
| 97 |
+
x = x + self.mlp(self.ln2(x))
|
| 98 |
+
return x
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
class GPT(nn.Module):
|
| 102 |
+
def __init__(self, c):
|
| 103 |
+
super().__init__()
|
| 104 |
+
self.cfg = c
|
| 105 |
+
self.tok_emb = nn.Embedding(c.vocab_size, c.n_embd)
|
| 106 |
+
self.pos_emb = nn.Embedding(c.block_size, c.n_embd)
|
| 107 |
+
self.drop = nn.Dropout(c.dropout)
|
| 108 |
+
self.blocks = nn.ModuleList([Block(c) for _ in range(c.n_layer)])
|
| 109 |
+
self.ln_f = nn.LayerNorm(c.n_embd)
|
| 110 |
+
self.lm_head = nn.Linear(c.n_embd, c.vocab_size, bias=False)
|
| 111 |
+
self.tok_emb.weight = self.lm_head.weight
|
| 112 |
+
self.apply(self._init)
|
| 113 |
+
for n, p in self.named_parameters():
|
| 114 |
+
if n.endswith("proj.weight") or n.endswith("fc2.weight"):
|
| 115 |
+
nn.init.normal_(p, mean=0.0, std=0.02 / math.sqrt(2 * c.n_layer))
|
| 116 |
+
|
| 117 |
+
def _init(self, m):
|
| 118 |
+
if isinstance(m, nn.Linear):
|
| 119 |
+
nn.init.normal_(m.weight, mean=0.0, std=0.02)
|
| 120 |
+
if m.bias is not None:
|
| 121 |
+
nn.init.zeros_(m.bias)
|
| 122 |
+
elif isinstance(m, nn.Embedding):
|
| 123 |
+
nn.init.normal_(m.weight, mean=0.0, std=0.02)
|
| 124 |
+
|
| 125 |
+
def forward(self, idx, targets=None):
|
| 126 |
+
B, T = idx.shape
|
| 127 |
+
if T > self.cfg.block_size:
|
| 128 |
+
idx = idx[:, -self.cfg.block_size:]
|
| 129 |
+
if targets is not None:
|
| 130 |
+
targets = targets[:, -self.cfg.block_size:]
|
| 131 |
+
B, T = idx.shape
|
| 132 |
+
|
| 133 |
+
pos = torch.arange(T, device=idx.device)
|
| 134 |
+
x = self.drop(self.tok_emb(idx) + self.pos_emb(pos))
|
| 135 |
+
for b in self.blocks:
|
| 136 |
+
x = b(x)
|
| 137 |
+
logits = self.lm_head(self.ln_f(x))
|
| 138 |
+
|
| 139 |
+
loss = None
|
| 140 |
+
if targets is not None:
|
| 141 |
+
loss = F.cross_entropy(
|
| 142 |
+
logits.reshape(B * T, -1),
|
| 143 |
+
targets.reshape(B * T),
|
| 144 |
+
ignore_index=-100,
|
| 145 |
+
)
|
| 146 |
+
return logits, loss
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# ==========================================================
|
| 150 |
+
# UTILS
|
| 151 |
+
# ==========================================================
|
| 152 |
+
|
| 153 |
+
def args():
|
| 154 |
+
p = argparse.ArgumentParser()
|
| 155 |
+
|
| 156 |
+
# Base pretrain checkpoint
|
| 157 |
+
p.add_argument("--base-repo-id", default="Daisuke675/scugnizz-1b")
|
| 158 |
+
p.add_argument("--base-ckpt", default="AUTO")
|
| 159 |
+
|
| 160 |
+
# Dataset
|
| 161 |
+
p.add_argument("--dataset", default="Daisuke675/scugnizz-instruct-pro-50k")
|
| 162 |
+
p.add_argument("--split", default="train")
|
| 163 |
+
|
| 164 |
+
# Training
|
| 165 |
+
p.add_argument("--block-size", type=int, default=1024)
|
| 166 |
+
p.add_argument("--batch-size", type=int, default=1)
|
| 167 |
+
p.add_argument("--grad-accum", type=int, default=16)
|
| 168 |
+
p.add_argument("--epochs", type=int, default=1)
|
| 169 |
+
p.add_argument("--max-steps", type=int, default=0)
|
| 170 |
+
p.add_argument("--lr", type=float, default=5e-6)
|
| 171 |
+
p.add_argument("--min-lr", type=float, default=1e-6)
|
| 172 |
+
p.add_argument("--warmup-steps", type=int, default=50)
|
| 173 |
+
p.add_argument("--weight-decay", type=float, default=0.05)
|
| 174 |
+
p.add_argument("--grad-clip", type=float, default=1.0)
|
| 175 |
+
p.add_argument("--dropout", type=float, default=0.05)
|
| 176 |
+
p.add_argument("--eval-interval", type=int, default=250)
|
| 177 |
+
p.add_argument("--save-interval", type=int, default=250)
|
| 178 |
+
p.add_argument("--log-interval", type=int, default=10)
|
| 179 |
+
p.add_argument("--eval-batches", type=int, default=30)
|
| 180 |
+
p.add_argument("--seed", type=int, default=42)
|
| 181 |
+
p.add_argument("--dtype", choices=["auto", "bfloat16", "float16", "float32"], default="auto")
|
| 182 |
+
|
| 183 |
+
# Output
|
| 184 |
+
p.add_argument("--out-dir", default="runs/sft-1b-from-1b")
|
| 185 |
+
p.add_argument("--hub-repo-id", default="Daisuke675/scugnizz-1b")
|
| 186 |
+
p.add_argument("--hub-path", default="training-runs/sft-1b-pro-50k")
|
| 187 |
+
p.add_argument("--push", action="store_true", default=True)
|
| 188 |
+
p.add_argument("--push-every-save", action="store_true", default=True)
|
| 189 |
+
|
| 190 |
+
return p.parse_args()
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def seed_all(s):
|
| 194 |
+
random.seed(s)
|
| 195 |
+
np.random.seed(s)
|
| 196 |
+
torch.manual_seed(s)
|
| 197 |
+
if torch.cuda.is_available():
|
| 198 |
+
torch.cuda.manual_seed_all(s)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def dtype_of(d, dev):
|
| 202 |
+
if d != "auto":
|
| 203 |
+
return d
|
| 204 |
+
if dev == "cuda" and torch.cuda.is_bf16_supported():
|
| 205 |
+
return "bfloat16"
|
| 206 |
+
if dev == "cuda":
|
| 207 |
+
return "float16"
|
| 208 |
+
return "float32"
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def ac(dev, dt):
|
| 212 |
+
if dev != "cuda" or dt == "float32":
|
| 213 |
+
return nullcontext()
|
| 214 |
+
return torch.amp.autocast("cuda", dtype=torch.bfloat16 if dt == "bfloat16" else torch.float16)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def lr_at(step, max_steps, a):
|
| 218 |
+
if step < a.warmup_steps:
|
| 219 |
+
return a.lr * (step + 1) / max(1, a.warmup_steps)
|
| 220 |
+
r = (step - a.warmup_steps) / max(1, max_steps - a.warmup_steps)
|
| 221 |
+
return a.min_lr + 0.5 * (1 + math.cos(math.pi * r)) * (a.lr - a.min_lr)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def save(path, model, opt, step, best, a, cfg):
|
| 225 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 226 |
+
payload = {
|
| 227 |
+
"step": int(step),
|
| 228 |
+
"model": model.state_dict(),
|
| 229 |
+
"optimizer": opt.state_dict(),
|
| 230 |
+
"best_val": float(best),
|
| 231 |
+
"args": vars(a),
|
| 232 |
+
"config": asdict(cfg),
|
| 233 |
+
}
|
| 234 |
+
torch.save(payload, path)
|
| 235 |
+
print("SAVED", path, flush=True)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def upload(out, repo, path, msg):
|
| 239 |
+
api = HfApi()
|
| 240 |
+
api.create_repo(repo, repo_type="model", exist_ok=True)
|
| 241 |
+
upload_folder(
|
| 242 |
+
repo_id=repo,
|
| 243 |
+
repo_type="model",
|
| 244 |
+
folder_path=out,
|
| 245 |
+
path_in_repo=path,
|
| 246 |
+
commit_message=msg,
|
| 247 |
+
)
|
| 248 |
+
print("UPLOADED", repo, path, flush=True)
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def resolve_base_checkpoint(repo_id, base_ckpt):
|
| 252 |
+
if base_ckpt and base_ckpt != "AUTO":
|
| 253 |
+
return hf_hub_download(repo_id=repo_id, filename=base_ckpt, repo_type="model"), base_ckpt
|
| 254 |
+
|
| 255 |
+
candidates = [
|
| 256 |
+
"training-runs/pretrain-1b/checkpoint_last.pt",
|
| 257 |
+
"training-runs/pretrain-1b-pcs-1b/checkpoint_last.pt",
|
| 258 |
+
"training-runs/pretrain-1b-1b-tokens/checkpoint_last.pt",
|
| 259 |
+
"training-runs/pretrain-1b-h200x8/checkpoint_last.pt",
|
| 260 |
+
"training-runs/pretrain/checkpoint_last.pt",
|
| 261 |
+
"checkpoint_last.pt",
|
| 262 |
+
]
|
| 263 |
+
|
| 264 |
+
errors = []
|
| 265 |
+
for c in candidates:
|
| 266 |
+
try:
|
| 267 |
+
p = hf_hub_download(repo_id=repo_id, filename=c, repo_type="model")
|
| 268 |
+
print("BASE CHECKPOINT FOUND:", c, flush=True)
|
| 269 |
+
return p, c
|
| 270 |
+
except Exception as e:
|
| 271 |
+
errors.append((c, repr(e)))
|
| 272 |
+
|
| 273 |
+
print("ERRORE: checkpoint base 1.3B non trovato automaticamente.", flush=True)
|
| 274 |
+
print("Percorsi provati:", flush=True)
|
| 275 |
+
for c, e in errors:
|
| 276 |
+
print(" -", c, "=>", e[:200], flush=True)
|
| 277 |
+
print("Rilancia passando il percorso esatto, esempio:", flush=True)
|
| 278 |
+
print("hf jobs uv run --flavor h200 --secrets HF_TOKEN 10_sft_1b_pro_50k.py --base-ckpt training-runs/TUO_PERCORSO/checkpoint_last.pt", flush=True)
|
| 279 |
+
raise FileNotFoundError("Base checkpoint not found")
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
# ==========================================================
|
| 283 |
+
# DATASET FORMAT
|
| 284 |
+
# ==========================================================
|
| 285 |
+
|
| 286 |
+
def pick(row, names):
|
| 287 |
+
for n in names:
|
| 288 |
+
if n in row and row[n] is not None:
|
| 289 |
+
v = row[n]
|
| 290 |
+
if isinstance(v, str) and v.strip():
|
| 291 |
+
return v.strip()
|
| 292 |
+
return ""
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def make_prompt_and_answer(row):
|
| 296 |
+
# Robust for common instruct schemas.
|
| 297 |
+
instruction = pick(row, ["instruction", "prompt", "question", "input", "query", "user"])
|
| 298 |
+
context = pick(row, ["context", "system", "source"])
|
| 299 |
+
answer = pick(row, ["output", "response", "answer", "completion", "assistant", "text"])
|
| 300 |
+
|
| 301 |
+
# If dataset has only text, split very lightly or train whole text.
|
| 302 |
+
if not answer and "text" in row and isinstance(row["text"], str):
|
| 303 |
+
txt = row["text"].strip()
|
| 304 |
+
return "", txt
|
| 305 |
+
|
| 306 |
+
if context:
|
| 307 |
+
prompt = f"### Instruction:\\n{instruction}\\n\\n### Context:\\n{context}\\n\\n### Response:\\n"
|
| 308 |
+
else:
|
| 309 |
+
prompt = f"### Instruction:\\n{instruction}\\n\\n### Response:\\n"
|
| 310 |
+
|
| 311 |
+
return prompt, answer
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def encode_example(tok, row, block_size):
|
| 315 |
+
prompt, answer = make_prompt_and_answer(row)
|
| 316 |
+
|
| 317 |
+
# Train only the answer where possible.
|
| 318 |
+
full = prompt + answer + tok.eos_token
|
| 319 |
+
|
| 320 |
+
full_ids = tok.encode(full)
|
| 321 |
+
prompt_ids = tok.encode(prompt)
|
| 322 |
+
|
| 323 |
+
if len(full_ids) > block_size + 1:
|
| 324 |
+
# Keep the end, but avoid losing all answer labels where possible.
|
| 325 |
+
full_ids = full_ids[-(block_size + 1):]
|
| 326 |
+
# If prompt got truncated, no prompt mask.
|
| 327 |
+
prompt_len = 0
|
| 328 |
+
else:
|
| 329 |
+
prompt_len = min(len(prompt_ids), len(full_ids))
|
| 330 |
+
|
| 331 |
+
x = full_ids[:-1]
|
| 332 |
+
y = full_ids[1:]
|
| 333 |
+
|
| 334 |
+
# Mask prompt labels. y position i predicts full_ids[i+1].
|
| 335 |
+
# If i+1 is still inside prompt, ignore.
|
| 336 |
+
labels = []
|
| 337 |
+
for i, target in enumerate(y):
|
| 338 |
+
if (i + 1) < prompt_len:
|
| 339 |
+
labels.append(-100)
|
| 340 |
+
else:
|
| 341 |
+
labels.append(target)
|
| 342 |
+
|
| 343 |
+
return x, labels
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
class SFTDataset:
|
| 347 |
+
def __init__(self, rows, tok, block_size):
|
| 348 |
+
self.items = []
|
| 349 |
+
self.tok = tok
|
| 350 |
+
self.block_size = block_size
|
| 351 |
+
|
| 352 |
+
for row in rows:
|
| 353 |
+
x, y = encode_example(tok, row, block_size)
|
| 354 |
+
if len(x) >= 8 and any(t != -100 for t in y):
|
| 355 |
+
self.items.append((x, y))
|
| 356 |
+
|
| 357 |
+
if not self.items:
|
| 358 |
+
raise RuntimeError("No usable SFT examples found. Check dataset columns.")
|
| 359 |
+
|
| 360 |
+
def __len__(self):
|
| 361 |
+
return len(self.items)
|
| 362 |
+
|
| 363 |
+
def batch(self, batch_size, dev):
|
| 364 |
+
idxs = np.random.randint(0, len(self.items), size=(batch_size,))
|
| 365 |
+
xs, ys = [], []
|
| 366 |
+
max_len = 0
|
| 367 |
+
for i in idxs:
|
| 368 |
+
x, y = self.items[i]
|
| 369 |
+
max_len = max(max_len, len(x))
|
| 370 |
+
xs.append(x)
|
| 371 |
+
ys.append(y)
|
| 372 |
+
|
| 373 |
+
max_len = min(max_len, self.block_size)
|
| 374 |
+
pad_id = self.tok.eos_token_id
|
| 375 |
+
|
| 376 |
+
bx, by = [], []
|
| 377 |
+
for x, y in zip(xs, ys):
|
| 378 |
+
x = x[-max_len:]
|
| 379 |
+
y = y[-max_len:]
|
| 380 |
+
pad = max_len - len(x)
|
| 381 |
+
bx.append([pad_id] * pad + x)
|
| 382 |
+
by.append([-100] * pad + y)
|
| 383 |
+
|
| 384 |
+
return (
|
| 385 |
+
torch.tensor(bx, dtype=torch.long, device=dev),
|
| 386 |
+
torch.tensor(by, dtype=torch.long, device=dev),
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
@torch.no_grad()
|
| 391 |
+
def eval_model(model, data, a, dev, dt):
|
| 392 |
+
model.eval()
|
| 393 |
+
losses = []
|
| 394 |
+
for _ in range(a.eval_batches):
|
| 395 |
+
x, y = data.batch(a.batch_size, dev)
|
| 396 |
+
with ac(dev, dt):
|
| 397 |
+
_, loss = model(x, y)
|
| 398 |
+
losses.append(float(loss.item()))
|
| 399 |
+
model.train()
|
| 400 |
+
return float(np.mean(losses))
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
@torch.no_grad()
|
| 404 |
+
def quick_generate(model, tok, prompt, dev, dt, max_new=120):
|
| 405 |
+
model.eval()
|
| 406 |
+
ids = torch.tensor([tok.encode(prompt)], dtype=torch.long, device=dev)
|
| 407 |
+
for _ in range(max_new):
|
| 408 |
+
x = ids[:, -model.cfg.block_size:]
|
| 409 |
+
with ac(dev, dt):
|
| 410 |
+
logits, _ = model(x)
|
| 411 |
+
logits = logits[:, -1, :].float() / 0.8
|
| 412 |
+
probs = torch.softmax(logits, dim=-1)
|
| 413 |
+
sorted_probs, sorted_idx = torch.sort(probs, descending=True)
|
| 414 |
+
cum = torch.cumsum(sorted_probs, dim=-1)
|
| 415 |
+
mask = cum > 0.9
|
| 416 |
+
mask[..., 1:] = mask[..., :-1].clone()
|
| 417 |
+
mask[..., 0] = False
|
| 418 |
+
sorted_probs[mask] = 0
|
| 419 |
+
sorted_probs = sorted_probs / sorted_probs.sum(dim=-1, keepdim=True)
|
| 420 |
+
sample = torch.multinomial(sorted_probs, 1)
|
| 421 |
+
nxt = sorted_idx.gather(-1, sample)
|
| 422 |
+
ids = torch.cat([ids, nxt], dim=1)
|
| 423 |
+
if int(nxt.item()) == tok.eos_token_id:
|
| 424 |
+
break
|
| 425 |
+
return tok.decode(ids[0].tolist(), skip_special_tokens=True)
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
# ==========================================================
|
| 431 |
+
# SCUGNIZZ AGENT MODE - ROUTER + MOCK TOOLS
|
| 432 |
+
# ==========================================================
|
| 433 |
+
|
| 434 |
+
AGENT_CITIES = [
|
| 435 |
+
"Udine","Trieste","Genova","Roma","Milano","Napoli","Torino","Bologna","Palermo","Cagliari",
|
| 436 |
+
"Verona","Bari","Lecce","Ancona","Aosta","Trento","Pordenone","Gorizia","Firenze","Padova"
|
| 437 |
+
]
|
| 438 |
+
AGENT_STOCKS = ["TSLA","AAPL","MSFT","NVDA","AMD","VWCE","SPY","ENI","ISP","GOOGL","AMZN","META","BTC","ETH"]
|
| 439 |
+
|
| 440 |
+
def agent_json(obj):
|
| 441 |
+
return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
|
| 442 |
+
|
| 443 |
+
def normalize_text(s):
|
| 444 |
+
return re.sub(r"\s+", " ", (s or "").strip())
|
| 445 |
+
|
| 446 |
+
def find_city(text):
|
| 447 |
+
low = text.lower()
|
| 448 |
+
for c in AGENT_CITIES:
|
| 449 |
+
if c.lower() in low:
|
| 450 |
+
return c
|
| 451 |
+
m = re.search(r"\b(?:a|di|per|in)\s+([A-ZÀ-Ü][a-zà-ü]+)", text)
|
| 452 |
+
return m.group(1) if m else None
|
| 453 |
+
|
| 454 |
+
def find_stock(text):
|
| 455 |
+
upper = text.upper()
|
| 456 |
+
for s in AGENT_STOCKS:
|
| 457 |
+
if re.search(rf"\b{re.escape(s)}\b", upper):
|
| 458 |
+
return s
|
| 459 |
+
m = re.search(r"\b([A-Z]{2,5})\b", upper)
|
| 460 |
+
return m.group(1) if m else None
|
| 461 |
+
|
| 462 |
+
def find_domain(text):
|
| 463 |
+
m = re.search(r"\b((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})\b", text)
|
| 464 |
+
return m.group(1).lower() if m else None
|
| 465 |
+
|
| 466 |
+
def find_ip(text):
|
| 467 |
+
m = re.search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text)
|
| 468 |
+
return m.group(0) if m else None
|
| 469 |
+
|
| 470 |
+
def find_url(text):
|
| 471 |
+
m = re.search(r"https?://[^\s]+", text)
|
| 472 |
+
return m.group(0).rstrip(".,)") if m else None
|
| 473 |
+
|
| 474 |
+
def find_hash(text):
|
| 475 |
+
m = re.search(r"\b[a-fA-F0-9]{32}\b|\b[a-fA-F0-9]{40}\b|\b[a-fA-F0-9]{64}\b", text)
|
| 476 |
+
return m.group(0) if m else None
|
| 477 |
+
|
| 478 |
+
def route_user_to_tool(text):
|
| 479 |
+
t = normalize_text(text)
|
| 480 |
+
low = t.lower()
|
| 481 |
+
|
| 482 |
+
if any(k in low for k in ["meteo", "tempo fa", "weather", "prevision"]):
|
| 483 |
+
city = find_city(t)
|
| 484 |
+
if city:
|
| 485 |
+
return {"name": "weather.forecast", "arguments": {"city": city}}
|
| 486 |
+
return {"error": "Mi serve la città per usare il tool meteo."}
|
| 487 |
+
|
| 488 |
+
if any(k in low for k in ["quota", "quotazione", "prezzo", "stock", "azioni", "etf", "quote"]):
|
| 489 |
+
sym = find_stock(t)
|
| 490 |
+
if sym:
|
| 491 |
+
return {"name": "finance.quote", "arguments": {"symbol": sym}}
|
| 492 |
+
return {"error": "Mi serve il simbolo del titolo/ETF per usare il tool finanza."}
|
| 493 |
+
|
| 494 |
+
if any(k in low for k in ["canzone", "brano", "sto ascoltando", "current song", "musica"]):
|
| 495 |
+
return {"name": "spotify.current_song", "arguments": {}}
|
| 496 |
+
|
| 497 |
+
if any(k in low for k in ["appuntamento", "calendario", "agenda", "evento", "meeting"]):
|
| 498 |
+
return {"name": "calendar.next_event", "arguments": {}}
|
| 499 |
+
|
| 500 |
+
if any(k in low for k in ["email non lette", "mail non lette", "unread", "posta non letta"]):
|
| 501 |
+
return {"name": "unread_mail_count", "arguments": {}}
|
| 502 |
+
|
| 503 |
+
if any(k in low for k in ["dns", "record"]):
|
| 504 |
+
d = find_domain(t)
|
| 505 |
+
record = "A"
|
| 506 |
+
for rtype in ["AAAA", "TXT", "MX", "NS", "A"]:
|
| 507 |
+
if re.search(rf"\b{rtype}\b", t.upper()):
|
| 508 |
+
record = rtype
|
| 509 |
+
break
|
| 510 |
+
if d:
|
| 511 |
+
return {"name": "dns.lookup", "arguments": {"domain": d, "record_type": record}}
|
| 512 |
+
return {"error": "Mi serve il dominio per il lookup DNS."}
|
| 513 |
+
|
| 514 |
+
if any(k in low for k in ["dominio", "domain", "whois", "osint"]):
|
| 515 |
+
d = find_domain(t)
|
| 516 |
+
if d:
|
| 517 |
+
return {"name": "osint.domain_lookup", "arguments": {"domain": d}}
|
| 518 |
+
return {"error": "Mi serve il dominio da analizzare."}
|
| 519 |
+
|
| 520 |
+
if any(k in low for k in ["indirizzo ip", "lookup ip", "ip "]):
|
| 521 |
+
ip = find_ip(t)
|
| 522 |
+
if ip:
|
| 523 |
+
return {"name": "ip.lookup", "arguments": {"ip": ip}}
|
| 524 |
+
return {"error": "Mi serve un indirizzo IP valido."}
|
| 525 |
+
|
| 526 |
+
if any(k in low for k in ["url", "link", "scansiona", "reputazione"]):
|
| 527 |
+
u = find_url(t)
|
| 528 |
+
if u:
|
| 529 |
+
return {"name": "url.scan", "arguments": {"url": u}}
|
| 530 |
+
return {"error": "Mi serve un URL completo da scansionare."}
|
| 531 |
+
|
| 532 |
+
if any(k in low for k in ["hash", "sha256", "md5", "sha1"]):
|
| 533 |
+
h = find_hash(t)
|
| 534 |
+
if h:
|
| 535 |
+
return {"name": "hash.lookup", "arguments": {"hash": h}}
|
| 536 |
+
return {"error": "Mi serve un hash valido."}
|
| 537 |
+
|
| 538 |
+
if any(k in low for k in ["sensore", "porta", "garage", "luce", "termostato", "telecamera"]):
|
| 539 |
+
room = None
|
| 540 |
+
device = None
|
| 541 |
+
for r in ["garage", "cucina", "salotto", "camera", "studio", "ingresso", "cantina", "terrazzo"]:
|
| 542 |
+
if r in low:
|
| 543 |
+
room = r
|
| 544 |
+
break
|
| 545 |
+
for d in ["sensore porta", "porta ingresso", "luce", "termostato", "telecamera", "sirena", "presa smart"]:
|
| 546 |
+
if d in low:
|
| 547 |
+
device = d
|
| 548 |
+
break
|
| 549 |
+
if device:
|
| 550 |
+
args = {"device": device}
|
| 551 |
+
if room:
|
| 552 |
+
args["room"] = room
|
| 553 |
+
return {"name": "home.sensor", "arguments": args}
|
| 554 |
+
|
| 555 |
+
return None
|
| 556 |
+
|
| 557 |
+
def execute_mock_tool(call):
|
| 558 |
+
name = call.get("name")
|
| 559 |
+
args = call.get("arguments") or {}
|
| 560 |
+
|
| 561 |
+
if name == "weather.forecast":
|
| 562 |
+
city = args.get("city", "Genova")
|
| 563 |
+
table = {
|
| 564 |
+
"Genova": ("pioggia", 24, 18),
|
| 565 |
+
"Udine": ("nuvoloso", 23, 9),
|
| 566 |
+
"Roma": ("sereno", 31, 8),
|
| 567 |
+
"Milano": ("caldo", 30, 7),
|
| 568 |
+
"Trento": ("neve", -2, 18),
|
| 569 |
+
}
|
| 570 |
+
cond, temp, wind = table.get(city, ("sereno", 25, 10))
|
| 571 |
+
return {"tool": name, "result": {"city": city, "condition": cond, "temperature_c": temp, "wind_kmh": wind}}
|
| 572 |
+
|
| 573 |
+
if name == "finance.quote":
|
| 574 |
+
sym = args.get("symbol", "TSLA")
|
| 575 |
+
prices = {"TSLA": 312.45, "MSFT": 512.34, "AMD": 165.77, "NVDA": 142.10, "VWCE": 127.45}
|
| 576 |
+
return {"tool": name, "result": {"symbol": sym, "price": prices.get(sym, 100.00), "currency": "USD" if sym != "VWCE" else "EUR", "change_percent": 0.8}}
|
| 577 |
+
|
| 578 |
+
if name == "spotify.current_song":
|
| 579 |
+
return {"tool": name, "result": {"artist": "Muse", "title": "Uprising", "album": "The Resistance"}}
|
| 580 |
+
|
| 581 |
+
if name == "calendar.next_event":
|
| 582 |
+
return {"tool": name, "result": {"title": "Manutenzione", "date": "domani", "time": "15:45"}}
|
| 583 |
+
|
| 584 |
+
if name == "unread_mail_count":
|
| 585 |
+
return {"tool": name, "result": {"unread": 7, "important": 2, "latest_sender": "Anna"}}
|
| 586 |
+
|
| 587 |
+
if name == "osint.domain_lookup":
|
| 588 |
+
domain = args.get("domain", "example.com")
|
| 589 |
+
return {"tool": name, "result": {"domain": domain, "status": "mock", "risk": "unknown", "note": "Risultato dimostrativo locale"}}
|
| 590 |
+
|
| 591 |
+
if name == "dns.lookup":
|
| 592 |
+
return {"tool": name, "result": {"domain": args.get("domain"), "record_type": args.get("record_type", "A"), "answers": ["93.184.216.34"]}}
|
| 593 |
+
|
| 594 |
+
if name == "ip.lookup":
|
| 595 |
+
return {"tool": name, "result": {"ip": args.get("ip"), "asn": "AS15169", "country": "US", "provider": "mock"}}
|
| 596 |
+
|
| 597 |
+
if name == "url.scan":
|
| 598 |
+
return {"tool": name, "result": {"url": args.get("url"), "verdict": "unknown", "risk_score": 0, "note": "Mock scan locale"}}
|
| 599 |
+
|
| 600 |
+
if name == "hash.lookup":
|
| 601 |
+
return {"tool": name, "result": {"hash": args.get("hash"), "verdict": "unknown", "engines_detected": 0}}
|
| 602 |
+
|
| 603 |
+
if name == "home.sensor":
|
| 604 |
+
return {"tool": name, "result": {"device": args.get("device", "sensore"), "room": args.get("room", "ingresso"), "state": "aperta"}}
|
| 605 |
+
|
| 606 |
+
return {"tool": name or "unknown", "error": "Tool non implementato", "arguments": args}
|
| 607 |
+
|
| 608 |
+
def build_tool_result_prompt(tool_result):
|
| 609 |
+
return (
|
| 610 |
+
"### Instruction:\n"
|
| 611 |
+
"TOOL_RESULT:\n"
|
| 612 |
+
+ agent_json(tool_result)
|
| 613 |
+
+ "\n\nScrivi una risposta naturale in italiano usando solo i dati utili. Non inventare nulla.\n\n"
|
| 614 |
+
"### Response:\n"
|
| 615 |
+
)
|
| 616 |
+
|
| 617 |
+
# ==========================================================
|
| 618 |
+
# SCUGNIZZLLM CHAT GUI
|
| 619 |
+
# ==========================================================
|
| 620 |
+
import threading
|
| 621 |
+
import tkinter as tk
|
| 622 |
+
from tkinter import ttk, scrolledtext, messagebox, filedialog
|
| 623 |
+
|
| 624 |
+
CHECKPOINTS = {
|
| 625 |
+
"Toolcalling only best": "training-runs/sft-toolcalling-only/checkpoint_best.pt",
|
| 626 |
+
"Toolcalling only last": "training-runs/sft-toolcalling-only/checkpoint_last.pt",
|
| 627 |
+
"V3 agentic smart mix best": "training-runs/sft-universal-tool-renderer-1b-v3-agentic-smart-mix/checkpoint_best.pt",
|
| 628 |
+
"SFT 1.32B best": "training-runs/sft-1b-pro-50k-1320m/checkpoint_best.pt",
|
| 629 |
+
"SFT 1.32B last": "training-runs/sft-1b-pro-50k-1320m/checkpoint_last.pt",
|
| 630 |
+
"SFT 1B best": "training-runs/sft-1b-pro-50k/checkpoint_best.pt",
|
| 631 |
+
"SFT 1B last": "training-runs/sft-1b-pro-50k/checkpoint_last.pt",
|
| 632 |
+
"Pretrain 1.32B last": "training-runs/pretrain-1b/checkpoint_last.pt",
|
| 633 |
+
}
|
| 634 |
+
REPO_ID = "ProjectScugnizz/scugnizz-1b"
|
| 635 |
+
|
| 636 |
+
@torch.no_grad()
|
| 637 |
+
def sample_next_token(logits, temperature=0.7, top_p=0.9, top_k=50, repetition_penalty=1.10, recent_tokens=None):
|
| 638 |
+
logits = logits.float()
|
| 639 |
+
if recent_tokens and repetition_penalty and repetition_penalty > 1.0:
|
| 640 |
+
for t in set(recent_tokens[-128:]):
|
| 641 |
+
logits[:, t] /= repetition_penalty
|
| 642 |
+
if temperature <= 0:
|
| 643 |
+
return torch.argmax(logits, dim=-1, keepdim=True)
|
| 644 |
+
logits = logits / max(temperature, 1e-6)
|
| 645 |
+
if top_k and top_k > 0:
|
| 646 |
+
vals, idx = torch.topk(logits, min(top_k, logits.size(-1)))
|
| 647 |
+
mask = torch.full_like(logits, float("-inf"))
|
| 648 |
+
logits = mask.scatter(-1, idx, vals)
|
| 649 |
+
probs = torch.softmax(logits, dim=-1)
|
| 650 |
+
if top_p and top_p < 1.0:
|
| 651 |
+
sorted_probs, sorted_idx = torch.sort(probs, descending=True)
|
| 652 |
+
cum = torch.cumsum(sorted_probs, dim=-1)
|
| 653 |
+
mask = cum > top_p
|
| 654 |
+
mask[..., 1:] = mask[..., :-1].clone()
|
| 655 |
+
mask[..., 0] = False
|
| 656 |
+
sorted_probs[mask] = 0
|
| 657 |
+
sorted_probs = sorted_probs / sorted_probs.sum(dim=-1, keepdim=True)
|
| 658 |
+
sample = torch.multinomial(sorted_probs, 1)
|
| 659 |
+
return sorted_idx.gather(-1, sample)
|
| 660 |
+
return torch.multinomial(probs, 1)
|
| 661 |
+
|
| 662 |
+
class ScugnizzEngine:
|
| 663 |
+
def __init__(self):
|
| 664 |
+
self.model = None
|
| 665 |
+
self.tok = None
|
| 666 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 667 |
+
self.dt = dtype_of("auto", "cuda" if torch.cuda.is_available() else "cpu")
|
| 668 |
+
self.loaded_name = None
|
| 669 |
+
def load(self, checkpoint_name):
|
| 670 |
+
ckpt_file = CHECKPOINTS[checkpoint_name]
|
| 671 |
+
self.tok = GPT2TokenizerFast.from_pretrained("gpt2")
|
| 672 |
+
self.tok.pad_token = self.tok.eos_token
|
| 673 |
+
path = hf_hub_download(repo_id=REPO_ID, filename=ckpt_file, repo_type="model")
|
| 674 |
+
ck = torch.load(path, map_location="cpu")
|
| 675 |
+
if isinstance(ck, dict) and "config" in ck:
|
| 676 |
+
cfg = GPTConfig(**ck["config"])
|
| 677 |
+
else:
|
| 678 |
+
cfg = GPTConfig(vocab_size=self.tok.vocab_size, block_size=1024, n_layer=24, n_head=16, n_embd=2048, dropout=0.0, bias=False, pcs_a=0.8309193524478643, pcs_b=0.0)
|
| 679 |
+
cfg.dropout = 0.0
|
| 680 |
+
model = GPT(cfg)
|
| 681 |
+
sd = ck["model"] if isinstance(ck, dict) and "model" in ck else ck
|
| 682 |
+
if any(k.startswith("module.") for k in sd.keys()):
|
| 683 |
+
sd = {k.replace("module.", "", 1): v for k, v in sd.items()}
|
| 684 |
+
model.load_state_dict(sd, strict=True)
|
| 685 |
+
model.to(self.device)
|
| 686 |
+
model.eval()
|
| 687 |
+
self.model = model
|
| 688 |
+
self.loaded_name = checkpoint_name
|
| 689 |
+
def build_prompt(self, history, user_msg):
|
| 690 |
+
text = ""
|
| 691 |
+
for role, msg in history[-8:]:
|
| 692 |
+
if role == "user":
|
| 693 |
+
text += f"### Instruction:\n{msg}\n\n### Response:\n"
|
| 694 |
+
else:
|
| 695 |
+
text += f"{msg}\n\n"
|
| 696 |
+
text += f"### Instruction:\n{user_msg}\n\n### Response:\n"
|
| 697 |
+
return text
|
| 698 |
+
@torch.no_grad()
|
| 699 |
+
def generate_stream(self, prompt, max_new=256, temperature=0.7, top_p=0.9, top_k=50, repetition_penalty=1.10, stop_flag=None):
|
| 700 |
+
if self.model is None:
|
| 701 |
+
raise RuntimeError("Modello non caricato.")
|
| 702 |
+
ids = torch.tensor([self.tok.encode(prompt)], dtype=torch.long, device=self.device)
|
| 703 |
+
generated = []
|
| 704 |
+
recent = ids[0].tolist()[-128:]
|
| 705 |
+
for _ in range(max_new):
|
| 706 |
+
if stop_flag and stop_flag():
|
| 707 |
+
break
|
| 708 |
+
x = ids[:, -self.model.cfg.block_size:]
|
| 709 |
+
with ac("cuda" if torch.cuda.is_available() else "cpu", self.dt):
|
| 710 |
+
logits, _ = self.model(x)
|
| 711 |
+
nxt = sample_next_token(logits[:, -1, :], temperature=temperature, top_p=top_p, top_k=top_k, repetition_penalty=repetition_penalty, recent_tokens=recent)
|
| 712 |
+
token_id = int(nxt.item())
|
| 713 |
+
if token_id == self.tok.eos_token_id:
|
| 714 |
+
break
|
| 715 |
+
ids = torch.cat([ids, nxt], dim=1)
|
| 716 |
+
recent.append(token_id)
|
| 717 |
+
generated.append(token_id)
|
| 718 |
+
piece = self.tok.decode([token_id], skip_special_tokens=True)
|
| 719 |
+
yield piece
|
| 720 |
+
current = self.tok.decode(generated, skip_special_tokens=True)
|
| 721 |
+
if "\n### Instruction:" in current or "\n### Response:" in current:
|
| 722 |
+
break
|
| 723 |
+
|
| 724 |
+
class ChatApp:
|
| 725 |
+
def __init__(self, root):
|
| 726 |
+
self.root = root
|
| 727 |
+
self.root.title("ScugnizzLLM Chat")
|
| 728 |
+
self.root.geometry("1100x760")
|
| 729 |
+
self.root.configure(bg="#151515")
|
| 730 |
+
self.engine = ScugnizzEngine()
|
| 731 |
+
self.history = []
|
| 732 |
+
self.stop = False
|
| 733 |
+
self.generating = False
|
| 734 |
+
self.setup_style()
|
| 735 |
+
self.build_ui()
|
| 736 |
+
def setup_style(self):
|
| 737 |
+
style = ttk.Style()
|
| 738 |
+
try: style.theme_use("clam")
|
| 739 |
+
except Exception: pass
|
| 740 |
+
style.configure("TFrame", background="#151515")
|
| 741 |
+
style.configure("TLabel", background="#151515", foreground="#e8e8e8")
|
| 742 |
+
style.configure("TButton", padding=6)
|
| 743 |
+
def build_ui(self):
|
| 744 |
+
top = ttk.Frame(self.root); top.pack(fill="x", padx=10, pady=8)
|
| 745 |
+
ttk.Label(top, text="Checkpoint:").pack(side="left")
|
| 746 |
+
self.ckpt_var = tk.StringVar(value="V3 agentic smart mix best")
|
| 747 |
+
ttk.Combobox(top, textvariable=self.ckpt_var, values=list(CHECKPOINTS.keys()), width=32, state="readonly").pack(side="left", padx=6)
|
| 748 |
+
self.load_btn = ttk.Button(top, text="Carica modello", command=self.load_model); self.load_btn.pack(side="left", padx=4)
|
| 749 |
+
ttk.Button(top, text="Nuova chat", command=self.new_chat).pack(side="left", padx=4)
|
| 750 |
+
ttk.Button(top, text="Salva chat", command=self.save_chat).pack(side="left", padx=4)
|
| 751 |
+
self.status = ttk.Label(top, text=f"Device: {self.engine.device}"); self.status.pack(side="right")
|
| 752 |
+
params = ttk.Frame(self.root); params.pack(fill="x", padx=10, pady=4)
|
| 753 |
+
self.temp=tk.DoubleVar(value=0.7); self.topp=tk.DoubleVar(value=0.9); self.topk=tk.IntVar(value=50); self.maxnew=tk.IntVar(value=256); self.rep=tk.DoubleVar(value=1.10); self.agent_mode=tk.BooleanVar(value=True); self.show_tool_log=tk.BooleanVar(value=True)
|
| 754 |
+
for label,var in [("Temp",self.temp),("Top-p",self.topp),("Top-k",self.topk),("Max token",self.maxnew),("Repetition",self.rep)]:
|
| 755 |
+
f=ttk.Frame(params); f.pack(side="left", padx=8); ttk.Label(f,text=label).pack(); ttk.Entry(f,textvariable=var,width=8).pack()
|
| 756 |
+
ttk.Checkbutton(params, text="Agent mode", variable=self.agent_mode).pack(side="left", padx=10)
|
| 757 |
+
ttk.Checkbutton(params, text="Tool log", variable=self.show_tool_log).pack(side="left", padx=10)
|
| 758 |
+
self.chat = scrolledtext.ScrolledText(self.root, wrap="word", bg="#101010", fg="#eeeeee", insertbackground="#ffffff", font=("Consolas", 11), padx=12, pady=12)
|
| 759 |
+
self.chat.pack(fill="both", expand=True, padx=10, pady=8)
|
| 760 |
+
self.chat.tag_config("user", foreground="#8fd3ff"); self.chat.tag_config("bot", foreground="#b6ffb6"); self.chat.tag_config("sys", foreground="#ffdf80"); self.chat.tag_config("tool", foreground="#ff9bd5")
|
| 761 |
+
self.append("ScugnizzLLM Chat pronto. Carica un checkpoint e scrivi una domanda.\n", "sys")
|
| 762 |
+
bottom = ttk.Frame(self.root); bottom.pack(fill="x", padx=10, pady=8)
|
| 763 |
+
self.input = tk.Text(bottom, height=4, bg="#202020", fg="#ffffff", insertbackground="#ffffff", font=("Consolas", 11)); self.input.pack(side="left", fill="x", expand=True)
|
| 764 |
+
self.input.bind("<Control-Return>", lambda e: self.send())
|
| 765 |
+
btns=ttk.Frame(bottom); btns.pack(side="right", padx=8)
|
| 766 |
+
self.send_btn=ttk.Button(btns, text="Invia\nCtrl+Invio", command=self.send); self.send_btn.pack(fill="x", pady=2)
|
| 767 |
+
ttk.Button(btns, text="Stop", command=self.stop_generation).pack(fill="x", pady=2)
|
| 768 |
+
def append(self, text, tag=None):
|
| 769 |
+
self.chat.insert("end", text, tag); self.chat.see("end"); self.root.update_idletasks()
|
| 770 |
+
def load_model(self):
|
| 771 |
+
name=self.ckpt_var.get(); self.status.config(text=f"Carico {name}..."); self.load_btn.config(state="disabled")
|
| 772 |
+
def worker():
|
| 773 |
+
try:
|
| 774 |
+
self.engine.load(name)
|
| 775 |
+
self.root.after(0, lambda: self.append(f"\n[OK] Modello caricato: {name}\n", "sys"))
|
| 776 |
+
self.root.after(0, lambda: self.status.config(text=f"Caricato: {name} | {self.engine.device}"))
|
| 777 |
+
except Exception as e:
|
| 778 |
+
self.root.after(0, lambda: messagebox.showerror("Errore caricamento", str(e)))
|
| 779 |
+
self.root.after(0, lambda: self.status.config(text="Errore caricamento"))
|
| 780 |
+
finally:
|
| 781 |
+
self.root.after(0, lambda: self.load_btn.config(state="normal"))
|
| 782 |
+
threading.Thread(target=worker, daemon=True).start()
|
| 783 |
+
|
| 784 |
+
def generate_text_blocking(self, prompt, max_new=None, temperature=None):
|
| 785 |
+
answer = ""
|
| 786 |
+
for piece in self.engine.generate_stream(
|
| 787 |
+
prompt,
|
| 788 |
+
max_new=int(max_new if max_new is not None else self.maxnew.get()),
|
| 789 |
+
temperature=float(temperature if temperature is not None else self.temp.get()),
|
| 790 |
+
top_p=float(self.topp.get()),
|
| 791 |
+
top_k=int(self.topk.get()),
|
| 792 |
+
repetition_penalty=float(self.rep.get()),
|
| 793 |
+
stop_flag=lambda: self.stop,
|
| 794 |
+
):
|
| 795 |
+
answer += piece
|
| 796 |
+
return answer.strip()
|
| 797 |
+
|
| 798 |
+
def run_agent_mode(self, user_msg):
|
| 799 |
+
routed = route_user_to_tool(user_msg)
|
| 800 |
+
|
| 801 |
+
if isinstance(routed, dict) and "error" in routed:
|
| 802 |
+
return routed["error"], "[router] " + routed["error"]
|
| 803 |
+
|
| 804 |
+
if isinstance(routed, dict) and "name" in routed:
|
| 805 |
+
tool_result = execute_mock_tool(routed)
|
| 806 |
+
tool_log = (
|
| 807 |
+
"[router] TOOL_CALL "
|
| 808 |
+
+ json.dumps(routed, ensure_ascii=False)
|
| 809 |
+
+ "\nTOOL_RESULT "
|
| 810 |
+
+ json.dumps(tool_result, ensure_ascii=False)
|
| 811 |
+
)
|
| 812 |
+
final_prompt = build_tool_result_prompt(tool_result)
|
| 813 |
+
final_answer = self.generate_text_blocking(final_prompt, max_new=int(self.maxnew.get()), temperature=0.05)
|
| 814 |
+
if not final_answer:
|
| 815 |
+
final_answer = json.dumps(tool_result, ensure_ascii=False)
|
| 816 |
+
return final_answer, tool_log
|
| 817 |
+
|
| 818 |
+
# Nessun tool: normale chat col modello
|
| 819 |
+
normal_prompt = self.engine.build_prompt(self.history, user_msg)
|
| 820 |
+
final_answer = self.generate_text_blocking(normal_prompt)
|
| 821 |
+
return final_answer, "[agent] Nessun tool necessario."
|
| 822 |
+
|
| 823 |
+
def send(self):
|
| 824 |
+
if self.generating:
|
| 825 |
+
return
|
| 826 |
+
msg = self.input.get("1.0", "end").strip()
|
| 827 |
+
if not msg:
|
| 828 |
+
return
|
| 829 |
+
if self.engine.model is None:
|
| 830 |
+
messagebox.showwarning("Modello non caricato", "Prima carica un checkpoint.")
|
| 831 |
+
return
|
| 832 |
+
|
| 833 |
+
self.input.delete("1.0", "end")
|
| 834 |
+
self.append(f"\nTu: {msg}\n", "user")
|
| 835 |
+
self.append("Scugnizz: ", "bot")
|
| 836 |
+
|
| 837 |
+
self.stop = False
|
| 838 |
+
self.generating = True
|
| 839 |
+
self.send_btn.config(state="disabled")
|
| 840 |
+
|
| 841 |
+
def worker():
|
| 842 |
+
answer = ""
|
| 843 |
+
try:
|
| 844 |
+
if self.agent_mode.get():
|
| 845 |
+
answer, tool_log = self.run_agent_mode(msg)
|
| 846 |
+
if self.show_tool_log.get() and tool_log:
|
| 847 |
+
self.root.after(0, lambda tl=tool_log: self.append("\n[TOOL LOG]\n" + tl + "\n\n", "tool"))
|
| 848 |
+
self.root.after(0, lambda a=answer: self.append(a, "bot"))
|
| 849 |
+
else:
|
| 850 |
+
prompt = self.engine.build_prompt(self.history, msg)
|
| 851 |
+
for piece in self.engine.generate_stream(
|
| 852 |
+
prompt,
|
| 853 |
+
max_new=int(self.maxnew.get()),
|
| 854 |
+
temperature=float(self.temp.get()),
|
| 855 |
+
top_p=float(self.topp.get()),
|
| 856 |
+
top_k=int(self.topk.get()),
|
| 857 |
+
repetition_penalty=float(self.rep.get()),
|
| 858 |
+
stop_flag=lambda: self.stop,
|
| 859 |
+
):
|
| 860 |
+
answer += piece
|
| 861 |
+
self.root.after(0, lambda p=piece: self.append(p, "bot"))
|
| 862 |
+
|
| 863 |
+
self.history.append(("user", msg))
|
| 864 |
+
self.history.append(("assistant", answer.strip()))
|
| 865 |
+
self.root.after(0, lambda: self.append("\n", "bot"))
|
| 866 |
+
|
| 867 |
+
except Exception as e:
|
| 868 |
+
self.root.after(0, lambda e=e: self.append(f"\n[ERRORE] {e}\n", "sys"))
|
| 869 |
+
finally:
|
| 870 |
+
self.generating = False
|
| 871 |
+
self.root.after(0, lambda: self.send_btn.config(state="normal"))
|
| 872 |
+
|
| 873 |
+
threading.Thread(target=worker, daemon=True).start()
|
| 874 |
+
|
| 875 |
+
def stop_generation(self): self.stop=True
|
| 876 |
+
def new_chat(self): self.history.clear(); self.chat.delete("1.0", "end"); self.append("Nuova chat.\n", "sys")
|
| 877 |
+
def save_chat(self):
|
| 878 |
+
path=filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text", "*.txt"), ("Markdown", "*.md")])
|
| 879 |
+
if path:
|
| 880 |
+
Path(path).write_text(self.chat.get("1.0", "end"), encoding="utf-8"); self.append(f"\n[OK] Chat salvata: {path}\n", "sys")
|
| 881 |
+
|
| 882 |
+
if __name__ == "__main__":
|
| 883 |
+
root = tk.Tk(); app = ChatApp(root); root.mainloop()
|