import argparse
import json
import sys
from pathlib import Path
import torch
import torch.nn.functional as F
from transformers import GPT2LMHeadModel, GPT2Tokenizer
def load_model(device: str = "cpu"):
print("Loading GPT-2...", file=sys.stderr)
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")
model.to(device)
model.eval()
print("Model ready.", file=sys.stderr)
return model, tokenizer
def score_text(model, tokenizer, text: str, top_k: int = 5, raw_logits: bool = False, device: str = "cpu"):
input_ids = tokenizer.encode(text, return_tensors="pt").to(device)
n_tokens = input_ids.shape[1]
if n_tokens < 2:
raise ValueError("Text must encode to at least 2 tokens")
records = []
with torch.no_grad():
outputs = model(input_ids)
all_logits = outputs.logits[0]
for i in range(n_tokens - 1):
target_id = input_ids[0, i + 1].item()
logits_i = all_logits[i]
if raw_logits:
scores = logits_i
else:
scores = F.log_softmax(logits_i, dim=-1)
topk_values, topk_indices = torch.topk(scores, top_k)
top_entries = []
for val, idx in zip(topk_values, topk_indices):
tok_str = tokenizer.decode([idx.item()])
tok_bytes = list(tok_str.encode("utf-8"))
top_entries.append({
"token": tok_str,
"logprob": round(val.item(), 6),
"bytes": tok_bytes,
})
chosen_token = tokenizer.decode([target_id])
chosen_score = scores[target_id].item()
chosen_bytes = list(chosen_token.encode("utf-8"))
records.append({
"token": chosen_token,
"logprob": round(chosen_score, 6),
"bytes": chosen_bytes,
"top_logprobs": top_entries,
})
return records
def generate_text(model, tokenizer, prompt: str, max_tokens: int = 20,
top_k: int = 5, raw_logits: bool = False, device: str = "cpu"):
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)
records = []
with torch.no_grad():
for _ in range(max_tokens):
outputs = model(input_ids)
logits_next = outputs.logits[0, -1, :]
if raw_logits:
scores = logits_next
else:
scores = F.log_softmax(logits_next, dim=-1)
chosen_id = torch.argmax(scores).item()
chosen_score = scores[chosen_id].item()
chosen_token = tokenizer.decode([chosen_id])
chosen_bytes = list(chosen_token.encode("utf-8"))
topk_values, topk_indices = torch.topk(scores, top_k)
top_entries = []
for val, idx in zip(topk_values, topk_indices):
tok_str = tokenizer.decode([idx.item()])
tok_bytes = list(tok_str.encode("utf-8"))
top_entries.append({
"token": tok_str,
"logprob": round(val.item(), 6),
"bytes": tok_bytes,
})
records.append({
"token": chosen_token,
"logprob": round(chosen_score, 6),
"bytes": chosen_bytes,
"top_logprobs": top_entries,
})
if chosen_id == tokenizer.eos_token_id:
break
input_ids = torch.cat([
input_ids,
torch.tensor([[chosen_id]], device=device)
], dim=1)
return records
def format_openai(records: list, model_name: str = "gpt2") -> dict:
content = []
for r in records:
entry = {
"token": r["token"],
"logprob": r["logprob"],
"bytes": r["bytes"],
"top_logprobs": [
{"token": t["token"], "logprob": t["logprob"], "bytes": t["bytes"]}
for t in r["top_logprobs"]
],
}
content.append(entry)
generated_text = "".join(r["token"] for r in records)
return {
"id": "logprobe-demo",
"object": "chat.completion",
"model": model_name,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": generated_text,
},
"logprobs": {
"content": content,
},
}],
}
def format_vllm(records: list, model_name: str = "gpt2") -> dict:
tokens = [r["token"] for r in records]
token_logprobs = [r["logprob"] for r in records]
top_logprobs = []
for r in records:
top_dict = {}
for t in r["top_logprobs"]:
top_dict[t["token"]] = t["logprob"]
top_logprobs.append(top_dict)
generated_text = "".join(tokens)
return {
"id": "logprobe-demo",
"object": "text_completion",
"model": model_name,
"choices": [{
"index": 0,
"text": generated_text,
"logprobs": {
"tokens": tokens,
"token_logprobs": token_logprobs,
"top_logprobs": top_logprobs,
},
}],
}
def format_jsonl(records: list) -> str:
lines = []
for r in records:
entry = {
"token": r["token"],
"logprob": r["logprob"],
"bytes": r["bytes"],
}
lines.append(json.dumps(entry, ensure_ascii=False))
return "\n".join(lines) + "\n"
def main():
parser = argparse.ArgumentParser(
description="Generate logprob data from GPT-2 for logprobe demonstration",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Score existing text (teacher-forced, deterministic)
python generate_logprobs.py --score "The capital of France is Paris."
# Generate continuation from prompt
python generate_logprobs.py --prompt "Once upon a time" --max-tokens 30
# Output raw logits to demonstrate logprobe catching unnormalized data
python generate_logprobs.py --score "Hello world" --raw-logits
# All three formats at once
python generate_logprobs.py --score "The quick brown fox" --format openai --output demo_openai.json
python generate_logprobs.py --score "The quick brown fox" --format vllm --output demo_vllm.json
python generate_logprobs.py --score "The quick brown fox" --format jsonl --output demo_stream.jsonl
""",
)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument(
"--score", type=str, metavar="TEXT",
help="Teacher-forced: score each token in the given text (deterministic)"
)
mode.add_argument(
"--prompt", type=str, metavar="TEXT",
help="Autoregressive: generate a continuation from this prompt"
)
parser.add_argument(
"--format", choices=["openai", "vllm", "jsonl"], default="openai",
help="Output format (default: openai)"
)
parser.add_argument(
"--raw-logits", action="store_true",
help="Output raw logit scores instead of log-probabilities. "
"logprobe should detect this as unnormalized."
)
parser.add_argument(
"--top-k", type=int, default=5,
help="Number of top logprobs to include (default: 5)"
)
parser.add_argument(
"--max-tokens", type=int, default=20,
help="Max tokens to generate in prompt mode (default: 20)"
)
parser.add_argument(
"--output", "-o", type=str, default=None,
help="Output file (default: stdout)"
)
parser.add_argument(
"--device", type=str, default="cpu",
help="Device: cpu, cuda, mps (default: cpu)"
)
args = parser.parse_args()
model, tokenizer = load_model(args.device)
model_label = "gpt2"
if args.raw_logits:
model_label = "gpt2 (RAW LOGITS — not log-probabilities)"
if args.score:
records = score_text(
model, tokenizer, args.score,
top_k=args.top_k, raw_logits=args.raw_logits, device=args.device,
)
else:
records = generate_text(
model, tokenizer, args.prompt,
max_tokens=args.max_tokens, top_k=args.top_k,
raw_logits=args.raw_logits, device=args.device,
)
if args.format == "openai":
data = format_openai(records, model_label)
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
elif args.format == "vllm":
data = format_vllm(records, model_label)
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
elif args.format == "jsonl":
output = format_jsonl(records)
if args.output:
Path(args.output).write_text(output)
print(f"Wrote {args.output}", file=sys.stderr)
else:
print(output, end="")
if __name__ == "__main__":
main()