import gradio as gr import torch import torch.nn as nn import sentencepiece as spm import math import os from groq import Groq # ── config ────────────────────────────────────────────────────────────────── VOCAB_SIZE = 4000 D_MODEL = 256 NHEAD = 4 NUM_LAYERS = 3 DIM_FF = 512 DROPOUT = 0.1 MAX_LEN = 128 PAD_ID, BOS_ID, EOS_ID = 0, 2, 3 DEVICE = "cuda" if torch.cuda.is_available() else "cpu" # ── model definition ───────────────────────────────────────────────────────── class PE(nn.Module): def __init__(self, d, mx=512): super().__init__() pe = torch.zeros(mx, d) pos = torch.arange(0, mx).float().unsqueeze(1) div = torch.exp(torch.arange(0, d, 2).float() * (-math.log(10000.0) / d)) pe[:, 0::2] = torch.sin(pos * div) pe[:, 1::2] = torch.cos(pos * div) self.register_buffer("pe", pe.unsqueeze(0)) def forward(self, x): return x + self.pe[:, :x.size(1)] class Transltr(nn.Module): def __init__(self): super().__init__() self.se = nn.Embedding(VOCAB_SIZE, D_MODEL, padding_idx=PAD_ID) self.te = nn.Embedding(VOCAB_SIZE, D_MODEL, padding_idx=PAD_ID) self.pe = PE(D_MODEL) self.tf = nn.Transformer(D_MODEL, NHEAD, NUM_LAYERS, NUM_LAYERS, DIM_FF, DROPOUT, batch_first=True) self.proj = nn.Linear(D_MODEL, VOCAB_SIZE) def forward(self, src, tgt, tgt_mask=None, src_key_padding_mask=None, tgt_key_padding_mask=None, memory_key_padding_mask=None): tmsk = self.tf.generate_square_subsequent_mask(tgt.size(1)).to(DEVICE) sm = (src == PAD_ID) tm = (tgt == PAD_ID) se = self.pe(self.se(src) * math.sqrt(D_MODEL)) te = self.pe(self.te(tgt) * math.sqrt(D_MODEL)) return self.proj(self.tf(se, te, tgt_mask=tmsk, src_key_padding_mask=sm, tgt_key_padding_mask=tm, memory_key_padding_mask=sm)) # ── load model & tokenizers ────────────────────────────────────────────────── src_sp = spm.SentencePieceProcessor() src_sp.load("eng_tok.model") tgt_sp = spm.SentencePieceProcessor() tgt_sp.load("tiv_tok.model") model = Transltr().to(DEVICE) ckpt = torch.load("transltr_model.pt", map_location=DEVICE) model.load_state_dict(ckpt["model_state"]) model.eval() groq_client = Groq(api_key=os.environ["GROQ_API_KEY"]) # ── inference ──────────────────────────────────────────────────────────────── def translate(text: str) -> str: ids = [BOS_ID] + src_sp.encode(text) + [EOS_ID] src = torch.tensor([ids], device=DEVICE) out = [BOS_ID] with torch.no_grad(): for _ in range(MAX_LEN): tgt = torch.tensor([out], device=DEVICE) logits = model(src, tgt) next_id = logits[0, -1].argmax().item() if next_id == EOS_ID: break out.append(next_id) return tgt_sp.decode(out[1:]) def speech_to_tiv(audio_path): if audio_path is None: return "", "" with open(audio_path, "rb") as f: transcription = groq_client.audio.transcriptions.create( file=("audio.wav", f), model="whisper-large-v3", language="en", ) english = transcription.text.strip() tiv = translate(english) return english, tiv def text_to_tiv(text): if not text.strip(): return "" return translate(text.strip()) # ── UI ─────────────────────────────────────────────────────────────────────── css = """ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;700;800&display=swap'); body { background: #0a0a0a !important; } .gradio-container { max-width: 820px !important; margin: 0 auto !important; font-family: 'Syne', sans-serif !important; background: #0a0a0a !important; } #title { text-align: center; padding: 2.5rem 0 1rem; color: #f0ebe0; } #title h1 { font-size: 3rem; font-weight: 800; letter-spacing: -2px; margin: 0; background: linear-gradient(135deg, #f0ebe0, #c8a96e); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } #title p { color: #888; font-family: 'Space Mono', monospace; font-size: 0.75rem; letter-spacing: 3px; text-transform: uppercase; margin-top: 0.4rem; } .tab-nav button { font-family: 'Space Mono', monospace !important; font-size: 0.7rem !important; letter-spacing: 2px !important; text-transform: uppercase !important; color: #888 !important; background: transparent !important; border: none !important; border-bottom: 2px solid transparent !important; padding: 0.75rem 1.5rem !important; } .tab-nav button.selected { color: #c8a96e !important; border-bottom: 2px solid #c8a96e !important; } .output-box { background: #141414; border: 1px solid #2a2a2a; border-radius: 8px; padding: 1.25rem; font-family: 'Space Mono', monospace; font-size: 1.1rem; color: #f0ebe0; min-height: 80px; line-height: 1.8; } .label-text { font-family: 'Space Mono', monospace; font-size: 0.65rem; letter-spacing: 3px; text-transform: uppercase; color: #555; margin-bottom: 0.5rem; } footer { display: none !important; } """ with gr.Blocks(css=css, title="Transltr — English to Tiv") as demo: gr.HTML("""
English → Tiv · Gospel-trained neural translation