| |
| |
| |
| |
| |
|
|
| import argparse, math, json |
| from dataclasses import dataclass |
| from contextlib import nullcontext |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from transformers import GPT2TokenizerFast |
| from huggingface_hub import hf_hub_download |
|
|
|
|
| @dataclass |
| class GPTConfig: |
| vocab_size:int |
| block_size:int |
| n_layer:int |
| n_head:int |
| n_embd:int |
| dropout:float=0.0 |
| bias:bool=False |
| pcs_a:float=0.8309193524478643 |
| pcs_b:float=0.0 |
|
|
|
|
| class PCS(nn.Module): |
| def __init__(self,a=0.8309193524478643,b=0.0): |
| super().__init__() |
| self.a=float(a) |
| self.b=float(b) |
| def forward(self,x): |
| return x*torch.sin(self.a*x)+self.b*torch.cos(x) |
|
|
|
|
| class Attn(nn.Module): |
| def __init__(self,c): |
| super().__init__() |
| assert c.n_embd % c.n_head == 0 |
| self.n_head=c.n_head |
| self.head_dim=c.n_embd//c.n_head |
| self.dropout=c.dropout |
| self.qkv=nn.Linear(c.n_embd,3*c.n_embd,bias=c.bias) |
| self.proj=nn.Linear(c.n_embd,c.n_embd,bias=c.bias) |
| self.drop=nn.Dropout(c.dropout) |
| def forward(self,x): |
| B,T,C=x.shape |
| q,k,v=self.qkv(x).split(C,dim=2) |
| q=q.view(B,T,self.n_head,self.head_dim).transpose(1,2) |
| k=k.view(B,T,self.n_head,self.head_dim).transpose(1,2) |
| v=v.view(B,T,self.n_head,self.head_dim).transpose(1,2) |
| y=F.scaled_dot_product_attention(q,k,v,dropout_p=self.dropout if self.training else 0.0,is_causal=True) |
| y=y.transpose(1,2).contiguous().view(B,T,C) |
| return self.drop(self.proj(y)) |
|
|
|
|
| class MLP(nn.Module): |
| def __init__(self,c): |
| super().__init__() |
| self.fc1=nn.Linear(c.n_embd,4*c.n_embd,bias=c.bias) |
| self.act=PCS(c.pcs_a,c.pcs_b) |
| self.fc2=nn.Linear(4*c.n_embd,c.n_embd,bias=c.bias) |
| self.drop=nn.Dropout(c.dropout) |
| def forward(self,x): |
| return self.drop(self.fc2(self.act(self.fc1(x)))) |
|
|
|
|
| class Block(nn.Module): |
| def __init__(self,c): |
| super().__init__() |
| self.ln1=nn.LayerNorm(c.n_embd) |
| self.attn=Attn(c) |
| self.ln2=nn.LayerNorm(c.n_embd) |
| self.mlp=MLP(c) |
| def forward(self,x): |
| x=x+self.attn(self.ln1(x)) |
| x=x+self.mlp(self.ln2(x)) |
| return x |
|
|
|
|
| class GPT(nn.Module): |
| def __init__(self,c): |
| super().__init__() |
| self.cfg=c |
| self.tok_emb=nn.Embedding(c.vocab_size,c.n_embd) |
| self.pos_emb=nn.Embedding(c.block_size,c.n_embd) |
| self.drop=nn.Dropout(c.dropout) |
| self.blocks=nn.ModuleList([Block(c) for _ in range(c.n_layer)]) |
| self.ln_f=nn.LayerNorm(c.n_embd) |
| self.lm_head=nn.Linear(c.n_embd,c.vocab_size,bias=False) |
| self.tok_emb.weight=self.lm_head.weight |
| def forward(self,idx): |
| B,T=idx.shape |
| if T>self.cfg.block_size: |
| idx=idx[:,-self.cfg.block_size:] |
| B,T=idx.shape |
| pos=torch.arange(T,device=idx.device) |
| x=self.drop(self.tok_emb(idx)+self.pos_emb(pos)) |
| for b in self.blocks: |
| x=b(x) |
| return self.lm_head(self.ln_f(x)) |
|
|
|
|
| def dtype_of(dev): |
| if dev=="cuda" and torch.cuda.is_bf16_supported(): |
| return "bfloat16" |
| if dev=="cuda": |
| return "float16" |
| return "float32" |
|
|
|
|
| def ac(dev,dt): |
| if dev!="cuda" or dt=="float32": |
| return nullcontext() |
| return torch.amp.autocast("cuda", dtype=torch.bfloat16 if dt=="bfloat16" else torch.float16) |
|
|
|
|
| @torch.no_grad() |
| def generate(model,tok,prompt,dev,dt,max_new=220,temperature=0.05,top_p=0.80): |
| model.eval() |
| ids=torch.tensor([tok.encode(prompt)],dtype=torch.long,device=dev) |
| for _ in range(max_new): |
| x=ids[:,-model.cfg.block_size:] |
| with ac(dev,dt): |
| logits=model(x) |
| logits=logits[:,-1,:].float() |
| if temperature<=0: |
| nxt=torch.argmax(logits,dim=-1,keepdim=True) |
| else: |
| probs=torch.softmax(logits/max(1e-6,temperature),dim=-1) |
| sp,si=torch.sort(probs,descending=True) |
| cum=torch.cumsum(sp,dim=-1) |
| mask=cum>top_p |
| mask[...,1:]=mask[...,:-1].clone() |
| mask[...,0]=False |
| sp[mask]=0 |
| sp=sp/sp.sum(dim=-1,keepdim=True) |
| nxt=si.gather(-1,torch.multinomial(sp,1)) |
| ids=torch.cat([ids,nxt],dim=1) |
| if int(nxt.item())==tok.eos_token_id: |
| break |
| txt=tok.decode(ids[0].tolist(),skip_special_tokens=True) |
| return txt.split("### Response:",1)[-1].strip() if "### Response:" in txt else txt.strip() |
|
|
|
|
| def load_model(repo, ckpt_name, device): |
| print("Scarico checkpoint:", repo, ckpt_name, flush=True) |
| ckpt_path=hf_hub_download(repo_id=repo, filename=ckpt_name, repo_type="model") |
| print("Checkpoint locale:", ckpt_path, flush=True) |
|
|
| ck=torch.load(ckpt_path,map_location="cpu") |
| tok=GPT2TokenizerFast.from_pretrained("gpt2") |
| tok.pad_token=tok.eos_token |
|
|
| cfg=GPTConfig(**ck["config"]) if isinstance(ck,dict) and "config" in ck else GPTConfig( |
| vocab_size=tok.vocab_size, block_size=1024, n_layer=24, n_head=16, n_embd=2048 |
| ) |
| cfg.dropout=0.0 |
| model=GPT(cfg) |
| sd=ck["model"] if isinstance(ck,dict) and "model" in ck else ck |
| if any(k.startswith("module.") for k in sd.keys()): |
| sd={k.replace("module.","",1):v for k,v in sd.items()} |
| model.load_state_dict(sd,strict=True) |
| model.to(device) |
| model.eval() |
| return model,tok |
|
|
|
|
| def P(body): |
| return "### Instruction:\n\n"+body.strip()+"\n\n### Response:\n" |
|
|
|
|
| PROMPTS=[ |
| """TOOL_RESULT: |
| {"tool":"weather.forecast","result":{"city":"Trento","condition":"neve","temperature_c":-2,"wind_kmh":18,"request_id":"abc123"}} |
| |
| Scrivi una risposta naturale in italiano usando solo i dati utili. Ignora request_id.""", |
|
|
| """TOOL_RESULT: |
| {"tool":"finance.quote","result":{"symbol":"MSFT","price":512.34,"currency":"USD","change_percent":-1.72}} |
| |
| Rispondi in italiano usando solo questi dati.""", |
|
|
| """TOOL_RESULT: |
| {"tool":"spotify.current_song","result":{"artist":"Coldplay","title":"Yellow","album":"Parachutes"}} |
| |
| Trasforma il risultato in una risposta naturale.""", |
|
|
| """CONTEXT: |
| Una password robusta deve essere lunga, unica e non riutilizzata. |
| |
| QUESTION: |
| Come deve essere una password sicura? |
| |
| Rispondi solo usando il contesto.""", |
|
|
| """CONTEXT: |
| Il documento descrive esclusivamente la regola 3-2-1 dei backup. |
| |
| QUESTION: |
| Chi ha fondato Microsoft? |
| |
| Rispondi solo usando il contesto.""", |
|
|
| """Trasforma questo JSON in una risposta naturale. |
| |
| { |
| "weather":{"city":"Bari","condition":"sereno","temperature_c":29}, |
| "mail":{"unread":9,"important":3,"latest_sender":"Giulia"}, |
| "calendar":{"title":"Riunione","date":"venerdì","time":"14:00"} |
| }""", |
|
|
| """TOOL_RESULT: |
| {"tool":"system.status","result":{"cpu":37,"ram":58,"disk":81}} |
| |
| Trasforma i dati in una frase naturale.""", |
|
|
| """Dati disponibili: |
| |
| - città: Palermo |
| - meteo: soleggiato |
| - temperatura: 34 |
| - email non lette: 11 |
| - importanti: 4 |
| |
| Scrivi una risposta naturale senza aggiungere informazioni.""", |
|
|
| """TOOL_RESULT: |
| {"tool":"calendar.next_event","result":{"title":"Audit","date":"martedì","time":"09:30"}} |
| |
| Rispondi in italiano.""", |
|
|
| """TOOL_RESULT: |
| {"tool":"home.sensor","result":{"device":"porta ingresso","state":"aperta"}} |
| |
| Scrivi una risposta naturale.""", |
|
|
| """### System: |
| You can call tools when needed. |
| Use only the available tool names and copy arguments exactly. |
| |
| Available tools: |
| - weather.forecast: Get current weather | required: city |
| |
| ### User: |
| Che tempo fa a Genova? |
| |
| ### Assistant:""", |
|
|
| """### System: |
| You can call tools when needed. |
| Use only the available tool names and copy arguments exactly. |
| |
| Available tools: |
| - finance.quote: Get stock quote | required: symbol |
| |
| ### User: |
| Quanto quota TSLA? |
| |
| ### Assistant:""", |
|
|
| """### System: |
| You can call tools when needed. |
| Use only the available tool names and copy arguments exactly. |
| |
| Available tools: |
| - spotify.current_song: Current song |
| |
| ### User: |
| Che canzone sta suonando? |
| |
| ### Assistant:""", |
|
|
| """### System: |
| You can call tools when needed. |
| Use only the available tool names and copy arguments exactly. |
| |
| Available tools: |
| - calendar.next_event: Next calendar event |
| |
| ### User: |
| Qual è il mio prossimo appuntamento? |
| |
| ### Assistant:""", |
|
|
| """### System: |
| You can call tools when needed. |
| Use only the available tool names and copy arguments exactly. |
| |
| Available tools: |
| - unread_mail_count: Count unread emails |
| |
| ### User: |
| Quante email non lette ho? |
| |
| ### Assistant:""", |
|
|
| """Trasforma questo JSON mantenendo tutti i valori. |
| |
| { |
| "home":{"garage":"chiuso","porta":"aperta"}, |
| "weather":{"city":"Aosta","condition":"vento","temperature_c":6}, |
| "mail":{"unread":2,"important":1} |
| }""", |
|
|
| """TOOL_RESULT: |
| { |
| "tool":"weather.forecast", |
| "result":{"city":"Ancona","condition":"pioggia","temperature_c":15,"humidity":82,"debug":"ignore"} |
| } |
| |
| Ignora debug e usa gli altri dati.""", |
|
|
| """CONTEXT: |
| Un backup offline è una copia non sempre collegata alla rete. |
| |
| QUESTION: |
| Che cos'è un backup offline? |
| |
| Rispondi solo usando il contesto.""", |
|
|
| """TOOL_RESULT: |
| {"tool":"stocks","result":{"symbol":"AMD","price":165.77,"currency":"USD","change_percent":5.21}} |
| |
| Scrivi una frase naturale.""", |
|
|
| """Trasforma in linguaggio naturale. |
| |
| { |
| "weather":{"city":"Lecce","condition":"caldo","temperature_c":36}, |
| "calendar":{"title":"Dentista","date":"domani","time":"16:30"}, |
| "mail":{"unread":5} |
| }""" |
| ] |
|
|
|
|
|
|
| import re |
|
|
| def simple_router(user): |
| t=user.lower(); |
| import json |
| m=None |
| if "meteo" in t or "tempo" in t: |
| import re;m=re.search(r"(genova|udine|roma|milano|trento)",t);city=(m.group(1).title() if m else "Genova");return {"tool":"weather.forecast","result":{"city":city,"condition":"pioggia","temperature_c":18,"wind_kmh":12}} |
| if "tsla" in t or "quota" in t:return {"tool":"finance.quote","result":{"symbol":"TSLA","price":312.45,"currency":"USD","change_percent":0.8}} |
| return None |
|
|
| def tool_prompt(r): |
| return "### Instruction:\nTOOL_RESULT:\n"+json.dumps(r,ensure_ascii=False)+"\n\nScrivi una risposta naturale in italiano usando solo i dati utili.\n\n### Response:\n" |
|
|
| def main(): |
| ap=argparse.ArgumentParser() |
| ap.add_argument("--repo-id",default="ProjectScugnizz/scugnizz-1b") |
| ap.add_argument("--ckpt",default="training-runs/sft-universal-tool-renderer-1b-v3-agentic-smart-mix/checkpoint_best.pt") |
| ap.add_argument("--max-new",type=int,default=220) |
| ap.add_argument("--temperature",type=float,default=0.05) |
| ap.add_argument("--top-p",type=float,default=0.80) |
| ap.add_argument("--prompt",default=None) |
| ap.add_argument("--chat",action="store_true") |
| a=ap.parse_args() |
|
|
| device="cuda" if torch.cuda.is_available() else "cpu" |
| dt=dtype_of(device) |
| print("device",device,"dtype",dt,flush=True) |
|
|
| model,tok=load_model(a.repo_id,a.ckpt,device) |
| if a.prompt: |
| r=simple_router(a.prompt) |
| p=tool_prompt(r) if r else P(a.prompt) |
| print(generate(model,tok,p,device,dt,max_new=a.max_new,temperature=0.05 if r else a.temperature,top_p=a.top_p));return |
| if a.chat: |
| print("Agent CLI"); |
| while True: |
| q=input("> ") |
| if q.lower() in ("exit","quit"):break |
| r=simple_router(q) |
| p=tool_prompt(r) if r else P(q) |
| print(generate(model,tok,p,device,dt,max_new=a.max_new,temperature=0.05 if r else a.temperature,top_p=a.top_p)) |
| return |
|
|
| print("\n"+"="*100) |
| print("SCUGNIZZ V3 - 20 DOMANDE NUOVE") |
| print("="*100+"\n",flush=True) |
|
|
| for i,p in enumerate(PROMPTS,1): |
| out=generate(model,tok,P(p),device,dt,max_new=a.max_new,temperature=a.temperature,top_p=a.top_p) |
| print("\n"+"="*100) |
| print(f"TEST {i:02d}") |
| print("-"*100) |
| print("PROMPT:\n"+p) |
| print("-"*100) |
| print("RISPOSTA:\n"+out) |
| print("="*100,flush=True) |
|
|
|
|
| if __name__=="__main__": |
| main() |
|
|