yisberh's picture
Update app.py
a3cff0d verified
Raw History Blame Contribute Delete
4.14 kB
import os
import json
import torch
import torch.nn as nn
import sentencepiece as spm
import gradio as gr
from huggingface_hub import hf_hub_download
import spaces
# --- 1. CONFIGURATION & MODEL DOWNLOAD ---
REPO_ID = "yisberh/grade8-math-custom-transformer"
# Fetch preloaded cache files instantly from local disk memory
config_path = hf_hub_download(repo_id=REPO_ID, filename="config.json", local_files_only=True)
model_path = hf_hub_download(repo_id=REPO_ID, filename="model.pt", local_files_only=True)
spm_path = hf_hub_download(repo_id=REPO_ID, filename="unit1_test.model", local_files_only=True)
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
sp = spm.SentencePieceProcessor(model_file=spm_path)
# --- 2. MODEL DEFINITION (Matching your exact architecture) ---
class Model(nn.Module):
def __init__(self, vocab):
super().__init__()
self.d = 256 # 256 embedding dimensions
self.emb = nn.Embedding(vocab, self.d)
self.pos = nn.Embedding(512, self.d)
layer = nn.TransformerEncoderLayer(
d_model=self.d,
nhead=8, # 8 attention heads
dim_feedforward=1024, # 1024 FFN dimension
batch_first=True,
dropout=0.0 # 0.0 dropout for speed during inference
)
self.tr = nn.TransformerEncoder(layer, 6) # 6 Layers deep
self.head = nn.Linear(self.d, vocab)
def forward(self, x):
n = x.size(1)
pos = torch.arange(n, device=x.device)
x = self.emb(x) + self.pos(pos)
mask = torch.triu(
torch.full((n, n), float("-inf"), device=x.device),
diagonal=1
)
return self.head(self.tr(x, mask=mask))
# Initialize model container framework on CPU first
model = Model(sp.vocab_size())
# Strip any potential DataParallel state_dict prefix formatting cleanly
state_dict = torch.load(model_path, map_location="cpu")
if any(k.startswith("module.") for k in state_dict.keys()):
state_dict = {k.replace("module.", ""): v for k in state_dict.items()}
model.load_state_dict(state_dict)
model.eval()
# --- 3. INFERENCE LOOP ROUTED VIA ZEROGPU ---
@spaces.GPU(duration=60) # Mandated to prevent "No @spaces.GPU function detected"
@torch.inference_mode()
def generate_response(question):
# Protect against early app validation/mock checks passing empty inputs
if not question or not isinstance(question, str) or not question.strip():
return "Please ask a question about Grade 8 Mathematics Unit 1."
# Identify device assigned dynamically by the Hugging Face manager
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
# Encode instructions using your native data pipeline format
ids = [sp.bos_id()] + sp.encode_as_ids(f"[INST] {question.strip()} [/INST]")
x = torch.tensor([ids], device=device)
max_new_tokens = 256 # Higher generation limits to support extended answers
for _ in range(max_new_tokens):
if x.size(1) >= 512: # Strict constraint boundary matching sequence history size
break
logits = model(x)
nxt = logits[:, -1].argmax(-1).item()
nxt_tensor = torch.tensor([[nxt]], device=device)
x = torch.cat([x, nxt_tensor], dim=1)
if nxt == sp.eos_id():
break
text = sp.decode(x[0].tolist())
# Split using the explicit token tags matching your SFT layout formatting
if "[/INST]" in text:
return text.split("[/INST]")[-1].strip()
return text
# --- 4. GRADIO INTERFACE LAYOUT CONFIGURATION ---
demo = gr.Interface(
fn=generate_response,
inputs=gr.Textbox(
lines=3,
placeholder="Enter your Grade 8 Mathematics question here... (Supports English & Amharic)",
label="Math Question"
),
outputs=gr.Textbox(label="Model Response"),
title="Grade 8 Mathematics AI Assistant (5M Params)",
description="Custom transformer model tracking Grade 8 Math Unit 1 roadmap objectives."
)
if __name__ == "__main__":
demo.queue().launch(server_name="0.0.0.0", server_port=7860)