MarxistLeninist's picture
download
raw
7.44 kB
#!/usr/bin/env python3
"""Minimal speculative decoding implementation for toy claims C4 and C5."""
import torch
import numpy as np
from transformers import AutoModelForCausalLM, AutoTokenizer
def sample_from_logits(logits, temperature=1.0):
"""Sample a token from logits using temperature."""
if temperature == 0:
return logits.argmax(dim=-1)
probs = torch.softmax(logits / temperature, dim=-1)
return torch.multinomial(probs, num_samples=1).squeeze(-1)
def speculative_decode(target_model, draft_model, tokenizer, prompt, max_draft_len=4, max_new_tokens=50, device='cpu', draft_temp=1.2):
"""
Minimal speculative decoding.
Draft model uses temperature sampling; target uses greedy.
"""
target_model.to(device)
draft_model.to(device)
target_model.eval()
draft_model.eval()
input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device)
init_len = input_ids.shape[1]
acceptance_lengths = []
n_draft_tokens = 0
n_target_calls = 0
with torch.no_grad():
while input_ids.shape[1] < init_len + max_new_tokens:
current_len = input_ids.shape[1]
# Draft model generates k tokens with temperature sampling
draft_ids = input_ids.clone()
draft_tokens = []
for _ in range(max_draft_len):
draft_logits = draft_model(draft_ids).logits[:, -1, :]
next_token = sample_from_logits(draft_logits, temperature=draft_temp)
draft_tokens.append(next_token.item())
draft_ids = torch.cat([draft_ids, next_token.unsqueeze(0)], dim=1)
n_draft_tokens += len(draft_tokens)
# Target model verifies all draft tokens in one forward pass (greedy)
draft_tensor = torch.tensor([draft_tokens], device=device)
verify_ids = torch.cat([input_ids, draft_tensor], dim=1)
target_logits = target_model(verify_ids).logits
n_target_calls += 1
# Verify each draft token greedily
accepted = 0
for i, draft_tok in enumerate(draft_tokens):
logit_pos = current_len + i - 1
if logit_pos >= target_logits.shape[1]:
break
target_tok = target_logits[:, logit_pos, :].argmax(dim=-1).item()
if target_tok == draft_tok:
accepted += 1
input_ids = torch.cat([input_ids, torch.tensor([[draft_tok]], device=device)], dim=1)
else:
input_ids = torch.cat([input_ids, torch.tensor([[target_tok]], device=device)], dim=1)
break
else:
# All draft tokens accepted; append bonus token from target
if input_ids.shape[1] < init_len + max_new_tokens:
bonus_tok = target_logits[:, -1, :].argmax(dim=-1)
input_ids = torch.cat([input_ids, bonus_tok.unsqueeze(0)], dim=1)
acceptance_lengths.append(accepted)
if input_ids.shape[1] >= init_len + max_new_tokens:
break
generated_text = tokenizer.decode(input_ids[0], skip_special_tokens=True)
return generated_text, acceptance_lengths, n_draft_tokens, n_target_calls
def generate_random_prompt(tokenizer, length=20, seed=None):
"""Generate a prompt with random tokens."""
if seed is not None:
torch.manual_seed(seed)
vocab_size = tokenizer.vocab_size
random_ids = torch.randint(0, vocab_size, (1, length))
return tokenizer.decode(random_ids[0], skip_special_tokens=True)
def main():
print("Loading models...")
model_name = "gpt2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
target_model = AutoModelForCausalLM.from_pretrained(model_name)
draft_model = AutoModelForCausalLM.from_pretrained(model_name)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}")
# Claim 4: Compare random tokens vs real prompts
print("\n" + "="*60)
print("CLAIM 4: Random tokens vs real prompts")
print("="*60)
real_prompts = [
"The quick brown fox jumps over the lazy dog.",
"In machine learning, speculative decoding accelerates inference by",
"The capital of France is Paris, and it is known for",
"To solve this equation, we first need to",
"The weather today is sunny with a chance of"
]
results_c4 = {'real': [], 'random': []}
for prompt in real_prompts:
_, al_real, _, _ = speculative_decode(target_model, draft_model, tokenizer, prompt, max_draft_len=4, max_new_tokens=20, device=device, draft_temp=1.2)
avg_al = np.mean(al_real) if al_real else 0
results_c4['real'].append(avg_al)
print(f"Real prompt: avg AL = {avg_al:.2f} (details: {al_real})")
for seed in range(5):
random_prompt = generate_random_prompt(tokenizer, length=15, seed=seed)
_, al_rand, _, _ = speculative_decode(target_model, draft_model, tokenizer, random_prompt, max_draft_len=4, max_new_tokens=20, device=device, draft_temp=1.2)
avg_al = np.mean(al_rand) if al_rand else 0
results_c4['random'].append(avg_al)
print(f"Random prompt: avg AL = {avg_al:.2f} (details: {al_rand})")
real_avg = np.mean(results_c4['real'])
rand_avg = np.mean(results_c4['random'])
print(f"\nReal prompts avg AL: {real_avg:.2f}")
print(f"Random prompts avg AL: {rand_avg:.2f}")
print(f"Random > Real: {rand_avg > real_avg}")
# Claim 5: Optimal draft length
print("\n" + "="*60)
print("CLAIM 5: Optimal draft length")
print("="*60)
prompt = "The weather today is"
results_c5 = {}
for draft_len in [1, 2, 3, 4]:
_, al, n_draft, n_target = speculative_decode(target_model, draft_model, tokenizer, prompt, max_draft_len=draft_len, max_new_tokens=20, device=device, draft_temp=1.2)
avg_al = np.mean(al) if al else 0
total_generated = sum(al) + len(al)
speedup = total_generated / n_target if n_target > 0 else 0
results_c5[draft_len] = {'avg_al': avg_al, 'speedup': speedup, 'n_target': n_target, 'al_list': al}
print(f"Draft length {draft_len}: avg AL = {avg_al:.2f}, approx speedup = {speedup:.2f}x, n_target={n_target}, ALs={al}")
optimal_dl = max(results_c5, key=lambda k: results_c5[k]['speedup'])
print(f"\nOptimal draft length (by speedup): {optimal_dl}")
# Save results
import json
with open('outputs/claims_c4_c5.json', 'w') as f:
json.dump({
'claim4': {
'real_avg_al': float(real_avg),
'random_avg_al': float(rand_avg),
'random_gt_real': bool(rand_avg > real_avg),
'real_details': [float(x) for x in results_c4['real']],
'random_details': [float(x) for x in results_c4['random']]
},
'claim5': {
str(k): {kk: (float(vv) if isinstance(vv, (int, float, np.floating)) else vv) for kk, vv in v.items()}
for k, v in results_c5.items()
},
'optimal_draft_length': int(optimal_dl)
}, f, indent=2)
print("\nResults saved to outputs/claims_c4_c5.json")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
7.44 kB
·
Xet hash:
152490079345602178227180ef71468356989c690be60bd0133e61c6e7f34c2a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.