Spaces:
Sleeping
Sleeping
Download scripts/bench_vs_torch.py from spitfire4794/test1111111: direct link, hf CLI and curl.
- Browser
- Download file 6.01 kB
-
https://huggingface.co/spaces/spitfire4794/test1111111/resolve/main/scripts/bench_vs_torch.py
- Command line
-
hf download hf://spaces/spitfire4794/test1111111/scripts/bench_vs_torch.py
-
curl -L -o bench_vs_torch.py https://huggingface.co/spaces/spitfire4794/test1111111/resolve/main/scripts/bench_vs_torch.py
6.01 kB
| """Fair batch-one decode comparison: HF eager, torch.compile, and CISM. | |
| All baselines run the SAME model checkpoint in FP32 with a KV cache, greedy | |
| decoding, one torch thread, warmup discarded. torch.compile uses a manual | |
| decode loop with StaticCache (its recommended fast path), not slow | |
| use_cache=False generate(). CISM numbers come from the same machine via | |
| cism bench. Output: JSON to stdout. Exit 0 on success. | |
| """ | |
| import argparse | |
| import json | |
| import time | |
| import torch | |
| def timed(fn, warmup_tokens: int, measured_tokens: int): | |
| fn(warmup_tokens) | |
| start = time.perf_counter() | |
| generated = fn(measured_tokens) | |
| elapsed = time.perf_counter() - start | |
| return { | |
| "tokens": generated, | |
| "seconds": elapsed, | |
| "tokens_per_second": generated / elapsed if elapsed > 0 else 0, | |
| } | |
| def run_eager(model, input_ids, eos_pad, count, warmup): | |
| def loop(tokens): | |
| out = model.generate( | |
| input_ids, attention_mask=None, max_new_tokens=tokens, | |
| min_new_tokens=tokens, do_sample=False, num_beams=1, | |
| use_cache=True, pad_token_id=eos_pad, | |
| ) | |
| return tokens | |
| result = timed(loop, warmup, count) | |
| result["method"] = "transformers generate(use_cache=True) eager" | |
| return result | |
| def run_manual_eager(model, input_ids, count, warmup): | |
| """Raw decode loop without generate()'s framework overhead.""" | |
| from transformers import StaticCache | |
| cache = StaticCache(config=model.config, max_batch_size=1, | |
| max_cache_len=warmup + count + len(input_ids[0]), | |
| device="cpu", dtype=torch.float32) | |
| def loop(tokens): | |
| with torch.inference_mode(): | |
| cache.reset() | |
| generated = 0 | |
| out = model(input_ids, past_key_values=cache, use_cache=True) | |
| token = int(out.logits[0, -1].argmax()) | |
| generated += 1 | |
| one = torch.tensor([[token]]) | |
| while generated < tokens: | |
| out = model(one, past_key_values=cache, use_cache=True) | |
| token = int(out.logits[0, -1].argmax()) | |
| generated += 1 | |
| return generated | |
| result = timed(loop, warmup, count) | |
| result["method"] = "manual eager decode loop + StaticCache (no generate overhead)" | |
| return result | |
| def _dynamo_snapshot(): | |
| import torch._dynamo | |
| return {name: sum(counts.values()) for name, counts in torch._dynamo.utils.counters.items()} | |
| def _dynamo_delta(before, after): | |
| keys = set(before) | set(after) | |
| return {key: after.get(key, 0) - before.get(key, 0) for key in keys if after.get(key, 0) != before.get(key, 0)} | |
| def run_compiled(model, input_ids, count, warmup, dynamic): | |
| from transformers import StaticCache | |
| cache = StaticCache(config=model.config, max_batch_size=1, | |
| max_cache_len=warmup + count + len(input_ids[0]), | |
| device="cpu", dtype=torch.float32) | |
| compiled = torch.compile(model, dynamic=dynamic) | |
| def loop(tokens): | |
| with torch.inference_mode(): | |
| cache.reset() | |
| generated = 0 | |
| out = compiled(input_ids, past_key_values=cache, use_cache=True) | |
| token = int(out.logits[0, -1].argmax()) | |
| generated += 1 | |
| one = torch.tensor([[token]]) | |
| while generated < tokens: | |
| out = compiled(one, past_key_values=cache, use_cache=True) | |
| token = int(out.logits[0, -1].argmax()) | |
| generated += 1 | |
| return generated | |
| # Warmup must absorb ALL compilation and recompilation; counters are reset | |
| # afterwards so the measured window only reports steady-state guard hits. | |
| loop(warmup) | |
| loop(warmup) | |
| before = _dynamo_snapshot() | |
| generated = timed(loop, warmup, count) | |
| after = _dynamo_snapshot() | |
| generated["dynamo_counter_delta_during_measurement"] = _dynamo_delta(before, after) | |
| generated["method"] = f"torch.compile(model, dynamic={dynamic}) + StaticCache decode loop" | |
| return generated | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("model") | |
| parser.add_argument("--revision", default=None) | |
| parser.add_argument("--threads", type=int, default=1) | |
| parser.add_argument("--tokens", type=int, default=64) | |
| parser.add_argument("--warmup", type=int, default=32) | |
| parser.add_argument("--skip-compile", action="store_true") | |
| args = parser.parse_args() | |
| torch.set_num_threads(args.threads) | |
| torch.set_num_interop_threads(1) | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| tokenizer = AutoTokenizer.from_pretrained(args.model, revision=args.revision) | |
| model = AutoModelForCausalLM.from_pretrained( | |
| args.model, revision=args.revision, dtype=torch.float32, | |
| attn_implementation="eager", | |
| ).to("cpu").eval() | |
| config = {"max_position_embeddings": model.config.max_position_embeddings} | |
| ids = tokenizer("The future of artificial intelligence is", return_tensors="pt").input_ids | |
| eos_pad = tokenizer.eos_token_id or 0 | |
| report = { | |
| "model": args.model, "torch": torch.__version__, "threads": args.threads, | |
| "precision": "fp32", "greedy": True, "warmup_tokens": args.warmup, | |
| "measured_tokens": args.tokens, | |
| } | |
| report["eager_generate"] = run_eager(model, ids, eos_pad, args.tokens, args.warmup) | |
| report["eager_manual_loop"] = run_manual_eager(model, ids, args.tokens, args.warmup) | |
| if args.skip_compile: | |
| report["compiled_dynamic_false"] = {"skipped": True} | |
| report["compiled_dynamic_true"] = {"skipped": True} | |
| else: | |
| for dynamic in (False, True): | |
| key = f"compiled_dynamic_{str(dynamic).lower()}" | |
| try: | |
| report[key] = run_compiled(model, ids, args.tokens, args.warmup, dynamic) | |
| except Exception as error: | |
| report[key] = {"failed": f"{type(error).__name__}: {error}"} | |
| print(json.dumps(report, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |