-
Notifications
You must be signed in to change notification settings - Fork 55
Consistent tPD metric names, and core evals on both streams #999
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
bc55bfe
2f737aa
3cc0f57
22b76f2
a684e82
ee84951
9d7e138
f205814
4e264ba
047c239
02b7a67
b330767
552d616
bf37f57
c9259a0
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 |
|---|---|---|
|
|
@@ -333,7 +333,10 @@ def log(self, step: int, record: "LogRecord") -> None: | |
| self._last_committed_step = step | ||
| record = { | ||
| _METRIC_KEYS.get( | ||
| k, f"train/{k}" if k.startswith(("grad_norms/", "loss/", "schedules/")) else k | ||
| k, | ||
| f"train/{k}" | ||
| if k.startswith(("grad_norms/", "loss/", "schedules/", "nontarget_data/")) | ||
|
Collaborator
Author
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. The renamed non-target keys start with — [written by Claude] |
||
| else k, | ||
| ): v | ||
| for k, v in record.items() | ||
| } # keys already starting "train/" or "eval/" pass through verbatim | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,7 @@ | |
| lower_leaky_hard_sigmoid, | ||
| upper_leaky_hard_sigmoid, | ||
| ) | ||
| from param_decomp.core.components import ComponentStacks | ||
| from param_decomp.core.configs import ( | ||
| DenseCITargetSpec, | ||
| IdentityCIErrorConfig, | ||
|
|
@@ -198,6 +199,11 @@ def slow_eval_step( | |
| return filter_jit(slow_eval_step, compiler_options=compiler_options) | ||
|
|
||
|
|
||
| def _raw_sample(chunks: dict[str, list[np.ndarray]], site: str) -> np.ndarray: | ||
|
Collaborator
Author
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. Bug fix, not a refactor. — [written by Claude] |
||
| """A site's kept raw values, or empty when `n_batches_accum` kept none.""" | ||
| return np.concatenate(chunks[site]) if site in chunks else np.empty(0, np.float32) | ||
|
|
||
|
|
||
| def accumulate_site_reductions( | ||
| slow_eval_step: SlowEvalStep, | ||
| model: DecomposedModel, | ||
|
|
@@ -246,8 +252,8 @@ def accumulate_site_reductions( | |
| density_counts=density[site], | ||
| ci_sums=sums[site], | ||
| n_positions=total_positions, | ||
| lower_sample=np.concatenate(lower_chunks[site]), | ||
| preactivations_sample=np.concatenate(preactivations_chunks[site]), | ||
| lower_sample=_raw_sample(lower_chunks, site), | ||
| preactivations_sample=_raw_sample(preactivations_chunks, site), | ||
| density_hist=hist.get(site), | ||
| ) | ||
| for site in density | ||
|
|
@@ -578,6 +584,89 @@ def _plot_ci_matrices(matrices: dict[str, np.ndarray], colormap: str, title_pref | |
| return _render_figure(fig) | ||
|
|
||
|
|
||
| def _component_weight_magnitudes(components: ComponentStacks) -> dict[str, Array]: | ||
| return { | ||
| name: jnp.linalg.norm(sc.V.astype(jnp.float32), axis=0) | ||
| * jnp.linalg.norm(sc.U.astype(jnp.float32), axis=1) | ||
| for name, sc in components.sites_items() | ||
| } | ||
|
|
||
|
|
||
| def weight_magnitudes(components: ComponentStacks) -> dict[str, np.ndarray]: | ||
| """Per-site `‖V_c‖·‖U_c‖` as host `(C,)` vectors. The norms reduce ON DEVICE, so only | ||
| C floats per site cross the boundary — never the V/U matrices themselves.""" | ||
| return { | ||
| name: np.asarray(value) for name, value in _component_weight_magnitudes(components).items() | ||
| } | ||
|
|
||
|
|
||
| def mean_cis(reductions: dict[str, SiteReduction]) -> dict[str, np.ndarray]: | ||
| """Per-site token-weighted mean CI.""" | ||
| assert all(r.n_positions > 0 for r in reductions.values()) | ||
| return {site: r.ci_sums / r.n_positions for site, r in reductions.items()} | ||
|
|
||
|
|
||
| def plot_weight_magnitudes(magnitudes: dict[str, np.ndarray]) -> bytes: | ||
| """Per-site `‖V_c‖·‖U_c‖` in descending magnitude order, log y. x is a component's rank | ||
| within its site, NOT its component id.""" | ||
| n_rows, n_cols = _grid_dims(len(magnitudes)) | ||
| fig = Figure(figsize=(8 * n_cols, 3 * n_rows)) | ||
| axs = fig.subplots(n_rows, n_cols, squeeze=False) | ||
| flat_axes = axs.T.ravel() | ||
| for ax in flat_axes[len(magnitudes) :]: | ||
| ax.set_visible(False) | ||
| for ax, (name, values) in zip(flat_axes, magnitudes.items(), strict=False): | ||
| ax.scatter(range(len(values)), np.sort(values)[::-1], marker="x", s=10) | ||
|
Collaborator
Author
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. Sorted descending, so x is a component's rank within its site, not its component id — you can no longer cross-reference a point against other per-component plots. — [written by Claude] |
||
| ax.set_yscale("log") | ||
| ax.set_xlabel("Component (descending ‖V‖·‖U‖)") | ||
| ax.set_ylabel("‖V‖·‖U‖") | ||
| ax.set_title(name, fontsize=10) | ||
| fig.tight_layout() | ||
| return _render_figure(fig) | ||
|
|
||
|
|
||
| def plot_mean_component_cis_two_streams( | ||
| target_mean_cis: dict[str, np.ndarray], | ||
| nontarget_mean_cis: dict[str, np.ndarray], | ||
| ) -> tuple[bytes, bytes]: | ||
| """Both streams' mean CI on one axis per site, ordered by descending TARGET mean. | ||
|
|
||
| The nontarget series takes the same permutation rather than its own, so a component's | ||
| two series line up vertically.""" | ||
| assert target_mean_cis.keys() == nontarget_mean_cis.keys(), ( | ||
| sorted(target_mean_cis), | ||
| sorted(nontarget_mean_cis), | ||
| ) | ||
| n_rows, n_cols = _grid_dims(len(target_mean_cis)) | ||
| ordered = { | ||
| name: (target[order], nontarget_mean_cis[name][order]) | ||
| for name, target in target_mean_cis.items() | ||
| for order in [np.argsort(target)[::-1]] | ||
| } | ||
| images: list[bytes] = [] | ||
| for log_y in (False, True): | ||
| fig = Figure(figsize=(8 * n_cols, 3 * n_rows)) | ||
| axs = fig.subplots(n_rows, n_cols, squeeze=False) | ||
| flat_axes = axs.T.ravel() | ||
| for ax in flat_axes[len(ordered) :]: | ||
| ax.set_visible(False) | ||
| for ax, (name, (target, nontarget)) in zip(flat_axes, ordered.items(), strict=False): | ||
| x = np.arange(len(target)) | ||
| if log_y: | ||
| ax.set_yscale("log") | ||
| ax.fill_between(x, target, step="mid", color="#1f77b4", label="target") | ||
|
Collaborator
Author
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.
— [written by Claude] |
||
| ax.fill_between( | ||
| x, nontarget, step="mid", color="#d62728", label="non-target", alpha=0.6 | ||
| ) | ||
| ax.set_xlabel("Component (sorted by target mean CI)") | ||
| ax.set_ylabel("mean CI") | ||
| ax.set_title(name, fontsize=10) | ||
| ax.legend(fontsize=7) | ||
| fig.tight_layout() | ||
| images.append(_render_figure(fig)) | ||
| return images[0], images[1] | ||
|
|
||
|
|
||
| def plot_permuted_ci_heatmaps( | ||
| position_ci: dict[str, PositionCI], permutation: dict[str, "Literal['identity', 'dense']"] | ||
| ) -> tuple[bytes, bytes]: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1185,14 +1185,18 @@ def make_targeted_train_step[PreparedT]( | |
| for term in objective.target.recon | ||
| if term.hidden_acts_reconstruction is not None | ||
| }, | ||
| "nontarget/impmin": objective.nontarget.impmin_coeff, | ||
| **{f"nontarget/{term.name}": term.coeff for term in nt_terms}, | ||
| } | ||
| if objective.target.imp.cfg.frequency is not None: | ||
| coeff_schedules[f"{objective.target.imp.name}/frequency"] = ( | ||
| objective.target.imp.cfg.frequency.coeff | ||
| ) | ||
|
|
||
| imp_name = objective.target.imp.name | ||
| nontarget_coeff_schedules: dict[str, LossCoeff] = { | ||
|
Collaborator
Author
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. Split out so the non-target coefficients can take the — [written by Claude] |
||
| imp_name: objective.nontarget.impmin_coeff, | ||
| **{term.name: term.coeff for term in nt_terms}, | ||
| } | ||
|
|
||
| def nontarget_draw_loss( | ||
| model: DecomposedModel[PreparedT], | ||
| prepared_weights: PreparedT, | ||
|
|
@@ -1360,8 +1364,8 @@ def loss_fn( | |
| nt_imp_lp, nt_imp_freq = imp_min_terms(nt_ci.upper, atoms.imp_min, imp_min_param) | ||
| nt_total = nt_imp_coeff * nt_imp_lp + freq_coeff * nt_imp_freq | ||
| nt_aux = { | ||
| f"loss/nontarget/{atoms.imp_loss_key}": nt_imp_lp, | ||
| "loss/nontarget/freq": nt_imp_freq, | ||
| f"nontarget_data/loss/{imp_name}": nt_imp_lp, | ||
| "nontarget_data/loss/FrequencyMinimalityLoss": nt_imp_freq, | ||
| } | ||
| nt_breakdowns = atoms.grid_losses( | ||
| nt_terms, | ||
|
|
@@ -1374,8 +1378,8 @@ def loss_fn( | |
| nt_terms, nt_recon_coeffs, nt_breakdowns, strict=True | ||
| ): | ||
| nt_total = nt_total + coeff * breakdown.total | ||
| nt_aux[f"loss/nontarget/{term.name}"] = breakdown.total | ||
| nt_aux["loss/nontarget/total"] = nt_total | ||
| nt_aux[f"nontarget_data/loss/{term.name}"] = breakdown.total | ||
| nt_aux["nontarget_data/loss/total"] = nt_total | ||
| total_loss = total_loss + nt_total | ||
| reported_total = reported_total + nt_total | ||
| return total_loss, (reported_total, imp_lp, imp_freq, term_breakdowns, nt_aux) | ||
|
|
@@ -1446,6 +1450,12 @@ def loss_fn( | |
| | nt_aux | ||
| | wd_metrics | ||
| | _scheduled_coeff_metrics(step_f32, atoms.total_steps, coeff_schedules) | ||
| | { | ||
| f"nontarget_data/{key}": value | ||
| for key, value in _scheduled_coeff_metrics( | ||
| step_f32, atoms.total_steps, nontarget_coeff_schedules | ||
| ).items() | ||
| } | ||
| ) | ||
| return new_state, metrics | ||
|
|
||
|
|
||
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.
New plot.
slow: ClassVar[bool]is required of every eval metric —assert_every_metric_declares_its_tierenforces that each class declares its own tier rather than inheriting one.— [written by Claude]