"""Local research demo showing model provenance, sampling and live run metrics."""
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
from pathlib import Path
from .inference import load, continue_text, infill
from .train import amp
from .hybrid import generate as generate_causal
from .editor import rewrite_span
PAGE = r'''
DiffuThink — From Scratch Lab
FROM RANDOM WEIGHTS TO TEXT
DiffuThink Research Lab
Un modèle hybride entraîné depuis zéro : continuation autorégressive et reconstruction par débruitage. Aucun poids externe préentraîné.
…paramètres
…récits d'entraînement
…tokens de contexte
…mises à jour
Texte généré
Le résultat apparaîtra ici.
Voir les étapes de génération
Deux objectifs, un même réseau
En continuation, l'attention causale interdit de voir les tokens futurs. Pour compléter des trous, l'attention bidirectionnelle utilise les deux côtés. RoPE, RMSNorm et SwiGLU restent communs. La continuation autorégressive n'est pas présentée comme de la diffusion.
Données : TinyStories, récits synthétiques générés par GPT-3.5/4. Poids appris dans ce projet. Développement assisté par IA. Des erreurs et répétitions restent possibles ; ce modèle n'est pas un assistant généraliste.
'''
def serve(args):
import torch
torch.set_num_threads(getattr(args,"threads",4))
model, tokenizer = load(args.model, args.device)
info = json.loads((Path(args.model) / "training_info.json").read_text())
phase = info.get("previous_phase")
total_steps = info["step"]
while phase:
total_steps += phase["step"]
phase = phase.get("previous_phase")
info = {**info, "phase_step": info["step"], "step": total_steps}
# Warm the kernels before reporting interactive latency to the viewer.
with torch.inference_mode(), amp(args.device, "bf16" if args.device == "cuda" else "fp32"):
warm = torch.tensor([[1, 3, 2]], device=args.device)
model(warm, torch.tensor([0.5], device=args.device))
class Handler(BaseHTTPRequestHandler):
def respond(self, status, body, mime="application/json; charset=utf-8"):
raw = body.encode()
self.send_response(status)
self.send_header("Content-Type",mime)
self.send_header("Content-Length",str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_GET(self):
if self.path == "/": self.respond(200,(Path(__file__).parent/"workshop.html").read_text(encoding="utf-8"),"text/html; charset=utf-8")
elif self.path == "/lab": self.respond(200,PAGE,"text/html; charset=utf-8")
elif self.path == "/api/info": self.respond(200,json.dumps(info))
else: self.respond(404,'{"error":"Not found"}')
def do_POST(self):
if self.path != "/api/generate":
self.respond(404,'{"error":"Not found"}')
return
try:
size=int(self.headers.get("Content-Length",0))
if not 0 < size <= 16384: raise ValueError("Invalid request size")
data=json.loads(self.rfile.read(size))
if not isinstance(data,dict) or not isinstance(data.get("prompt"),str): raise ValueError("prompt must be a string")
options=dict(steps=int(data.get("steps",12)),temperature=float(data.get("temperature",0.7)),seed=int(data.get("seed",42)),policy=data.get("policy","confidence"),precision="bf16" if args.device=="cuda" else "fp32")
if not 1<=options["steps"]<=32 or not 0<=options["temperature"]<=1.5: raise ValueError("Invalid sampling range")
if data.get("mode")=="rewrite":
result=rewrite_span(model,tokenizer,data["prompt"],data.get("start"),data.get("end"),seed=options["seed"],precision=options["precision"])
elif data.get("mode")=="infill":
if not isinstance(data.get("suffix",""),str): raise ValueError("suffix must be a string")
result=infill(model,tokenizer,data["prompt"],data.get("suffix",""),int(data.get("missing_tokens",2)),**options)
elif data.get("mode") in ("continue", "diffusion"):
length=int(data.get("max_new_tokens",64))
maximum = 256 if data["mode"] == "continue" else 128
if not 1<=length<=maximum: raise ValueError(f"Length must be 1..{maximum}")
if data["mode"] == "continue":
if info.get("continuation_mode") != "autoregressive":
raise ValueError("This checkpoint has no trained causal continuation mode")
result=generate_causal(model,tokenizer,data["prompt"],max_new_tokens=length,finish_sentence_tokens=32,**options)
else:
result=continue_text(model,tokenizer,data["prompt"],max_new_tokens=length,**options)
else: raise ValueError("Unknown task")
self.respond(200,json.dumps(result,ensure_ascii=False))
except (ValueError,TypeError,OverflowError) as exc:
self.respond(400,json.dumps({"error":str(exc)}))
server=HTTPServer((getattr(args,"host","127.0.0.1"),args.port),Handler)
print(f"DiffuThink v2: http://127.0.0.1:{args.port}",flush=True)
try: server.serve_forever()
except KeyboardInterrupt: pass
finally: server.server_close()