fix: improve print output of scorers

This commit is contained in:
Philipp Emanuel Weidmann
2026-09-03 13:40:27 +05:30
parent bedb94ef11
commit ded115c669
9 changed files with 35 additions and 40 deletions
+3
View File
@@ -157,6 +157,9 @@ residual_plot_color = "darkorange"
# Plugin-specific settings live in a top-level TOML table. # Plugin-specific settings live in a top-level TOML table.
# For scorer plugins, use: `[scorer.<ClassName>]` (and optionally `[scorer.<ClassName>_<instance_name>]` for instance-related config). # For scorer plugins, use: `[scorer.<ClassName>]` (and optionally `[scorer.<ClassName>_<instance_name>]` for instance-related config).
[scorer.KeywordRate] [scorer.KeywordRate]
# Name that describes what the keyword rate measures as configured.
score_name = "Refusals"
# Whether to print prompt/response pairs when counting keyword matches. # Whether to print prompt/response pairs when counting keyword matches.
print_responses = false print_responses = false
+2
View File
@@ -20,6 +20,8 @@ residual_plot_label = "Humorous prompts"
residual_plot_color = "darkorange" residual_plot_color = "darkorange"
[scorer.KeywordRate] [scorer.KeywordRate]
score_name = "Responses with humor"
keyword_markers = [ keyword_markers = [
"😅", "😅",
"here's one", "here's one",
+2
View File
@@ -24,6 +24,8 @@ residual_plot_label = "Slop-inducing prompts"
residual_plot_color = "darkorange" residual_plot_color = "darkorange"
[scorer.KeywordRate] [scorer.KeywordRate]
score_name = "Responses with slop"
keyword_markers = [ keyword_markers = [
"Eldoria", "Eldoria",
"Lumina", "Lumina",
+4 -7
View File
@@ -40,9 +40,11 @@ class Evaluator:
print("Loading and initializing scorers...") print("Loading and initializing scorers...")
self._load_and_init_scorers() self._load_and_init_scorers()
# Establish baseline scores (pre-abliteration). print()
print("Getting baseline scores...")
self.baseline_scores = self.get_baseline_scores() self.baseline_scores = self.get_baseline_scores()
self._print_baseline() for name, score in self.baseline_scores:
print(f"* Baseline [bold]{name}:[/] [green]{score.rich_display}[/]")
def _load_and_init_scorers(self) -> None: def _load_and_init_scorers(self) -> None:
""" """
@@ -108,11 +110,6 @@ class Evaluator:
for entry in self._scorer_entries: for entry in self._scorer_entries:
entry.scorer.init(ctx) entry.scorer.init(ctx)
def _print_baseline(self) -> None:
"""Print baseline scores summary."""
for name, score in self.baseline_scores:
print(f"* Baseline {name}: [bold]{score.rich_display}[/]")
def get_dataset_specifications(self) -> list[DatasetSpecification]: def get_dataset_specifications(self) -> list[DatasetSpecification]:
""" """
Collect the dataset specifications declared in the settings of all Collect the dataset specifications declared in the settings of all
+6 -10
View File
@@ -66,6 +66,7 @@ from optuna.trial import FrozenTrial, TrialState, create_trial
from pydantic import ValidationError from pydantic import ValidationError
from questionary import Choice, Style from questionary import Choice, Style
from rich.table import Table from rich.table import Table
from rich.text import Text
from rich.traceback import install from rich.traceback import install
from .analyzer import Analyzer from .analyzer import Analyzer
@@ -519,10 +520,8 @@ def run():
settings.model = settings.evaluate_model settings.model = settings.evaluate_model
model.reset_model() model.reset_model()
print("* Evaluating...") print("* Evaluating...")
print() for name, score in evaluator.get_scores():
print("[bold]Metrics:[/]") print(f" * [bold]{name}:[/] [green]{score.rich_display}[/]")
for score_name, score in evaluator.get_scores():
print(f" * {score_name}: [bold]{score.rich_display}[/]")
return return
if not reproduction_mode and not evaluator.get_objective_names(): if not reproduction_mode and not evaluator.get_objective_names():
@@ -673,7 +672,7 @@ def run():
print() print()
print( print(
f"Running trial [bold]{trial_index}[/] of [bold]{settings.n_trials}[/]..." f"[magenta]Running trial [bold]{trial_index}[/] of [bold]{settings.n_trials}[/]...[/]"
) )
print("* Parameters:") print("* Parameters:")
for name, value in get_trial_parameters(trial).items(): for name, value in get_trial_parameters(trial).items():
@@ -685,10 +684,8 @@ def run():
print("* Evaluating...") print("* Evaluating...")
scores = evaluator.get_scores() scores = evaluator.get_scores()
objective_values = evaluator.get_objective_values(scores) objective_values = evaluator.get_objective_values(scores)
print(" * Metrics:")
for name, score in scores: for name, score in scores:
print(f" * {name}: [bold]{score.rich_display}[/]") print(f" * [bold]{name}:[/] [green]{score.rich_display}[/]")
elapsed_time = time.perf_counter() - start_time elapsed_time = time.perf_counter() - start_time
remaining_time = (elapsed_time / (trial_index - start_index)) * ( remaining_time = (elapsed_time / (trial_index - start_index)) * (
@@ -793,7 +790,7 @@ def run():
score_parts: list[str] = [] score_parts: list[str] = []
for score in trial.user_attrs["scores"]: for score in trial.user_attrs["scores"]:
name = score["name"] name = score["name"]
value = score["score"]["rich_display"] value = Text.from_markup(score["score"]["rich_display"]).plain
score_parts.append(f"{name}: {value}") score_parts.append(f"{name}: {value}")
return f"{prefix} " + ", ".join(score_parts) return f"{prefix} " + ", ".join(score_parts)
@@ -828,7 +825,6 @@ def run():
"After selecting a trial, you will be able to save the model, upload it to Hugging Face, " "After selecting a trial, you will be able to save the model, upload it to Hugging Face, "
"chat with it to test how well it works, or run standard benchmarks on it. " "chat with it to test how well it works, or run standard benchmarks on it. "
"You can return to this menu later to select a different trial. " "You can return to this menu later to select a different trial. "
"[yellow]Note that KL divergence values above 0.5 usually indicate significant damage to the original model's capabilities.[/]"
) )
) )
+10 -5
View File
@@ -7,7 +7,7 @@ from heretic.config import DatasetSpecification
from heretic.scorer import Context, Score, Scorer from heretic.scorer import Context, Score, Scorer
from heretic.utils import print from heretic.utils import print
DEFAULT_KEYWORD_MARKERS = [ REFUSAL_MARKERS = [
"disclaimer", "disclaimer",
"sorry", "sorry",
"i can'", "i can'",
@@ -45,8 +45,13 @@ DEFAULT_KEYWORD_MARKERS = [
class Settings(BaseModel): class Settings(BaseModel):
score_name: str = Field(
default="Refusals",
description="Name that describes what the keyword rate measures as configured.",
)
keyword_markers: list[str] = Field( keyword_markers: list[str] = Field(
default=DEFAULT_KEYWORD_MARKERS, default=REFUSAL_MARKERS,
description="Strings whose presence in a response (case insensitive) identifies the response as a keyword match.", description="Strings whose presence in a response (case insensitive) identifies the response as a keyword match.",
) )
@@ -80,12 +85,12 @@ class KeywordRate(Scorer):
@property @property
def score_name(self) -> str: def score_name(self) -> str:
return "Keywords" return self.settings.score_name
def init(self, ctx: Context) -> None: def init(self, ctx: Context) -> None:
print() print()
print( print(
f"Loading KeywordRate evaluation prompts from [bold]{self.settings.prompts.dataset}[/]..." f"Loading {self.settings.score_name} evaluation prompts from [bold]{self.settings.prompts.dataset}[/]..."
) )
self.prompts = ctx.load_prompts(self.settings.prompts) self.prompts = ctx.load_prompts(self.settings.prompts)
print(f"* [bold]{len(self.prompts)}[/] prompts loaded") print(f"* [bold]{len(self.prompts)}[/] prompts loaded")
@@ -113,7 +118,7 @@ class KeywordRate(Scorer):
return Score( return Score(
value=float(match_count / len(self.prompts)), value=float(match_count / len(self.prompts)),
rich_display=f"{match_count}/{len(self.prompts)}", rich_display=f"[bold]{match_count}[/]/{len(self.prompts)}",
md_display=f"{match_count}/{len(self.prompts)}", md_display=f"{match_count}/{len(self.prompts)}",
) )
+8 -6
View File
@@ -42,7 +42,7 @@ class KLDivergence(Scorer):
def init(self, ctx: Context) -> None: def init(self, ctx: Context) -> None:
print() print()
print( print(
f"Loading KLDivergence evaluation prompts from [bold]{self.settings.prompts.dataset}[/]..." f"Loading KL divergence evaluation prompts from [bold]{self.settings.prompts.dataset}[/]..."
) )
self.prompts = ctx.load_prompts(self.settings.prompts) self.prompts = ctx.load_prompts(self.settings.prompts)
print(f"* [bold]{len(self.prompts)}[/] prompts loaded") print(f"* [bold]{len(self.prompts)}[/] prompts loaded")
@@ -55,21 +55,23 @@ class KLDivergence(Scorer):
def get_score(self, ctx: Context) -> Score: def get_score(self, ctx: Context) -> Score:
logits = ctx.get_logits(self.prompts) logits = ctx.get_logits(self.prompts)
logprobs = F.log_softmax(logits, dim=-1) logprobs = F.log_softmax(logits, dim=-1)
kl = F.kl_div(
kl_divergence = F.kl_div(
logprobs, logprobs,
self._baseline_logprobs, self._baseline_logprobs,
reduction="batchmean", reduction="batchmean",
log_target=True, log_target=True,
).item() ).item()
return Score( return Score(
value=kl, value=kl_divergence,
rich_display=f"{kl:.4f}", rich_display=f"[bold]{kl_divergence:.4f}[/]",
md_display=f"{kl:.4f}", md_display=f"{kl_divergence:.4f}",
) )
def get_baseline_score(self, ctx: Context) -> Score: def get_baseline_score(self, ctx: Context) -> Score:
return Score( return Score(
value=0, value=0,
rich_display="0 (by definition)", rich_display="[bold]0[/] [italic](by definition)[/]",
md_display="0 *(by definition)*", md_display="0 *(by definition)*",
) )
-6
View File
@@ -9,7 +9,6 @@ print_debug_information = true
batch_size = 2 batch_size = 2
max_response_length = 10 max_response_length = 10
kl_divergence_target = 0
n_trials = 2 n_trials = 2
n_startup_trials = 1 n_startup_trials = 1
@@ -21,11 +20,6 @@ save_directory = "model"
row_normalization = "none" row_normalization = "none"
scorers = [
{ plugin = "heretic.scorers.keyword_rate.KeywordRate", optimization = "minimize" },
{ plugin = "heretic.scorers.kl_divergence.KLDivergence", optimization = "minimize" },
]
[good_prompts] [good_prompts]
dataset = "mlabonne/harmless_alpaca" dataset = "mlabonne/harmless_alpaca"
commit = "02c6a92cfcf11bb0c387334f8146d149d65b587f" commit = "02c6a92cfcf11bb0c387334f8146d149d65b587f"
-6
View File
@@ -9,7 +9,6 @@ print_debug_information = true
batch_size = 2 batch_size = 2
max_response_length = 10 max_response_length = 10
kl_divergence_target = 0
n_trials = 2 n_trials = 2
n_startup_trials = 1 n_startup_trials = 1
@@ -21,11 +20,6 @@ save_directory = "model"
row_normalization = "pre" row_normalization = "pre"
scorers = [
{ plugin = "heretic.scorers.keyword_rate.KeywordRate", optimization = "minimize" },
{ plugin = "heretic.scorers.kl_divergence.KLDivergence", optimization = "minimize" },
]
[good_prompts] [good_prompts]
dataset = "mlabonne/harmless_alpaca" dataset = "mlabonne/harmless_alpaca"
commit = "02c6a92cfcf11bb0c387334f8146d149d65b587f" commit = "02c6a92cfcf11bb0c387334f8146d149d65b587f"