Spaces:
Sleeping
Sleeping
| """ | |
| Gradio demo for the SLM-264M data transformer fine-tune. | |
| Pattern: code lives here (in the Space), weights live in the model repo and are | |
| pulled at startup with huggingface_hub. You bundle model.py alongside this file. | |
| Files this Space expects: | |
| - app.py (this file) | |
| - model.py (YOUR architecture file — copy it from your training repo) | |
| - requirements.txt | |
| - README.md (Space card, has the sdk: gradio frontmatter) | |
| Weights + tokenizer are downloaded from MODEL_REPO below. If that repo is private, | |
| add an HF_TOKEN secret to the Space (Settings -> Variables and secrets). | |
| """ | |
| import os | |
| import torch | |
| import gradio as gr | |
| from tokenizers import Tokenizer | |
| from huggingface_hub import hf_hub_download | |
| import spaces | |
| # ---- point these at your fine-tuned transform model repo ------------------- | |
| # MODEL_REPO = os.environ.get("MODEL_REPO", "uday210/slm-264m-transform") # <-- EDIT | |
| # CKPT_FILENAME = os.environ.get("CKPT_FILENAME", "slm-264m-transform.pt") # <-- EDIT | |
| MODEL_REPO = os.environ.get("MODEL_REPO", "uday210/slm-transform-264m") | |
| CKPT_FILENAME = os.environ.get("CKPT_FILENAME", "slm-transform-final.pt") | |
| TOKENIZER_REPO = os.environ.get("TOKENIZER_REPO", "uday210/slm-264m-base") # <-- ADD | |
| TOKENIZER_FILENAME = os.environ.get("TOKENIZER_FILENAME", "tokenizer.json") | |
| # ---- prompt template: MUST match how the fine-tune was trained ------------- | |
| # Your notes show the inference format as: | |
| # "Convert this CSV to JSON:\n<data>\nOutput:\n" | |
| # If your training pairs used a different wording, change it here or the model | |
| # will be prompted off-distribution. | |
| PROMPT_TEMPLATE = "Convert this {src} to {tgt}:\n{data}\nOutput:\n" | |
| BLOCK_SIZE = 2048 | |
| EOT_TOKEN = "<|endoftext|>" | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| if device == "cpu": | |
| torch.set_num_threads(os.cpu_count() or 4) | |
| # --------------------------------------------------------------------------- | |
| # Load architecture + weights | |
| # --------------------------------------------------------------------------- | |
| from model import GPT # noqa: E402 (your import-safe model.py) | |
| _token = os.environ.get("HF_TOKEN") # only needed if the model repo is private | |
| ckpt_path = hf_hub_download(MODEL_REPO, CKPT_FILENAME, token=_token) | |
| # tokenizer is bundled in the Space, next to app.py | |
| tok_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), TOKENIZER_FILENAME) | |
| ckpt = torch.load(ckpt_path, map_location=device, weights_only=False) | |
| model = GPT(ckpt["config"]) | |
| model.load_state_dict(ckpt["model"]) | |
| model.eval().to(device) | |
| tok = Tokenizer.from_file(tok_path) | |
| EOT_ID = tok.token_to_id(EOT_TOKEN) | |
| # --------------------------------------------------------------------------- | |
| # Generation (greedy by default — this task is deterministic transformation) | |
| # --------------------------------------------------------------------------- | |
| def generate(prompt: str, max_new_tokens: int = 512, temperature: float = 0.0): | |
| ids = tok.encode(prompt).ids | |
| x = torch.tensor(ids, dtype=torch.long, device=device)[None] | |
| start = len(ids) | |
| for _ in range(max_new_tokens): | |
| logits, _ = model(x[:, -BLOCK_SIZE:]) | |
| logits = logits[:, -1, :] | |
| if temperature and temperature > 0: | |
| probs = torch.softmax(logits / temperature, dim=-1) | |
| nxt = torch.multinomial(probs, num_samples=1) | |
| else: | |
| nxt = logits.argmax(dim=-1, keepdim=True) | |
| x = torch.cat([x, nxt], dim=1) | |
| if EOT_ID is not None and nxt.item() == EOT_ID: | |
| break | |
| # stream the decoded suffix so far | |
| yield tok.decode(x[0, start:].tolist()) | |
| yield tok.decode(x[0, start:].tolist()) | |
| def transform(data, src_fmt, tgt_fmt, max_new_tokens, temperature): | |
| data = (data or "").strip() | |
| if not data: | |
| yield "Paste some input data first." | |
| return | |
| if src_fmt == tgt_fmt: | |
| yield "Source and target format are the same — pick different formats." | |
| return | |
| prompt = PROMPT_TEMPLATE.format(src=src_fmt, tgt=tgt_fmt, data=data) | |
| for partial in generate(prompt, int(max_new_tokens), float(temperature)): | |
| yield partial | |
| # --------------------------------------------------------------------------- | |
| # UI | |
| # --------------------------------------------------------------------------- | |
| FORMATS = ["CSV", "JSON", "XML"] | |
| EXAMPLE_CSV = "name,city,age\nalice,london,31\nbob,paris,47\ncarol,tokyo,29" | |
| with gr.Blocks(title="SLM-264M Data Transformer", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown( | |
| "# 🔄 SLM-264M Data Transformer\n" | |
| "A 264M-parameter small language model, trained from scratch and fine-tuned to " | |
| "convert between **CSV, JSON, and XML**. Runs on CPU — expect a few tokens/sec.\n\n" | |
| "*Note: numbers can occasionally be miscopied; text and structure are reliable.*" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| data_in = gr.Textbox( | |
| label="Input data", value=EXAMPLE_CSV, lines=8, | |
| placeholder="Paste CSV, JSON, or XML here…", | |
| ) | |
| with gr.Row(): | |
| src = gr.Dropdown(FORMATS, value="CSV", label="From") | |
| tgt = gr.Dropdown(FORMATS, value="JSON", label="To") | |
| with gr.Accordion("Advanced", open=False): | |
| max_tok = gr.Slider(64, 1024, value=512, step=64, label="Max new tokens") | |
| temp = gr.Slider( | |
| 0.0, 1.0, value=0.0, step=0.1, | |
| label="Temperature (0 = greedy, recommended for exact transforms)", | |
| ) | |
| go = gr.Button("Transform", variant="primary") | |
| with gr.Column(): | |
| out = gr.Textbox(label="Output", lines=14, show_copy_button=True) | |
| go.click(transform, [data_in, src, tgt, max_tok, temp], out) | |
| data_in.submit(transform, [data_in, src, tgt, max_tok, temp], out) | |
| if __name__ == "__main__": | |
| demo.launch() |