-
Notifications
You must be signed in to change notification settings - Fork 581
specdec_bench: emit speculation_profile.json alongside acceptance metrics #2247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cafd30e
878421c
c170403
095124e
f0b449f
1c6ddd3
43e57bf
8ba9025
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,15 +16,30 @@ | |
| import json | ||
| import os | ||
|
|
||
| from ..speculation_profile import build_profile | ||
| from .base import Metric | ||
|
|
||
|
|
||
| class AcceptanceRate(Metric): | ||
| # Set once per run by run.py via set_profile_metadata(). Class-level so the | ||
| # MTBench/SpecBench subclasses pick it up without extra wiring, mirroring how | ||
| # Metric.update_directory() distributes the output path. | ||
| profile_metadata = None | ||
|
|
||
| def __init__(self): | ||
| super().__init__() | ||
| self.prompt_ar = {} | ||
| self.name = "acceptance_rate" | ||
|
|
||
| @classmethod | ||
| def set_profile_metadata(cls, metadata): | ||
| """Describe what is being measured, so a speculation_profile.json can be written. | ||
|
|
||
| Without this the acceptance numbers are still computed and written as before; | ||
| only the deployment-facing profile is skipped. | ||
| """ | ||
| AcceptanceRate.profile_metadata = metadata | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| def process_step(self, step_outputs, request_id, turn_id): | ||
| if request_id not in self.prompt_ar: | ||
| self.prompt_ar[request_id] = {} | ||
|
|
@@ -63,6 +78,43 @@ def _process_lengths(self, lengths): | |
| for k, cond_ar in self.out["Conditional_Acceptance_Rate"].items(): | ||
| running_joint *= cond_ar | ||
| self.out["Joint_Acceptance_Rate"][k] = running_joint | ||
| # Emitted here rather than in each process_final(): this is the single point | ||
| # where the acceptance distribution is final, and all three variants | ||
| # (AcceptanceRate / MTBench / SpecBench) route through it, so none can | ||
| # silently stop producing a profile. | ||
| self._write_speculation_profile() | ||
|
|
||
| def _write_speculation_profile(self): | ||
| """Write speculation_profile.json — the deployment-facing view of these numbers. | ||
|
|
||
| Skipped silently when run.py did not supply metadata (e.g. an ad-hoc run with | ||
| no --save_dir): the profile is only meaningful if we can say what it describes. | ||
| """ | ||
| metadata = AcceptanceRate.profile_metadata | ||
| if not metadata or not self.directory: | ||
| return | ||
| profile = build_profile( | ||
| self.out, | ||
| per_category=self.out.get("Category_AL"), | ||
| **metadata, | ||
| ) | ||
| path = os.path.join(self.directory, "speculation_profile.json") | ||
| os.makedirs(self.directory, exist_ok=True) | ||
| with open(path, "w") as f: | ||
| json.dump(profile, f, indent=2) | ||
| validation = profile.get("validation") or {} | ||
| consistency = validation.get("mean_consistency") or {} | ||
| if not consistency.get("passed", True): | ||
| # Loud, because a failure here means the vectors do not describe the | ||
| # measured mean — the profile is wrong in a way downstream cannot detect. | ||
| print( | ||
| "WARNING: speculation profile failed its mean-consistency check " | ||
| f"(implied {consistency.get('implied_mean_accept_length')} vs " | ||
| f"reported {consistency.get('reported_mean_accept_length')}). " | ||
| f"See {path}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 5 'update_directory\(args\.save_dir\)|save_dir is not None' examples/specdec_bench/run.py
rg -n -C 5 'WARNING: speculation profile|Wrote speculation profile|See \{path\}' \
examples/specdec_bench/specdec_bench/metrics/acceptance_rate.pyRepository: NVIDIA/Model-Optimizer Length of output: 1704 Information Disclosure (CWE-532): Insertion of Sensitive Information into Log File Reachability: External · Exploitability: Moderate Do not print the full artifact path. Log a generic completion message or a redacted identifier instead of 🤖 Prompt for AI AgentsSource: Path instructions |
||
| ) | ||
| else: | ||
| print(f"Wrote speculation profile to {path}") | ||
|
|
||
| def process_final(self, text_outputs): | ||
| all_ar = [] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: NVIDIA/Model-Optimizer
Length of output: 13545
🏁 Script executed:
Repository: NVIDIA/Model-Optimizer
Length of output: 22356
Require a valid DFLASH
block_sizebefore building the profile.args.block_sizedefaults toNone, but the profile falls back toargs.draft_lengthwhilerun_simplepassesNoneto the wrappers asspeculative_num_draft_tokens. The wrappers therefore receiveNoneinstead of their fallback value, so the profile can record a K that does not match the DFLASH engine configuration. Reject a missing or non-positiveblock_sizebefore starting the run.🤖 Prompt for AI Agents