Skip to content

Multi-concept Gram/cosine dynamics across OLMo post-training - #3

Open
xzAscC wants to merge 6 commits into
masterfrom
feat/multi-concept-gram-cosine
Open

Multi-concept Gram/cosine dynamics across OLMo post-training#3
xzAscC wants to merge 6 commits into
masterfrom
feat/multi-concept-gram-cosine

Conversation

@xzAscC

@xzAscC xzAscC commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • Stream-download all concept sources into datasets/*.json (HumanEval-X 5-lang, MiniF2F, BeyondX, MATH-500 CoT→direct, Belebele 5-lang, WinoGender, SST-2, LLM-LAT) with pinned shared item IDs.
  • Expand DiM loaders to the full 46 slide-spec concepts (20 code + 3 math + 20 IF + 3 general) with A→B polarity (+B − A).
  • Align layer schedule to 10%–90% ([3,6,9,11,14,17,20,22,25,28]), default 6 trajectories (Think-SFT + 5 RL-Zero), chat-template wrapping, and batched activation extraction.
  • Compute within-concept checkpoint cosine stability and between-concept Gram matrices; add heatmap visualization CLI.

Pipeline

uv run python experiments/download_datasets.py
experiments/run_concept_dynamics.sh full   # → results/concept_dynamics_multi
uv run python experiments/plot_concept_dynamics.py --input results/concept_dynamics_multi

Test plan

  • uv run pytest tests/test_contrastive_datasets.py tests/test_concept_dynamics.py tests/test_experiment_config.py
  • All 46 concepts load 50/50 samples from materialized JSONs
  • Quick smoke: 1 model × 1 ckpt × 2 concepts completes and writes Gram
  • Full extraction started locally (first ckpt ~4 min for 46 concepts × 10 layers); resume-safe under results/concept_dynamics_multi

Summary by cubic

Adds a multi-concept concept-dynamics pipeline across OLMo post-training with 46 paired concepts, 6 trajectories, slide-layer sampling, and Gram/stability heatmaps. Datasets are streamed and materialized to JSON for deterministic, offline runs.

  • New Features

    • Stream-download 9 sources to datasets/*.json with pinned shared IDs; offline loaders read JSONs.
    • Expand contrastive loaders to 46 concepts across code, math, instruction-following, and general (gender/sentiment/refusal).
    • Align layer sampling to the 10–90% slide formula; default 10 layers.
    • Default to 6 trajectories (Think-SFT + 5 RL-Zero); add batched extraction and optional chat-template wrapping.
    • Compute between-concept Gram matrices and within-concept checkpoint cosine stability; add CLI for heatmaps and summaries.
    • New scripts: experiments/download_datasets.py, experiments/run_concept_dynamics.py/.sh, experiments/plot_concept_dynamics.py; docs updated.
  • Migration

    • Run: uv run python experiments/download_datasets.py (once) to create datasets/*.json.
    • Run the experiment: experiments/run_concept_dynamics.sh full (or quick); outputs under results/concept_dynamics_multi.
    • Concept keys are now namespaced (e.g., code_python_vs_cpp, if_eng_vs_fra, gender_she_vs_he); legacy aliases remain supported.

Written for commit 7571332. Summary will update on new commits.

Review in cubic

xzAscC and others added 6 commits July 22, 2026 02:09
Download HumanEval-X, MiniF2F, BeyondX, MATH-500, Belebele, WinoGender,
SST-2, and LLM-LAT one source at a time and pin shared item IDs.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Cover 20 HumanEval-X pairs, 20 Belebele pairs, MiniF2F, BeyondX,
MATH-500 CoT-to-direct, WinoGender she-to-he, SST-2, and LLM-LAT.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Use the slide layer schedule, optional chat-template wrapping, batched
forward passes, and quick-mode checkpoint caps.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Default to six OLMo trajectories, the full 46-concept catalogue, and
results/concept_dynamics_multi output paths.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Plot per-checkpoint entanglement matrices and within-concept checkpoint
stability, plus a summary CLI entrypoint.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Describe dataset download, DiM extraction, cosine/Gram analysis, and
plotting commands for the weekly multi-concept setup.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 issues found across 18 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/dataset_store.py">

<violation number="1" location="src/dataset_store.py:109">
P2: Concurrent dataset downloads writing the same JSON can fail or lose a completed write because every call shares `<path>.tmp`. Use a unique temporary file per invocation before `os.replace` so the atomic-write helper is safe for parallel callers.</violation>
</file>

<file name="experiments/run_concept_dynamics.py">

<violation number="1" location="experiments/run_concept_dynamics.py:345">
P1: The reported HumanEval-X preflight can pass while the Python/C++ programs actually extracted were never validated, because validation uses the first task IDs but extraction uses pinned sampled IDs. Validate the exact pinned subset and its hashes (or make extraction select the same subset) before calling this preflight strict.

(Based on your team's feedback about subset-only preflight validation.) [FEEDBACK_USED]</violation>
</file>

<file name="experiments/plot_concept_dynamics.py">

<violation number="1" location="experiments/plot_concept_dynamics.py:71">
P3: These three unused private helpers are dead code and suggest validation that the CLI does not perform. Remove them until a caller needs them.</violation>

<violation number="2" location="experiments/plot_concept_dynamics.py:201">
P2: An unknown `--concepts` value silently renders every Gram concept instead of the requested subset. Validate requested names or make `_filter_concepts` reject an empty match so mistyped filters do not produce misleading plots.</violation>

<violation number="3" location="experiments/plot_concept_dynamics.py:231">
P3: Supplying both `--concept` and `--checkpoint` silently produces a stability plot and ignores the checkpoint selector. Reject mutually incompatible selector combinations so an accidental extra flag cannot generate the wrong visualization.</violation>
</file>

<file name="src/contrastive_datasets.py">

<violation number="1" location="src/contrastive_datasets.py:103">
P3: A misspelled duplicate seed constant was introduced (`SAMPlE_SEED` next to `SAMPLE_SEED`). Keeping two near-identical constants increases drift risk if one is updated later, so collapsing to one correctly named constant would keep sampling behavior unambiguous.</violation>

<violation number="2" location="src/contrastive_datasets.py:950">
P3: An unused private helper (`_get_loader`) was added but never called. Keeping dead dispatch helpers makes the loader path harder to audit and easier to desynchronize from the actual `_LOADERS` usage.</violation>
</file>

<file name="src/visualization.py">

<violation number="1" location="src/visualization.py:1229">
P2: Summary plots silently omit requested concepts after the first three. Limit to three only for the default selection so `--concepts` renders every supplied concept.</violation>
</file>

<file name="src/concept_dynamics.py">

<violation number="1" location="src/concept_dynamics.py:186">
P2: Batched extraction can select the wrong token position for left-padded tokenizers, which skews concept vectors for those models. This comes from using `attention_mask.sum(dim=1)-1`, which assumes right padding; deriving the last non-pad index from the right edge avoids padding-side dependence.</violation>
</file>

<file name="experiments/download_datasets.py">

<violation number="1" location="experiments/download_datasets.py:668">
P2: Dataset download failures are reported but still produce a successful process status, so shell/CI pipelines proceed to extraction with missing or stale datasets. Return nonzero after all downloaders have been attempted when `failures` is nonempty.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

f"{args.humaneval_report_path} (n_required={n_samples})"
strict_code = _preflight_concepts_needing_strict_check(concepts)
if strict_code:
_run_strict_humaneval_preflight(args.humaneval_report_path, n_samples)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The reported HumanEval-X preflight can pass while the Python/C++ programs actually extracted were never validated, because validation uses the first task IDs but extraction uses pinned sampled IDs. Validate the exact pinned subset and its hashes (or make extraction select the same subset) before calling this preflight strict.

(Based on your team's feedback about subset-only preflight validation.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At experiments/run_concept_dynamics.py, line 345:

<comment>The reported HumanEval-X preflight can pass while the Python/C++ programs actually extracted were never validated, because validation uses the first task IDs but extraction uses pinned sampled IDs. Validate the exact pinned subset and its hashes (or make extraction select the same subset) before calling this preflight strict.

(Based on your team's feedback about subset-only preflight validation.) </comment>

<file context>
@@ -250,30 +340,23 @@ def main(argv: list[str] | None = None):
-            f"{args.humaneval_report_path} (n_required={n_samples})"
+    strict_code = _preflight_concepts_needing_strict_check(concepts)
+    if strict_code:
+        _run_strict_humaneval_preflight(args.humaneval_report_path, n_samples)
+
+    relaxed_code = _preflight_concepts_needing_warning(concepts)
</file context>

Comment thread src/dataset_store.py
"""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
tmp = p.with_suffix(p.suffix + ".tmp")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Concurrent dataset downloads writing the same JSON can fail or lose a completed write because every call shares <path>.tmp. Use a unique temporary file per invocation before os.replace so the atomic-write helper is safe for parallel callers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/dataset_store.py, line 109:

<comment>Concurrent dataset downloads writing the same JSON can fail or lose a completed write because every call shares `<path>.tmp`. Use a unique temporary file per invocation before `os.replace` so the atomic-write helper is safe for parallel callers.</comment>

<file context>
@@ -0,0 +1,234 @@
+    """
+    p = Path(path)
+    p.parent.mkdir(parents=True, exist_ok=True)
+    tmp = p.with_suffix(p.suffix + ".tmp")
+    with tmp.open("w", encoding="utf-8") as handle:
+        json.dump(obj, handle, ensure_ascii=False, indent=2, sort_keys=False)
</file context>

plot_concept_dynamics_summary(
args.input,
models=models,
concepts=concepts,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: An unknown --concepts value silently renders every Gram concept instead of the requested subset. Validate requested names or make _filter_concepts reject an empty match so mistyped filters do not produce misleading plots.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At experiments/plot_concept_dynamics.py, line 201:

<comment>An unknown `--concepts` value silently renders every Gram concept instead of the requested subset. Validate requested names or make `_filter_concepts` reject an empty match so mistyped filters do not produce misleading plots.</comment>

<file context>
@@ -0,0 +1,239 @@
+        plot_concept_dynamics_summary(
+            args.input,
+            models=models,
+            concepts=concepts,
+        )
+    except FileNotFoundError as exc:
</file context>

Comment thread src/visualization.py
summary_concepts.extend(c for c in model_block if c not in summary_concepts)
if concepts:
summary_concepts = [c for c in summary_concepts if c in set(concepts)]
summary_concepts = summary_concepts[:3]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Summary plots silently omit requested concepts after the first three. Limit to three only for the default selection so --concepts renders every supplied concept.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/visualization.py, line 1229:

<comment>Summary plots silently omit requested concepts after the first three. Limit to three only for the default selection so `--concepts` renders every supplied concept.</comment>

<file context>
@@ -813,6 +939,319 @@ def generate_all_plots(results_dir: Optional[str] = None):
+        summary_concepts.extend(c for c in model_block if c not in summary_concepts)
+    if concepts:
+        summary_concepts = [c for c in summary_concepts if c in set(concepts)]
+    summary_concepts = summary_concepts[:3]
+
+    for model in target_models:
</file context>
Suggested change
summary_concepts = summary_concepts[:3]
if concepts is None:
summary_concepts = summary_concepts[:3]

Comment thread src/concept_dynamics.py
dtype=torch.long,
)
else:
last_indices = attention_mask.to(dtype=torch.long).sum(dim=1) - 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Batched extraction can select the wrong token position for left-padded tokenizers, which skews concept vectors for those models. This comes from using attention_mask.sum(dim=1)-1, which assumes right padding; deriving the last non-pad index from the right edge avoids padding-side dependence.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/concept_dynamics.py, line 186:

<comment>Batched extraction can select the wrong token position for left-padded tokenizers, which skews concept vectors for those models. This comes from using `attention_mask.sum(dim=1)-1`, which assumes right padding; deriving the last non-pad index from the right edge avoids padding-side dependence.</comment>

<file context>
@@ -132,45 +144,85 @@ def extract_layer_activations(
+                dtype=torch.long,
+            )
+        else:
+            last_indices = attention_mask.to(dtype=torch.long).sum(dim=1) - 1
 
+        batch_idx = torch.arange(
</file context>
Suggested change
last_indices = attention_mask.to(dtype=torch.long).sum(dim=1) - 1
mask = attention_mask.to(dtype=torch.long)
last_indices = mask.shape[1] - 1 - mask.flip(dims=[1]).argmax(dim=1)

else:
_log("All requested downloads completed.")
_log("=" * 60)
return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Dataset download failures are reported but still produce a successful process status, so shell/CI pipelines proceed to extraction with missing or stale datasets. Return nonzero after all downloaders have been attempted when failures is nonempty.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At experiments/download_datasets.py, line 668:

<comment>Dataset download failures are reported but still produce a successful process status, so shell/CI pipelines proceed to extraction with missing or stale datasets. Return nonzero after all downloaders have been attempted when `failures` is nonempty.</comment>

<file context>
@@ -0,0 +1,672 @@
+    else:
+        _log("All requested downloads completed.")
+    _log("=" * 60)
+    return 0
+
+
</file context>

return sorted(seen)


def _list_checkpoints(gram: dict, model: str) -> list[str]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: These three unused private helpers are dead code and suggest validation that the CLI does not perform. Remove them until a caller needs them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At experiments/plot_concept_dynamics.py, line 71:

<comment>These three unused private helpers are dead code and suggest validation that the CLI does not perform. Remove them until a caller needs them.</comment>

<file context>
@@ -0,0 +1,239 @@
+    return sorted(seen)
+
+
+def _list_checkpoints(gram: dict, model: str) -> list[str]:
+    block = gram.get(model, {}) if isinstance(gram, dict) else {}
+    return sorted(k for k, v in block.items() if isinstance(v, dict) and v)
</file context>

)
return 1

if args.concept:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Supplying both --concept and --checkpoint silently produces a stability plot and ignores the checkpoint selector. Reject mutually incompatible selector combinations so an accidental extra flag cannot generate the wrong visualization.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At experiments/plot_concept_dynamics.py, line 231:

<comment>Supplying both `--concept` and `--checkpoint` silently produces a stability plot and ignores the checkpoint selector. Reject mutually incompatible selector combinations so an accidental extra flag cannot generate the wrong visualization.</comment>

<file context>
@@ -0,0 +1,239 @@
+        )
+        return 1
+
+    if args.concept:
+        return _plot_single_stability(args, stab_path)
+    if args.checkpoint:
</file context>

_LOADERS[_concept_key] = _if_loader_for(_concept_key)


def _get_loader(concept: str) -> Callable[[int], list[tuple[str, str]]]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: An unused private helper (_get_loader) was added but never called. Keeping dead dispatch helpers makes the loader path harder to audit and easier to desynchronize from the actual _LOADERS usage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contrastive_datasets.py, line 950:

<comment>An unused private helper (`_get_loader`) was added but never called. Keeping dead dispatch helpers makes the loader path harder to audit and easier to desynchronize from the actual `_LOADERS` usage.</comment>

<file context>
@@ -471,42 +841,170 @@ def load_winogender_pairs(n_samples: int = 50) -> list[tuple[str, str]]:
+    _LOADERS[_concept_key] = _if_loader_for(_concept_key)
+
+
+def _get_loader(concept: str) -> Callable[[int], list[tuple[str, str]]]:
+    resolved = _resolve_concept(concept)
+    return _LOADERS[resolved]
</file context>

Comment on lines +103 to +104
SAMPlE_SEED: int = 42 # local convenience alias (kept private)
SAMPLE_SEED: int = 42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: A misspelled duplicate seed constant was introduced (SAMPlE_SEED next to SAMPLE_SEED). Keeping two near-identical constants increases drift risk if one is updated later, so collapsing to one correctly named constant would keep sampling behavior unambiguous.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contrastive_datasets.py, line 103:

<comment>A misspelled duplicate seed constant was introduced (`SAMPlE_SEED` next to `SAMPLE_SEED`). Keeping two near-identical constants increases drift risk if one is updated later, so collapsing to one correctly named constant would keep sampling behavior unambiguous.</comment>

<file context>
@@ -52,82 +55,234 @@
+
+DEFAULT_N_SAMPLES: int = 50
+N_SHARED_ITEMS: int = 50
+SAMPlE_SEED: int = 42  # local convenience alias (kept private)
+SAMPLE_SEED: int = 42
+
</file context>
Suggested change
SAMPlE_SEED: int = 42 # local convenience alias (kept private)
SAMPLE_SEED: int = 42
SAMPLE_SEED: int = 42

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant