uuugi's picture
Initial release of GCLM: Code, Paper, Benchmarks and Examples
44810a8 verified
Raw
History Blame Contribute Delete
6.38 kB
"""
Benchmark: Complexity and Scaling Analysis (Paper Experiment 2 & 3)
Evaluates:
1. Offline BFS Build Time (ms) vs Number of States |S|
2. Memory Footprint (MB) vs Number of States |S|
3. Online Token Masking Latency (us) vs Number of States |S| (Empirical O(1) Verification)
Generates publication-quality figure: 'paper_figure_scaling.png'
"""
import os
import time
import torch
import matplotlib.pyplot as plt
from tabulate import tabulate
from typing import List, Dict
from core.fsm_builder import ReachabilityFSM
from core.logit_processor import GoalReachabilityLogitsProcessor
def run_scaling_benchmark(output_plot_path: str = "paper_figure_scaling.png"):
print("\n" + "=" * 80)
print(" [EXPERIMENT 2 & 3] FSM Complexity & O(1) Latency Scaling Benchmark")
print("=" * 80)
device = "cpu" # Run on CPU for strict baseline consistency
vocab_sizes = [32000, 151643] # Standard 32k vs Qwen 151k
state_counts = [10, 50, 200, 1000, 5000, 10000]
max_budget = 50
num_latency_trials = 500
results: Dict[int, Dict[str, List]] = {
v: {"states": [], "build_time_ms": [], "memory_mb": [], "online_latency_us": []}
for v in vocab_sizes
}
table_rows = []
for vocab_size in vocab_sizes:
print(f"\n--- Testing Vocab Size: {vocab_size:,} ---")
for num_states in state_counts:
# 1. Build FSM structure
fsm = ReachabilityFSM(num_states=num_states, vocab_size=vocab_size, device=device)
# Add chain transitions + some branches
for s in range(num_states - 1):
fsm.add_transition(s, token_id=s % vocab_size, to_state=s + 1)
# Add branching transition
if s + 2 < num_states:
fsm.add_transition(s, token_id=(s * 7 + 13) % vocab_size, to_state=s + 2)
fsm.set_goal_states([num_states - 1])
# 2. Measure Offline BFS Build Time
start_b = time.perf_counter()
fsm.build_reachability(max_steps=max_budget)
end_b = time.perf_counter()
build_time_ms = (end_b - start_b) * 1000
# 3. Measure Memory Footprint
mem_bytes = fsm.memory_footprint_bytes()
mem_mb = mem_bytes / (1024 * 1024)
# 4. Measure Online 1-Token Masking Latency
processor = GoalReachabilityLogitsProcessor(fsm=fsm, max_budget=max_budget)
dummy_input = torch.tensor([[0]], dtype=torch.long, device=device)
dummy_scores = torch.randn((1, vocab_size), device=device)
# Warmup
for _ in range(50):
_ = processor(dummy_input, dummy_scores.clone())
start_l = time.perf_counter()
for _ in range(num_latency_trials):
_ = processor(dummy_input, dummy_scores)
end_l = time.perf_counter()
online_latency_us = ((end_l - start_l) / num_latency_trials) * 1e6
results[vocab_size]["states"].append(num_states)
results[vocab_size]["build_time_ms"].append(build_time_ms)
results[vocab_size]["memory_mb"].append(mem_mb)
results[vocab_size]["online_latency_us"].append(online_latency_us)
table_rows.append([
f"{vocab_size:,}",
f"{num_states:,}",
f"{build_time_ms:.2f} ms",
f"{mem_mb:.2f} MB",
f"{online_latency_us:.2f} us ({online_latency_us/1000:.4f} ms)",
])
headers = [
"Vocab Size",
"States |S|",
"Offline BFS Time",
"Memory Footprint",
"Online 1-Token Latency",
]
print("\n" + tabulate(table_rows, headers=headers, tablefmt="grid"))
# -------------------------------------------------------------
# Generate Publication-Quality Figures (Matplotlib)
# -------------------------------------------------------------
print(f"\n[PLOT] Generating publication-quality figures -> {output_plot_path}...")
plt.style.use("seaborn-v0_8-whitegrid" if "seaborn-v0_8-whitegrid" in plt.style.available else "default")
fig, axes = plt.subplots(1, 3, figsize=(18, 5), dpi=300)
colors = {32000: "#1f77b4", 151643: "#ff7f0e"}
labels = {32000: "Vocab: 32,000 (LLaMA/Mistral)", 151643: "Vocab: 151,643 (Qwen2.5)"}
# Plot 1: Offline BFS Build Time
for v in vocab_sizes:
axes[0].plot(
results[v]["states"],
results[v]["build_time_ms"],
marker="o",
linewidth=2,
color=colors[v],
label=labels[v],
)
axes[0].set_title("(a) Offline BFS Precomputation Time", fontsize=12, fontweight="bold")
axes[0].set_xlabel("FSM State Count |S|", fontsize=11)
axes[0].set_ylabel("Build Time (ms)", fontsize=11)
axes[0].set_xscale("log")
axes[0].legend(fontsize=9)
axes[0].grid(True, linestyle="--", alpha=0.6)
# Plot 2: Memory Footprint
for v in vocab_sizes:
axes[1].plot(
results[v]["states"],
results[v]["memory_mb"],
marker="s",
linewidth=2,
color=colors[v],
label=labels[v],
)
axes[1].set_title("(b) Memory Footprint (VRAM / RAM)", fontsize=12, fontweight="bold")
axes[1].set_xlabel("FSM State Count |S|", fontsize=11)
axes[1].set_ylabel("Memory (MB)", fontsize=11)
axes[1].set_xscale("log")
axes[1].legend(fontsize=9)
axes[1].grid(True, linestyle="--", alpha=0.6)
# Plot 3: Online 1-Token Latency (O(1) Verification)
for v in vocab_sizes:
axes[2].plot(
results[v]["states"],
results[v]["online_latency_us"],
marker="^",
linewidth=2,
color=colors[v],
label=labels[v],
)
axes[2].set_title("(c) Online Runtime Latency: Strict O(1)", fontsize=12, fontweight="bold")
axes[2].set_xlabel("FSM State Count |S|", fontsize=11)
axes[2].set_ylabel("Latency per Token (us)", fontsize=11)
axes[2].set_xscale("log")
axes[2].legend(fontsize=9)
axes[2].grid(True, linestyle="--", alpha=0.6)
plt.tight_layout()
plt.savefig(output_plot_path, bbox_inches="tight")
plt.close()
print(f"[SUCCESS] Figure successfully saved to: {os.path.abspath(output_plot_path)}\n")
if __name__ == "__main__":
run_scaling_benchmark()