-
Notifications
You must be signed in to change notification settings - Fork 6
(2/n) Organize Benchmark Execution Into Responsibility-Owned Packages #80
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
bc135a8
Organize benchmark and artifact packages
kargibora a4ceb43
Move benchmark dispatch out of pairwise
kargibora ab0e602
refactor: rename slugify -> safe_filename (review feedback)
kargibora cd0627f
fix: ruff check
kargibora 5334fb9
fix: update fluency paths after package move
kargibora b1b75d7
docs: update dataset package reference
kargibora File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| """Run artifacts and reproducibility metadata.""" | ||
|
|
||
| from judgearena.artifacts.metadata import to_jsonable, write_run_metadata | ||
| from judgearena.artifacts.run import ( | ||
| prepare_run_directory, | ||
| safe_filename, | ||
| write_run_metadata_safely, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "prepare_run_directory", | ||
| "safe_filename", | ||
| "to_jsonable", | ||
| "write_run_metadata", | ||
| "write_run_metadata_safely", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| """Shared lifecycle helpers for benchmark result directories.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from judgearena.artifacts.metadata import write_run_metadata | ||
| from judgearena.log import attach_file_handler, get_logger, make_run_log_path | ||
|
|
||
| if TYPE_CHECKING: | ||
| from judgearena.config import RunConfig | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
|
|
||
| def safe_filename(value: str) -> str: | ||
| """Return a filesystem-safe model, task, or benchmark name.""" | ||
| slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") | ||
| return slug or "value" | ||
|
|
||
|
|
||
| def prepare_run_directory( | ||
| cfg: RunConfig, path: str | Path, *, attach_log: bool = True | ||
| ) -> Path: | ||
| """Create a result directory and persist its resolved configuration.""" | ||
| output_dir = Path(path) | ||
| output_dir.mkdir(parents=True, exist_ok=True) | ||
| if attach_log and not cfg.run.no_log_file: | ||
| attach_file_handler(make_run_log_path(output_dir)) | ||
|
|
||
| from judgearena.config import dump_config | ||
|
|
||
| dump_config(cfg, output_dir / "config.yaml") | ||
| return output_dir | ||
|
|
||
|
|
||
| def write_run_metadata_safely(**kwargs): | ||
| """Write reproducibility metadata without discarding completed results.""" | ||
| try: | ||
| return write_run_metadata(**kwargs) | ||
| except OSError as exc: | ||
| logger.warning("Failed to write run metadata: %s", exc) | ||
| return None | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Benchmark-specific runners and evaluation logic.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Arena-anchored ELO benchmark implementation.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| """Pure sampling and Bradley-Terry rating logic for arena-anchored ELO.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import hashlib | ||
| import json | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
| from sklearn.linear_model import LogisticRegression | ||
|
|
||
|
|
||
| def winner_to_pref(winner: str) -> float | None: | ||
| """Convert a hard winner label to a continuous preference value.""" | ||
| if winner == "model_a": | ||
| return 0.0 | ||
| if winner == "model_b": | ||
| return 1.0 | ||
| if winner in ("tie", "tie (bothbad)"): | ||
| return 0.5 | ||
| return None | ||
|
|
||
|
|
||
| def _is_nan_pref(pref) -> bool: | ||
| return pref is None or (isinstance(pref, float) and np.isnan(pref)) | ||
|
|
||
|
|
||
| def fit_bradley_terry( | ||
| df: pd.DataFrame, | ||
| pref_col: str = "pref", | ||
| scale: float = 400, | ||
| base: float = 10, | ||
| init_rating: float = 1000, | ||
| baseline_model: str | None = None, | ||
| baseline_rating: float = 1000, | ||
| ) -> dict[str, float]: | ||
| """Fit Bradley-Terry ratings via weighted logistic regression.""" | ||
| df = df.dropna(subset=[pref_col]) | ||
| if df.empty: | ||
| return {} | ||
|
|
||
| grouped = ( | ||
| df.groupby(["model_a", "model_b", pref_col]).size().reset_index(name="count") | ||
| ) | ||
| all_models = sorted(set(grouped["model_a"]) | set(grouped["model_b"])) | ||
| models = pd.Series(np.arange(len(all_models)), index=all_models) | ||
|
|
||
| model_a = grouped["model_a"].map(models).to_numpy() | ||
| model_b = grouped["model_b"].map(models).to_numpy() | ||
| prefs = grouped[pref_col].to_numpy(dtype=float) | ||
| counts = grouped["count"].to_numpy(dtype=float) | ||
| n = len(grouped) | ||
|
|
||
| design = np.zeros((2 * n, len(models))) | ||
| top = np.arange(n) | ||
| bottom = n + top | ||
| design[top, model_a] = np.log(base) | ||
| design[top, model_b] = -np.log(base) | ||
| design[bottom, model_a] = np.log(base) | ||
| design[bottom, model_b] = -np.log(base) | ||
|
|
||
| labels = np.concatenate([np.ones(n), np.zeros(n)]) | ||
| sample_weights = np.concatenate([(1.0 - prefs) * counts, prefs * counts]) | ||
| if sample_weights.sum() == 0: | ||
| return {} | ||
|
|
||
| model = LogisticRegression(fit_intercept=False, C=1e10, tol=1e-6, max_iter=1000) | ||
| model.fit(design, labels, sample_weight=sample_weights) | ||
| ratings = scale * model.coef_[0] + init_rating | ||
| if baseline_model is not None and baseline_model in models.index: | ||
| ratings += baseline_rating - ratings[models[baseline_model]] | ||
| return dict(pd.Series(ratings, index=models.index)) | ||
|
|
||
|
|
||
| def _sample_fingerprint(sampled: pd.DataFrame) -> str: | ||
| rows = [ | ||
| { | ||
| "index": int(index) if isinstance(index, int | np.integer) else str(index), | ||
| "question_id": str(row["question_id"]), | ||
| "model_a": str(row["model_a"]), | ||
| "model_b": str(row["model_b"]), | ||
| } | ||
| for index, row in sampled.iterrows() | ||
| ] | ||
| return hashlib.sha256(json.dumps(rows, sort_keys=True).encode()).hexdigest() | ||
|
|
||
|
|
||
| def select_seeded_random_arena_battles( | ||
| df_battles: pd.DataFrame, | ||
| *, | ||
| n_battles: int, | ||
| seed: int, | ||
| ) -> tuple[pd.DataFrame, dict[str, object]]: | ||
| """Select a shared random battle panel for outside-model ELO estimation.""" | ||
| sampled = df_battles.sample( | ||
| n=min(n_battles, len(df_battles)), random_state=seed, replace=False | ||
| ) | ||
| metadata: dict[str, object] = { | ||
| "sampling_mode": "seeded_random", | ||
| "random_seed": seed, | ||
| "requested_rows": n_battles, | ||
| "sampled_rows": len(sampled), | ||
| "sampled_original_indices": [ | ||
| int(index) if isinstance(index, int | np.integer) else str(index) | ||
| for index in sampled.index | ||
| ], | ||
| "sampled_question_ids": [ | ||
| str(value) for value in sampled["question_id"].tolist() | ||
| ], | ||
| "sample_fingerprint": _sample_fingerprint(sampled), | ||
| } | ||
| return sampled.reset_index(drop=True), metadata | ||
|
|
||
|
|
||
| def sampling_cache_token( | ||
| sampling_metadata: dict[str, object], | ||
| *, | ||
| n_instructions: int | None, | ||
| n_instructions_per_language: int | None, | ||
| ) -> str: | ||
| if sampling_metadata.get("sampling_mode") == "seeded_random": | ||
| return ( | ||
| "seeded-random_" | ||
| f"{sampling_metadata['requested_rows']}_" | ||
| f"seed-{sampling_metadata['random_seed']}_" | ||
| f"{str(sampling_metadata['sample_fingerprint'])[:12]}" | ||
| ) | ||
| return f"head_{n_instructions}_{n_instructions_per_language}" | ||
|
|
||
|
|
||
| def prefs_to_battle_results( | ||
| prefs, | ||
| our_model_is_position_a, | ||
| opponent_models, | ||
| model_name: str, | ||
| *, | ||
| judge_model: str | None = None, | ||
| question_ids=None, | ||
| ) -> pd.DataFrame: | ||
| """Map position-oriented preferences into model-name-level battle rows.""" | ||
| records = [] | ||
| for pref, is_pos_a, opponent in zip( | ||
| prefs, our_model_is_position_a, opponent_models, strict=True | ||
| ): | ||
| if _is_nan_pref(pref) or pref == 0.5: | ||
| winner = "tie" | ||
| elif pref < 0.5: | ||
| winner = "model_a" | ||
| else: | ||
| winner = "model_b" | ||
|
|
||
| if is_pos_a: | ||
| record = { | ||
| "model_a": model_name, | ||
| "model_b": opponent, | ||
| "winner": winner, | ||
| "pref": pref, | ||
| } | ||
| else: | ||
| record = { | ||
| "model_a": opponent, | ||
| "model_b": model_name, | ||
| "winner": winner, | ||
| "pref": None if _is_nan_pref(pref) else pref, | ||
| } | ||
| record["pref_hard"] = winner_to_pref(winner) | ||
| records.append(record) | ||
|
|
||
| frame = pd.DataFrame(records) | ||
| frame["source"] = "llm-judge" | ||
| frame["judge_model"] = judge_model | ||
| if question_ids is not None: | ||
| frame["question_id"] = question_ids | ||
| return frame | ||
|
|
||
|
|
||
| def arena_anchor_battles(df_arena_all: pd.DataFrame) -> pd.DataFrame: | ||
| """Build deterministic human-anchor battles from a loaded arena frame.""" | ||
| frame = df_arena_all.loc[:, ["model_a", "model_b", "winner"]].copy() | ||
| counts = pd.concat([frame["model_a"], frame["model_b"]]).value_counts() | ||
| well_represented = set(counts[counts >= 500].index) | ||
| frame = frame[ | ||
| frame["model_a"].isin(well_represented) | ||
| & frame["model_b"].isin(well_represented) | ||
| ] | ||
| frame["pref"] = frame["winner"].map(winner_to_pref) | ||
| frame["pref_hard"] = frame["pref"] | ||
| frame["source"] = "human" | ||
| return frame |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.