adarsh-007's picture
Add @spaces.GPU for ZeroGPU
824c947 verified
Raw History Blame Contribute Delete
6.14 kB
import os
import gradio as gr
import torch
import torch.nn.functional as F
try:
import spaces # available on Hugging Face ZeroGPU Spaces
gpu = spaces.GPU
except ImportError: # running locally
gpu = lambda fn: fn
class Sequential:
def __init__(self, layers):
self.layers = layers
def __call__(self, x):
for layer in self.layers:
x = layer(x)
self.out = x
return self.out
class Embedding:
def __init__(self, num_embeddings, embeddings_size):
self.weight = torch.randn((num_embeddings, embeddings_size))
def __call__(self, ix):
self.out = self.weight[ix]
return self.out
class Flatten:
def __init__(self, n):
self.n = n
def __call__(self, x):
batch_size, block_size, n_emb = x.shape
x = x.view(batch_size, block_size // self.n, n_emb * self.n)
if x.shape[1] == 1:
x = x.squeeze(1)
self.out = x
return self.out
class Linear:
def __init__(self, fanin, fanout, bias=True):
self.weight = torch.randn((fanin, fanout)) / (fanin**0.5)
self.bias = torch.randn(fanout) if bias else None
def __call__(self, x):
self.out = x @ self.weight
if self.bias is not None:
self.out += self.bias
return self.out
class BatchNorm1d:
def __init__(self, dim, eps=1e-5, momentum=0.1):
self.eps = eps
self.momentum = momentum
self.training = True
self.gamma = torch.ones(dim)
self.beta = torch.zeros(dim)
self.running_mean = torch.zeros(dim)
self.running_var = torch.ones(dim)
def __call__(self, x):
# inference only: always use running stats
xhat = (x - self.running_mean) / torch.sqrt(self.running_var + self.eps)
self.out = self.gamma * xhat + self.beta
return self.out
class Tanh:
def __call__(self, x):
self.out = torch.tanh(x)
return self.out
model_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wavenet.pt")
print("model path:", model_path)
ckpt = torch.load(model_path, map_location="cpu")
itos = ckpt["itos"]
stoi = {s: i for i, s in itos.items()}
block_size = ckpt["block_size"]
vocab_size = len(itos)
# Use the SAME hyperparameters and layer order as in training
n_emb = 50
n_hidden = 300
model = Sequential([
Embedding(vocab_size, n_emb),
Flatten(2), Linear(n_emb * 2, n_hidden, bias=False), BatchNorm1d(n_hidden), Tanh(),
Flatten(2), Linear(n_hidden * 2, n_hidden, bias=False), BatchNorm1d(n_hidden), Tanh(),
Flatten(2), Linear(n_hidden * 2, n_hidden, bias=False), BatchNorm1d(n_hidden), Tanh(),
Linear(n_hidden, vocab_size),
])
for key, t in ckpt["state"].items():
i, name = key.split(".")
setattr(model.layers[int(i)], name, t)
@torch.no_grad()
def generate_one(prefix="", temperature=1.0, max_len=30):
context = [0] * block_size
out = []
for ch in prefix:
context = context[1:] + [stoi[ch]]
out.append(stoi[ch])
while len(out) < max_len:
logits = model(torch.tensor([context]))
probs = F.softmax(logits / temperature, dim=1)
ix = torch.multinomial(probs, num_samples=1).item()
if ix == 0: # end-of-name token
break
context = context[1:] + [ix]
out.append(ix)
return "".join(itos[i] for i in out)
# ZeroGPU Spaces require at least one @spaces.GPU function; the model itself runs on CPU
@gpu
def generate_names(num_names, prefix, temperature):
prefix = (prefix or "").strip().lower()
unknown = sorted({ch for ch in prefix if ch not in stoi or stoi[ch] == 0})
if unknown:
raise gr.Error(f"Unsupported characters in prefix: {' '.join(unknown)}")
names = [generate_one(prefix, temperature).capitalize() for _ in range(int(num_names))]
return "\n".join(names)
with gr.Blocks() as iface:
gr.Markdown(
"""
# 🇮🇳 Indian Name Generator
A character-level language model trained on Indian names, based on the **WaveNet architecture**.
It generates new, name-like words one character at a time.
### Model details:
- **All layers are custom built** from scratch in PyTorch (Embedding, Flatten, Linear,
BatchNorm1d, Tanh, Sequential) — no `torch.nn` layers used
- **WaveNet architecture**: characters are fused hierarchically, 2 at a time, over a
context of 8 previous characters
- **Embedding size (n_emb):** 50
- **Hidden units (n_hidden):** 300
- **Total parameters:** 401,277
- **Dataset:** [swami93/indian-names-1.5M](https://huggingface.co/datasets/swami93/indian-names-1.5M)
- **Training notebook:** [Kaggle — predict-indian-names-wavenet-arch](https://www.kaggle.com/code/eluriadarsh/predict-indian-names-wavenet-arch)
### Performance (cross-entropy loss):
| Split | Loss |
|---|---|
| Training | 1.90 |
| Validation | 2.01 |
| Test | 2.01 |
### How to use:
- Choose how many names to generate
- Optionally give a starting prefix (e.g. `pra`, `su`, `ka`)
- **Temperature**: lower = safer, more common-sounding names; higher = more creative
"""
)
with gr.Row():
with gr.Column():
num_names = gr.Slider(1, 50, value=10, step=1, label="Number of names")
prefix = gr.Textbox(label="Starts with (optional)", placeholder="e.g. pra")
temperature = gr.Slider(0.3, 2.0, value=1.0, step=0.1, label="Temperature")
submit_btn = gr.Button("✨ Generate Names", variant="primary")
with gr.Column():
output = gr.Textbox(label="Generated names", lines=15)
gr.Examples(
examples=[[10, "", 1.0], [10, "pra", 0.8], [10, "su", 1.0], [10, "ka", 1.3]],
inputs=[num_names, prefix, temperature],
label="Try an example",
)
submit_btn.click(fn=generate_names, inputs=[num_names, prefix, temperature], outputs=output)
if __name__ == "__main__":
iface.launch(server_name="0.0.0.0", server_port=7860)