155 lines
5.4 KiB
Python
155 lines
5.4 KiB
Python
"""Generation-parameter profiles: named, per-bench gen_kwargs presets.
|
|
|
|
Problem being solved: ``DatasetSpec.gen_config`` is baked into the dataset
|
|
plugin at authoring time (the Qwen3 era defaults), but different models /
|
|
protocols need different parameters (dp4 wants t0/32768 everywhere, Qwen3
|
|
wanted mixed values). Without this layer every runner script re-declares
|
|
its own ``GEN = {...}`` dict and hand-merges overrides -- we did that for
|
|
days across seven stage scripts.
|
|
|
|
Resolution order (later wins):
|
|
1. DatasetSpec.gen_config (plugin's built-in default)
|
|
2. profile['default'] (protocol-wide baseline)
|
|
3. profile['<bench>'] (per-bench override)
|
|
4. explicit run_eval(gen_kwargs=...) (one-off)
|
|
|
|
Usage:
|
|
# register
|
|
@register_gen_profile('dp4-nothink')
|
|
def dp4():
|
|
return {'default': {'temperature': 0.0, 'max_tokens': 32768},
|
|
'simple_qa': {'max_tokens': 1024}}
|
|
|
|
# consume
|
|
run_eval(ds, spec, gen_profile='dp4-nothink')
|
|
evalharness eval run hle --model ... --profile dp4-nothink
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import Any, Callable, Dict, Optional
|
|
|
|
PROFILES: Dict[str, Callable[[], Dict[str, Dict[str, Any]]]] = {}
|
|
|
|
|
|
def register_gen_profile(name: str):
|
|
def decorator(fn):
|
|
PROFILES[name] = fn
|
|
return fn
|
|
|
|
return decorator
|
|
|
|
|
|
def get_profile(name: str) -> Optional[Dict[str, Dict[str, Any]]]:
|
|
"""Resolve a profile by name.
|
|
|
|
Lookup order:
|
|
1. @register_gen_profile registry (code-defined, built-ins live here)
|
|
2. YAML file, selected by (in order):
|
|
a. $EVALHARNESS_GEN_PROFILES env var (explicit path)
|
|
b. ./gen_profiles.yaml (next to the invocation / repo root)
|
|
c. ~/.config/evalharness/gen_profiles.yaml
|
|
A YAML file may define MANY profiles; the file's profiles are also merged
|
|
into list_profiles() so CLI completion/Errors can see them.
|
|
"""
|
|
fn = PROFILES.get(name)
|
|
if fn is not None:
|
|
return fn()
|
|
loaded = _load_yaml_profiles()
|
|
if name in loaded:
|
|
return loaded[name]
|
|
return None
|
|
|
|
|
|
_YAML_CACHE: Optional[Dict[str, Dict[str, Dict[str, Any]]]] = None
|
|
|
|
|
|
def _candidate_yaml_paths():
|
|
import os
|
|
|
|
paths = []
|
|
env = os.environ.get('EVALHARNESS_GEN_PROFILES')
|
|
if env:
|
|
paths.append(Path(env))
|
|
paths.append(Path('gen_profiles.yaml'))
|
|
paths.append(Path.home() / '.config' / 'evalharness' / 'gen_profiles.yaml')
|
|
return paths
|
|
|
|
|
|
def _load_yaml_profiles() -> Dict[str, Dict[str, Dict[str, Any]]]:
|
|
"""Read every profile from the first YAML that exists; empty if none."""
|
|
global _YAML_CACHE
|
|
if _YAML_CACHE is not None:
|
|
return _YAML_CACHE
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
_YAML_CACHE = {}
|
|
return _YAML_CACHE
|
|
for path in _candidate_yaml_paths():
|
|
try:
|
|
if path and path.exists():
|
|
data = yaml.safe_load(path.read_text(encoding='utf-8')) or {}
|
|
# accept both flat (file IS one profile: has 'default')
|
|
# and namespaced (top-level keys are profile names)
|
|
if 'default' in data or 'bench' in {k.split('.')[0] for k in data
|
|
if isinstance(data.get(k), dict)}:
|
|
data = {'default': data} if 'default' in data else data
|
|
_YAML_CACHE = {k: v for k, v in data.items() if isinstance(v, dict)}
|
|
return _YAML_CACHE
|
|
except Exception:
|
|
continue
|
|
_YAML_CACHE = {}
|
|
return _YAML_CACHE
|
|
|
|
|
|
def list_profiles():
|
|
return sorted(set(PROFILES) | set(_load_yaml_profiles()))
|
|
|
|
|
|
def merge_gen_kwargs(bench: str, spec, gen_kwargs: Optional[Dict[str, Any]],
|
|
profile_name: str = '') -> Dict[str, Any]:
|
|
"""Layered merge for one bench (later layers win)."""
|
|
out: Dict[str, Any] = {}
|
|
out.update(getattr(spec, 'gen_config', None) or {})
|
|
if profile_name:
|
|
prof = get_profile(profile_name)
|
|
if prof is None:
|
|
raise KeyError(f'unknown gen profile {profile_name!r}; '
|
|
f'available: {", ".join(list_profiles())}')
|
|
out.update(prof.get('default') or {})
|
|
out.update(prof.get(bench) or {})
|
|
out.update(gen_kwargs or {})
|
|
return out
|
|
|
|
|
|
# ------------------------------ built-ins ------------------------------
|
|
|
|
@register_gen_profile('dp4-nothink')
|
|
def _dp4_nothink():
|
|
"""DeepSeek-V4-Flash nothinking protocol (es DP4-flash-int8-nothinking):
|
|
t0 / 32768 / top_p 1.0 everywhere; judged benches can be trimmed."""
|
|
return {'default': {'temperature': 0.0, 'max_tokens': 32768, 'top_p': 1.0}}
|
|
|
|
|
|
@register_gen_profile('qwen3-es-parity')
|
|
def _qwen3_parity():
|
|
"""Qwen3-8B evalscope-parity protocol (the values used for the 28-bench
|
|
alignment): CoT benches get 32k room, short-answer benches stay small."""
|
|
return {
|
|
'default': {'temperature': 0.0, 'max_tokens': 32768},
|
|
'simple_qa': {'max_tokens': 1024},
|
|
'hle': {'max_tokens': 8192},
|
|
'gpqa_diamond': {'temperature': 1.0, 'max_tokens': 8192},
|
|
'aime24': {'temperature': 1.0},
|
|
'aime25': {'temperature': 1.0},
|
|
'aime26': {'temperature': 1.0},
|
|
'hmmt26': {'temperature': 1.0},
|
|
'imo_answerbench': {'temperature': 1.0},
|
|
}
|
|
|
|
|
|
@register_gen_profile('t1-short')
|
|
def _t1_short():
|
|
"""temp=1 sampling for small repeated benches (variance measurement)."""
|
|
return {'default': {'temperature': 1.0, 'max_tokens': 32768, 'top_p': 1.0}}
|