Spaces:
Running
Running
| import os | |
| import sys | |
| import torch | |
| import gradio as gr | |
| from tokenizers import Tokenizer | |
| from huggingface_hub import hf_hub_download, snapshot_download | |
| # ── Repo IDs ─────────────────────────────────────────────────────────────────── | |
| REPO_V1 = "IvmeLabs/Ivme-Conversate-v1-Base" | |
| REPO_V2 = "IvmeLabs/Ivme-Conversate-v2-Base" | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| # ── Load v1 ──────────────────────────────────────────────────────────────────── | |
| def load_v1(): | |
| tokenizer_path = hf_hub_download(repo_id=REPO_V1, filename="ivme_tokenizer.json") | |
| model_path = hf_hub_download(repo_id=REPO_V1, filename="ivme_base_ema.pt") | |
| model_py_path = hf_hub_download(repo_id=REPO_V1, filename="model.py") | |
| model_dir = os.path.dirname(model_py_path) | |
| if model_dir not in sys.path: | |
| sys.path.insert(0, model_dir) | |
| from model import IvmeConversate # noqa: E402 | |
| tok = Tokenizer.from_file(tokenizer_path) | |
| ckpt = torch.load(model_path, map_location=device, weights_only=False) | |
| cfg = ckpt["cfg"] | |
| cfg.attn_backend = "sdpa" | |
| model = IvmeConversate(cfg).to(device) | |
| model.load_state_dict(ckpt["model"]) | |
| model.eval() | |
| max_ctx = ( | |
| getattr(cfg, "block_size", None) | |
| or getattr(cfg, "n_ctx", None) | |
| or getattr(cfg, "max_seq_len", None) | |
| or getattr(cfg, "context_length", None) | |
| or 1024 | |
| ) | |
| eos_id = tok.token_to_id("<|eos|>") | |
| return {"tokenizer": tok, "model": model, "max_ctx": max_ctx, "eos_id": eos_id} | |
| # ── Load v2 ──────────────────────────────────────────────────────────────────── | |
| def load_v2(): | |
| # v2's architecture code lives under a `model/` package in the repo, which | |
| # collides by name with v1's already-imported top-level `model` module, so | |
| # we must remove any cached `model` module before (re)importing v2's package. | |
| for mod_name in list(sys.modules): | |
| if mod_name == "model" or mod_name.startswith("model."): | |
| del sys.modules[mod_name] | |
| repo_local_dir = snapshot_download(REPO_V2, allow_patterns=["model/*"]) | |
| if repo_local_dir not in sys.path: | |
| sys.path.insert(0, repo_local_dir) | |
| from model import IvmeConfig, IvmeConversateV2 # noqa: E402 | |
| tokenizer_path = hf_hub_download(repo_id=REPO_V2, filename="tokenizer.json") | |
| ckpt_path = hf_hub_download(repo_id=REPO_V2, filename="ckpt_final.pt") | |
| tok = Tokenizer.from_file(tokenizer_path) | |
| torch.serialization.add_safe_globals([IvmeConfig]) | |
| ckpt = torch.load(ckpt_path, map_location=device, weights_only=False) | |
| cfg = ckpt["config"] | |
| model = IvmeConversateV2(cfg) | |
| state_dict = ckpt["ema_state_dict"] | |
| state_dict = {k.removeprefix("_orig_mod."): v for k, v in state_dict.items()} | |
| model.load_state_dict(state_dict) | |
| model.to(device).eval() | |
| max_ctx = getattr(cfg, "context_len", 1024) | |
| eos_id = tok.token_to_id("<|endoftext|>") | |
| return {"tokenizer": tok, "model": model, "max_ctx": max_ctx, "eos_id": eos_id} | |
| print("Loading İvme-Conversate-v1-Base...") | |
| V1 = load_v1() | |
| print("Loading İvme-Conversate-v2-Base...") | |
| V2 = load_v2() | |
| REGISTRY = { | |
| "İvme-Conversate-v2-Base (recommended)": V2, | |
| "İvme-Conversate-v1-Base": V1, | |
| } | |
| BENCH = { | |
| # name: (v1, v2, higher_is_better) | |
| "WikiText-2 byte perplexity": (2.96, 2.2250, False), | |
| "BLiMP (macro-avg)": (61.40, 75.09, True), | |
| "ARC-Easy (acc_norm)": (30.85, 39.98, True), | |
| } | |
| # ── Shared generation core ──────────────────────────────────────────────────── | |
| def _generate(bundle, prompt, max_new_tokens, temperature, top_k, repetition_penalty): | |
| tokenizer = bundle["tokenizer"] | |
| model = bundle["model"] | |
| max_ctx = bundle["max_ctx"] | |
| eos_id = bundle["eos_id"] | |
| prompt = prompt or "" | |
| input_ids = tokenizer.encode(prompt).ids | |
| if not input_ids: | |
| yield prompt | |
| return | |
| generated = torch.tensor([input_ids], device=device, dtype=torch.long) | |
| vocab_size = None | |
| response_tokens: list[int] = [] | |
| temperature = max(float(temperature), 1e-6) | |
| for _ in range(int(max_new_tokens)): | |
| window = generated[:, -max_ctx:] | |
| out = model(window) | |
| if isinstance(out, (tuple, list)): | |
| out = out[0] | |
| elif isinstance(out, dict): | |
| out = out.get("logits", next(iter(out.values()))) | |
| logits = out[:, -1, :].float() | |
| if vocab_size is None: | |
| vocab_size = logits.size(-1) | |
| if repetition_penalty and repetition_penalty != 1.0: | |
| seen = torch.unique(generated[0]) | |
| scores = logits[0, seen] | |
| scores = torch.where( | |
| scores > 0, scores / repetition_penalty, scores * repetition_penalty | |
| ) | |
| logits[0, seen] = scores | |
| logits = logits / temperature | |
| k = int(top_k) | |
| if k > 0: | |
| k = min(k, vocab_size) | |
| topk_vals, _ = torch.topk(logits, k) | |
| logits[logits < topk_vals[:, -1:]] = float("-inf") | |
| probs = torch.softmax(logits, dim=-1) | |
| if not torch.isfinite(probs).all() or probs.sum() <= 0: | |
| next_tok = torch.argmax(logits, dim=-1, keepdim=True) | |
| else: | |
| next_tok = torch.multinomial(probs, num_samples=1) | |
| tok_id = next_tok.item() | |
| if eos_id is not None and tok_id == eos_id: | |
| break | |
| response_tokens.append(tok_id) | |
| generated = torch.cat([generated, next_tok], dim=1) | |
| yield prompt + tokenizer.decode(response_tokens) | |
| if not response_tokens: | |
| yield prompt | |
| def continue_text(model_choice, prompt, max_new_tokens, temperature, top_k, repetition_penalty): | |
| bundle = REGISTRY[model_choice] | |
| yield from _generate(bundle, prompt, max_new_tokens, temperature, top_k, repetition_penalty) | |
| def compare_generate(prompt, max_new_tokens, temperature, top_k, repetition_penalty): | |
| """Run v1 and v2 on the same prompt/settings, streaming both in parallel steps.""" | |
| gen_v1 = _generate(V1, prompt, max_new_tokens, temperature, top_k, repetition_penalty) | |
| gen_v2 = _generate(V2, prompt, max_new_tokens, temperature, top_k, repetition_penalty) | |
| last_v1, last_v2 = prompt, prompt | |
| done_v1 = done_v2 = False | |
| while not (done_v1 and done_v2): | |
| if not done_v1: | |
| try: | |
| last_v1 = next(gen_v1) | |
| except StopIteration: | |
| done_v1 = True | |
| if not done_v2: | |
| try: | |
| last_v2 = next(gen_v2) | |
| except StopIteration: | |
| done_v2 = True | |
| yield last_v1, last_v2 | |
| def benchmark_table(): | |
| rows = [] | |
| for name, (v1, v2, higher_better) in BENCH.items(): | |
| delta = (v2 - v1) if higher_better else (v1 - v2) | |
| pct = (delta / abs(v1)) * 100 if v1 else 0 | |
| arrow = "↑" if higher_better else "↓" | |
| rows.append([name + f" {arrow}", f"{v1:.2f}", f"{v2:.2f}", f"{'+' if delta >= 0 else ''}{delta:.2f} ({pct:+.0f}%)"]) | |
| return rows | |
| # ── UI ───────────────────────────────────────────────────────────────────────── | |
| CSS = """ | |
| body, .gradio-container { font-family: 'Inter', system-ui, sans-serif; } | |
| #component-0 { max-width: 900px; margin: 0 auto; padding: 16px; } | |
| footer { display: none !important; } | |
| .ivme-output textarea { font-size: 1.02rem; line-height: 1.6; } | |
| """ | |
| EXAMPLES = [ | |
| "The theory of relativity states that", | |
| "In the beginning, the universe was", | |
| "def fibonacci(n):", | |
| "The most important thing to remember about cooking is", | |
| "Once upon a time, in a small village by the sea,", | |
| "Python is a programming language that", | |
| ] | |
| with gr.Blocks(css=CSS, title="İvme-Conversate") as demo: | |
| gr.Markdown( | |
| "## İvme-Conversate — Tiny Language Models, Text Continuation\n" | |
| "Sub-25M-parameter decoder-only **base** models · not instruction-tuned · " | |
| "these models **continue** text, they do not chat or answer questions." | |
| ) | |
| with gr.Tabs(): | |
| # ── Tab 1: single-model playground with picker ────────────────────── | |
| with gr.Tab("Playground"): | |
| model_picker = gr.Dropdown( | |
| choices=list(REGISTRY.keys()), | |
| value="İvme-Conversate-v2-Base (recommended)", | |
| label="Model", | |
| ) | |
| prompt_box = gr.Textbox( | |
| label="Prompt", | |
| placeholder="The theory of relativity states that…", | |
| lines=3, | |
| value="The theory of relativity states that", | |
| ) | |
| with gr.Row(): | |
| gen_btn = gr.Button("Generate", variant="primary", scale=3) | |
| clear_btn = gr.Button("Clear", scale=1) | |
| output_box = gr.Textbox( | |
| label="Continuation", | |
| lines=12, | |
| show_copy_button=True, | |
| elem_classes="ivme-output", | |
| interactive=False, | |
| ) | |
| gr.Examples(examples=[[e] for e in EXAMPLES], inputs=prompt_box, label="Try a prompt") | |
| with gr.Accordion("Settings", open=False): | |
| with gr.Row(): | |
| max_tokens = gr.Slider(16, 512, value=200, step=8, label="Max new tokens") | |
| temperature = gr.Slider(0.1, 2.0, value=0.7, step=0.05, label="Temperature") | |
| with gr.Row(): | |
| top_k = gr.Slider(0, 200, value=40, step=1, label="Top-k (0 = disabled)") | |
| rep_penalty = gr.Slider(1.0, 2.0, value=1.15, step=0.05, label="Repetition penalty") | |
| gen_inputs = [model_picker, prompt_box, max_tokens, temperature, top_k, rep_penalty] | |
| gen_btn.click(continue_text, gen_inputs, output_box) | |
| prompt_box.submit(continue_text, gen_inputs, output_box) | |
| clear_btn.click(lambda: ("", ""), None, [prompt_box, output_box], queue=False) | |
| # ── Tab 2: side-by-side compare ────────────────────────────────────── | |
| with gr.Tab("Compare v1 vs v2"): | |
| gr.Markdown( | |
| "Run the **same prompt and settings** through both models at once " | |
| "to see the difference training data made, plus the benchmark deltas below." | |
| ) | |
| cmp_prompt = gr.Textbox( | |
| label="Prompt", | |
| lines=3, | |
| value="Once upon a time, there was a", | |
| ) | |
| with gr.Row(): | |
| cmp_gen_btn = gr.Button("Generate both", variant="primary", scale=3) | |
| cmp_clear_btn = gr.Button("Clear", scale=1) | |
| with gr.Row(): | |
| cmp_out_v1 = gr.Textbox( | |
| label="v1-Base", | |
| lines=10, | |
| show_copy_button=True, | |
| elem_classes="ivme-output", | |
| interactive=False, | |
| ) | |
| cmp_out_v2 = gr.Textbox( | |
| label="v2-Base", | |
| lines=10, | |
| show_copy_button=True, | |
| elem_classes="ivme-output", | |
| interactive=False, | |
| ) | |
| gr.Examples(examples=[[e] for e in EXAMPLES], inputs=cmp_prompt, label="Try a prompt") | |
| with gr.Accordion("Settings", open=False): | |
| with gr.Row(): | |
| cmp_max_tokens = gr.Slider(16, 512, value=150, step=8, label="Max new tokens") | |
| cmp_temperature = gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="Temperature") | |
| with gr.Row(): | |
| cmp_top_k = gr.Slider(0, 200, value=50, step=1, label="Top-k (0 = disabled)") | |
| cmp_rep_penalty = gr.Slider(1.0, 2.0, value=1.0, step=0.05, label="Repetition penalty") | |
| gr.Markdown("### Benchmark improvement, v1 → v2") | |
| gr.Dataframe( | |
| headers=["Benchmark", "v1", "v2", "Δ (v1 → v2)"], | |
| value=benchmark_table(), | |
| interactive=False, | |
| row_count=(len(BENCH), "fixed"), | |
| ) | |
| cmp_inputs = [cmp_prompt, cmp_max_tokens, cmp_temperature, cmp_top_k, cmp_rep_penalty] | |
| cmp_gen_btn.click(compare_generate, cmp_inputs, [cmp_out_v1, cmp_out_v2]) | |
| cmp_prompt.submit(compare_generate, cmp_inputs, [cmp_out_v1, cmp_out_v2]) | |
| cmp_clear_btn.click(lambda: ("", "", ""), None, [cmp_prompt, cmp_out_v1, cmp_out_v2], queue=False) | |
| demo.queue().launch() | |