ereniko's picture
Update app.py
fdad84f verified
Raw
History Blame Contribute Delete
13.1 kB
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 ────────────────────────────────────────────────────
@torch.no_grad()
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()