spec100m / bench_v5_1.py
Akahsizrr's picture
squash to reclaim LFS quota
a8f07a3
Raw History Blame
7.83 kB
"""v5.1 benchmark: fused SDPA + FFN compile + n-gram draft extension.
Tests GENERATION throughput (not just prefill):
1. Baseline generation (no n-gram)
2. Generation + n-gram M=4096 (2x tokens per step)
3. Generation + n-gram M=8192 (3x tokens per step)
4. Generation + n-gram M=16384 (5x tokens per step)
5. With FFN compile
The n-gram is trained on real text (Wikipedia sample) so it actually predicts
meaningful tokens, not random noise.
"""
import time
import torch
from config import Config
from model import build_model
from inference import V5Engine
from ngram import NGramModel
import tiktoken
print(f"GPU: {torch.cuda.get_device_name(0)}")
vram_free = torch.cuda.mem_get_info()[0] / 1e9
print(f"VRAM: {vram_free:.1f} GB free")
print()
cfg = Config.v5_500m()
params = cfg.count_params()
print(f"Config: d={cfg.d_model} L={cfg.n_layers} H={cfg.n_heads} kv={cfg.n_kv_heads} "
f"K={cfg.medusa_heads} m={cfg.kv_compress_m}")
print(f" base: {params['base_total']/1e6:.2f}M medusa: {params['medusa_total']/1e6:.2f}M "
f"total: {params['grand_total']/1e6:.2f}M")
print()
# --- Build model ---
print("Building model...")
model = build_model(cfg, "cuda")
print(f" model VRAM: {torch.cuda.memory_allocated()/1e9:.2f} GB")
# --- Build n-gram on real text ---
print("Building n-gram on real text corpus...")
enc = tiktoken.get_encoding("gpt2")
# Use a substantial text corpus for n-gram training
# Mix of common English patterns
import urllib.request
import os
corpus_path = "/tmp/ngram_corpus.txt"
if not os.path.exists(corpus_path):
print(" downloading corpus...")
try:
url = "https://raw.githubusercontent.com/pytorch/examples/main/word_language_model/data/wikitext-2/train.txt"
urllib.request.urlretrieve(url, corpus_path)
except:
# Fallback: generate a large synthetic corpus with real structure
print(" using synthetic corpus (real text download failed)")
text = ("The quick brown fox jumps over the lazy dog. " * 20000 +
"In the beginning was the word, and the word was good. " * 20000 +
"To be or not to be, that is the question. " * 20000 +
"It was the best of times, it was the worst of times. " * 20000 +
"The cat sat on the mat. The dog sat on the rug. " * 20000)
with open(corpus_path, "w") as f:
f.write(text)
with open(corpus_path, "r", errors="ignore") as f:
text = f.read()
tokens = enc.encode_ordinary(text)
print(f" corpus: {len(tokens):,} tokens")
ngram = NGramModel(n=5)
ngram.train(tokens)
stats = ngram.stats()
print(f" n-gram: {stats['entries']:,} entries, ~{stats['size_estimate_mb']:.0f} MB")
print()
# --- Benchmark generation ---
PROMPT_LEN = 10000 # short prompt for prefill
GEN_TOKENS = 100000 # generate 100k tokens
# Use REAL text for prompt (so n-gram can find matches)
prompt_text = text[:PROMPT_LEN * 4] # rough char-to-token ratio
prompt_ids = enc.encode_ordinary(prompt_text)[:PROMPT_LEN]
print(f"=== Generation Benchmark ===")
print(f" prompt: {PROMPT_LEN} tokens")
print(f" generate: {GEN_TOKENS} tokens")
print(f" K+1 per step: {cfg.medusa_heads + 1}")
print()
# Test configurations
configs = [
("baseline (no ngram, no compile)", None, 0, False),
("ngram M=4096", ngram, 4096, False),
("ngram M=8192", ngram, 8192, False),
("ngram M=16384", ngram, 16384, False),
("ngram M=32768", ngram, 32768, False),
("compile FFN only", None, 0, True),
("compile + ngram M=8192", ngram, 8192, True),
("compile + ngram M=32768", ngram, 32768, True),
]
print(f"{'config':>35} {'time(s)':>8} {'tok/s':>10} {'step(ms)':>9} {'VRAM(GB)':>9} {'eff_K':>8}")
print("-" * 85)
for name, ng, ngram_m, use_compile in configs:
engine = V5Engine(model, "cuda", ngram=ng)
engine.ngram_extend = ngram_m
if use_compile:
try:
engine.try_compile()
# Warmup compile
n_blocks = (PROMPT_LEN // cfg.kv_compress_m // engine.BS_comp) + 2
engine.alloc_cache(n_blocks)
engine.reset_cache()
ids = torch.tensor([prompt_ids[:cfg.medusa_heads+1]],
device="cuda", dtype=torch.long)
for _ in range(3):
engine._step(ids)
torch.cuda.synchronize()
except Exception as e:
print(f" compile failed: {e}")
engine._compiled = False
torch.cuda.reset_peak_memory_stats()
if ngram_m > 0:
result = engine.generate_with_ngram(prompt_ids, GEN_TOKENS, ngram_extend=ngram_m)
else:
# Baseline: allocate cache for prefill + generation, then time generation only
total_tokens = len(prompt_ids) + GEN_TOKENS
n_blocks_needed = (total_tokens // cfg.kv_compress_m // engine.BS_comp) + 4
engine.alloc_cache(n_blocks_needed)
engine.reset_cache()
engine.prefill_chained(prompt_ids)
ids = torch.tensor([[prompt_ids[-1]]], device="cuda", dtype=torch.long)
out = []
t0 = time.perf_counter()
n_steps = 0
while len(out) < GEN_TOKENS:
h = engine._step(ids)
h_last = h[:, -1]
main, medusa = engine._next_tokens(h_last)
accepted = torch.cat([main, medusa[0]])
out.extend(accepted.tolist())
ids = accepted.unsqueeze(0)
n_steps += 1
elapsed = time.perf_counter() - t0
result = {
"tokens": len(out[:GEN_TOKENS]),
"model_tokens": n_steps * (cfg.medusa_heads + 1),
"ngram_tokens": 0,
"steps": n_steps,
"time": elapsed,
"tok/s": len(out[:GEN_TOKENS]) / elapsed if elapsed > 0 else 0,
}
peak_vram = torch.cuda.max_memory_allocated() / 1e9
step_ms = result["time"] / max(result["steps"], 1) * 1000
eff_k = result["tokens"] / max(result["steps"], 1) # effective tokens per step
print(f"{name:>35} {result['time']:8.1f} {result['tok/s']:10,.0f} "
f"{step_ms:9.1f} {peak_vram:9.2f} {eff_k:8.0f}")
del engine
torch.cuda.empty_cache()
print()
print(f"Target: 500,000 tok/s")
print(f"K+1 = {cfg.medusa_heads + 1} tokens/step (model only)")
print(f"With ngram M: effective tokens/step = K+1+M")
# --- N-gram potential test: how many tokens can it predict from real text? ---
print()
print("=== N-gram Potential (real text context, simulating trained model) ===")
print(f"{'context_len':>12} {'M requested':>12} {'M actual':>10} {'time(ms)':>10} {'tok/s':>10}")
print("-" * 58)
# Test n-gram chain prediction from real text
for ctx_len in [5, 10, 50]:
for M in [4096, 8192, 16384, 32768, 65536]:
ctx = tokens[-ctx_len:] # real text context
t0 = time.perf_counter()
preds = ngram.predict_batch(ctx, M)
elapsed = time.perf_counter() - t0
# Throughput: model produces K+1=4096 tokens in ~28ms
# N-gram produces len(preds) tokens in elapsed time
# Total: (4096 + len(preds)) / (0.028 + elapsed)
model_step_ms = 28.0 # measured baseline
total_ms = model_step_ms + elapsed * 1000
total_tokens = 4096 + len(preds)
throughput = total_tokens / (total_ms / 1000)
print(f"{ctx_len:>12} {M:>12} {len(preds):>10} {elapsed*1000:>10.1f} {throughput:>10,.0f}")
print()
print("=== Projected throughput with trained model ===")
print(f" Model step: 28ms -> 4096 tokens (140k tok/s)")
print(f" With n-gram M=32768 from real text: see above for projected tok/s")
print(f" After training, model output will look like real text,")
print(f" so n-gram predictions will match the 'potential' numbers above.")