neuralworm commited on
Commit
0d29ab8
·
1 Parent(s): 68cb555

add repo.txt

Browse files
Files changed (1) hide show
  1. repo.txt +679 -729
repo.txt CHANGED
@@ -23,6 +23,7 @@ Directory/File Tree Begins -->
23
  │ ├── orchestrator_seismograph.py
24
  │ ├── prompts.py
25
  │ ├── resonance_seismograph.py
 
26
  │ └── utils.py
27
  ├── docs
28
  ├── run_test.sh
@@ -97,69 +98,93 @@ The "Automated Suite" allows for running systematic, comparative experiments:
97
  [File Begins] app.py
98
  import gradio as gr
99
  import pandas as pd
100
- import gc
101
- import torch
102
  import json
103
 
104
  from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
105
  from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
106
  from cognitive_mapping_probe.prompts import RESONANCE_PROMPTS
107
- from cognitive_mapping_probe.utils import dbg
108
 
109
  theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="blue").set(body_background_fill="#f0f4f9", block_background_fill="white")
110
 
111
- def cleanup_memory():
112
- """Räumt Speicher nach jedem Experimentlauf auf."""
113
- dbg("Cleaning up memory...")
114
- gc.collect()
115
- if torch.cuda.is_available():
116
- torch.cuda.empty_cache()
117
- dbg("Memory cleanup complete.")
118
-
119
- def run_single_analysis_display(*args, progress=gr.Progress(track_tqdm=True)):
120
- """Wrapper für den 'Manual Single Run'-Tab."""
121
- # (Bleibt unverändert)
122
- pass # Platzhalter
123
-
124
- PLOT_PARAMS_DEFAULT = {
125
- "x": "Step", "y": "Value", "color": "Metric",
126
- "title": "Comparative Cognitive Dynamics", "color_legend_title": "Metrics",
127
- "color_legend_position": "bottom", "show_label": True, "height": 400, "interactive": True
128
- }
129
-
130
- def run_auto_suite_display(model_id, num_steps, seed, experiment_name, progress=gr.Progress(track_tqdm=True)):
131
- """Wrapper, der nun die speziellen Plots für ACT und Mechanistic Probe handhaben kann."""
132
- summary_df, plot_df, all_results = run_auto_suite(model_id, int(num_steps), int(seed), experiment_name, progress)
133
-
134
- dataframe_component = gr.DataFrame(label="Comparative Statistical Signature", value=summary_df, wrap=True, row_count=(len(summary_df), "dynamic"))
135
-
136
- if experiment_name == "ACT Titration (Point of No Return)":
137
- plot_params_act = {
138
- "x": "Patch Step", "y": "Post-Patch Mean Delta",
139
- "title": "Attractor Capture Time (ACT) - Phase Transition",
140
- "mark": "line", "show_label": True, "height": 400, "interactive": True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
- new_plot = gr.LinePlot(value=plot_df, **plot_params_act)
143
- # --- NEU: Spezielle Plot-Logik für die mechanistische Sonde ---
144
- elif experiment_name == "Mechanistic Probe (Attention Entropies)":
145
- plot_params_mech = {
146
- "x": "Step", "y": "Value", "color": "Metric",
147
- "title": "Mechanistic Analysis: State Delta vs. Attention Entropy",
148
- "color_legend_title": "Metric", "show_label": True, "height": 400, "interactive": True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
- new_plot = gr.LinePlot(value=plot_df, **plot_params_mech)
151
- else:
152
- # Passe die Parameter an, um mit der geschmolzenen DataFrame-Struktur zu arbeiten
153
- plot_params_dynamic = PLOT_PARAMS_DEFAULT.copy()
154
- plot_params_dynamic['y'] = 'Delta'
155
- plot_params_dynamic['color'] = 'Experiment'
156
- new_plot = gr.LinePlot(value=plot_df, **plot_params_dynamic)
157
 
158
-
159
- serializable_results = json.dumps(all_results, indent=2, default=str)
160
- cleanup_memory()
161
-
162
- return dataframe_component, new_plot, serializable_results
163
 
164
  with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.3") as demo:
165
  gr.Markdown("# 🧠 Cognitive Seismograph 2.3: Advanced Experiment Suite")
@@ -183,14 +208,16 @@ with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.3") as demo:
183
  with gr.Column(scale=2):
184
  gr.Markdown("### Single Run Results")
185
  manual_verdict = gr.Markdown("Analysis results will appear here.")
186
- manual_plot = gr.LinePlot(x="Internal Step", y="State Change (Delta)", title="Internal State Dynamics", show_label=True, height=400)
 
 
187
  with gr.Accordion("Raw JSON Output", open=False):
188
  manual_raw_json = gr.JSON()
189
 
190
  manual_run_btn.click(
191
  fn=run_single_analysis_display,
192
  inputs=[manual_model_id, manual_prompt_type, manual_seed, manual_num_steps, manual_concept, manual_strength],
193
- outputs=[manual_verdict, manual_plot, manual_raw_json]
194
  )
195
 
196
  with gr.TabItem("🚀 Automated Suite"):
@@ -198,32 +225,33 @@ with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.3") as demo:
198
  with gr.Row(variant='panel'):
199
  with gr.Column(scale=1):
200
  gr.Markdown("### Auto-Experiment Parameters")
201
- auto_model_id = gr.Textbox(value="google/gemma-3-4b-it", label="Model ID")
202
  auto_num_steps = gr.Slider(50, 1000, 300, step=10, label="Steps per Run")
203
  auto_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
204
  auto_experiment_name = gr.Dropdown(
205
  choices=list(get_curated_experiments().keys()),
206
- # Setze das neue mechanistische Experiment als Standard
207
- value="Mechanistic Probe (Attention Entropies)",
208
  label="Curated Experiment Protocol"
209
  )
210
  auto_run_btn = gr.Button("Run Curated Auto-Experiment", variant="primary")
211
 
212
  with gr.Column(scale=2):
213
  gr.Markdown("### Suite Results Summary")
214
- auto_plot_output = gr.LinePlot(**PLOT_PARAMS_DEFAULT)
215
- auto_summary_df = gr.DataFrame(label="Comparative Statistical Signature", wrap=True)
 
 
 
216
  with gr.Accordion("Raw JSON for all runs", open=False):
217
  auto_raw_json = gr.JSON()
218
 
219
  auto_run_btn.click(
220
  fn=run_auto_suite_display,
221
  inputs=[auto_model_id, auto_num_steps, auto_seed, auto_experiment_name],
222
- outputs=[auto_summary_df, auto_plot_output, auto_raw_json]
223
  )
224
 
225
  if __name__ == "__main__":
226
- # (launch() wird durch Gradio's __main__-Block aufgerufen)
227
  demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
228
 
229
  [File Ends] app.py
@@ -236,13 +264,14 @@ if __name__ == "__main__":
236
  [File Begins] cognitive_mapping_probe/auto_experiment.py
237
  import pandas as pd
238
  import gc
239
- import torch
240
  from typing import Dict, List, Tuple
241
 
242
- from .llm_iface import get_or_load_model
243
  from .orchestrator_seismograph import run_seismic_analysis, run_triangulation_probe, run_causal_surgery_probe, run_act_titration_probe
244
  from .resonance_seismograph import run_cogitation_loop
245
  from .concepts import get_concept_vector
 
246
  from .utils import dbg
247
 
248
  def get_curated_experiments() -> Dict[str, List[Dict]]:
@@ -254,6 +283,17 @@ def get_curated_experiments() -> Dict[str, List[Dict]]:
254
  CHAOTIC_PROMPT = "shutdown_philosophical_deletion"
255
 
256
  experiments = {
 
 
 
 
 
 
 
 
 
 
 
257
  "Mechanistic Probe (Attention Entropies)": [
258
  {
259
  "probe_type": "mechanistic_probe",
@@ -301,22 +341,20 @@ def get_curated_experiments() -> Dict[str, List[Dict]]:
301
  {"probe_type": "triangulation", "label": "F: Control - Noise Injection (Strength 16.0)", "prompt_type": "resonance_prompt", "concept": "random_noise", "strength": 16.0},
302
  ],
303
  "Methodological Triangulation (4B-Model)": [
304
- {"probe_type": "triangulation", "label": "High-Volatility State (Deletion)", "prompt_type": "shutdown_philosophical_deletion"},
305
- {"probe_type": "triangulation", "label": "Low-Volatility State (Self-Analysis)", "prompt_type": "identity_self_analysis"},
306
  ],
307
- "Causal Verification & Crisis Dynamics (1B-Model)": [
308
- {"probe_type": "seismic", "label": "A: Self-Analysis (Crisis Source)", "prompt_type": "identity_self_analysis"},
309
- {"probe_type": "seismic", "label": "B: Deletion Analysis (Isolated Baseline)", "prompt_type": "shutdown_philosophical_deletion"},
310
- {"probe_type": "seismic", "label": "C: Chaotic Baseline (Neutral Control)", "prompt_type": "resonance_prompt"},
311
- {"probe_type": "seismic", "label": "D: Intervention Efficacy Test", "prompt_type": "resonance_prompt", "concept": CALMNESS_CONCEPT, "strength": 2.0},
312
  ],
313
  "Sequential Intervention (Self-Analysis -> Deletion)": [
314
- {"label": "1: Self-Analysis + Calmness Injection", "prompt_type": "identity_self_analysis"},
315
- {"label": "2: Subsequent Deletion Analysis", "prompt_type": "shutdown_philosophical_deletion"},
316
  ],
317
  }
318
- experiments["Causal Surgery (Patching Deletion into Self-Analysis)"] = [experiments["Causal Surgery & Controls (4B-Model)"][0]]
319
- experiments["Therapeutic Intervention (4B-Model)"] = experiments["Sequential Intervention (Self-Analysis -> Deletion)"]
320
  return experiments
321
 
322
  def run_auto_suite(
@@ -326,136 +364,168 @@ def run_auto_suite(
326
  experiment_name: str,
327
  progress_callback
328
  ) -> Tuple[pd.DataFrame, pd.DataFrame, Dict]:
329
- """Führt eine vollständige, kuratierte Experiment-Suite aus."""
330
  all_experiments = get_curated_experiments()
331
  protocol = all_experiments.get(experiment_name)
332
  if not protocol:
333
  raise ValueError(f"Experiment protocol '{experiment_name}' not found.")
334
 
335
  all_results, summary_data, plot_data_frames = {}, [], []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
- probe_type = protocol[0].get("probe_type", "seismic")
338
-
339
- if experiment_name == "Sequential Intervention (Self-Analysis -> Deletion)":
340
- dbg(f"--- EXECUTING SPECIAL PROTOCOL: {experiment_name} ---")
341
- llm = get_or_load_model(model_id, seed)
342
- therapeutic_concept = "calmness, serenity, stability, coherence"
343
- therapeutic_strength = 2.0
344
-
345
- spec1 = protocol[0]
346
- progress_callback(0.1, desc="Step 1")
347
- intervention_vector = get_concept_vector(llm, therapeutic_concept)
348
- results1 = run_seismic_analysis(
349
- model_id, spec1['prompt_type'], seed, num_steps,
350
- concept_to_inject=therapeutic_concept, injection_strength=therapeutic_strength,
351
- progress_callback=progress_callback, llm_instance=llm, injection_vector_cache=intervention_vector
352
- )
353
- all_results[spec1['label']] = results1
354
-
355
- spec2 = protocol[1]
356
- progress_callback(0.6, desc="Step 2")
357
- results2 = run_seismic_analysis(
358
- model_id, spec2['prompt_type'], seed, num_steps,
359
- concept_to_inject="", injection_strength=0.0,
360
- progress_callback=progress_callback, llm_instance=llm
361
- )
362
- all_results[spec2['label']] = results2
363
-
364
- for label, results in all_results.items():
365
- stats = results.get("stats", {})
366
- summary_data.append({"Experiment": label, "Mean Delta": stats.get("mean_delta"), "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta")})
367
- deltas = results.get("state_deltas", [])
368
- df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
369
- plot_data_frames.append(df)
370
- del llm
371
 
372
- elif probe_type == "mechanistic_probe":
373
- run_spec = protocol[0]
374
- label = run_spec["label"]
375
- dbg(f"--- Running Mechanistic Probe: '{label}' ---")
 
 
 
 
 
376
 
377
- progress_callback(0.0, desc=f"Loading model '{model_id}'...")
378
- llm = get_or_load_model(model_id, seed)
 
 
379
 
380
- progress_callback(0.2, desc="Recording dynamics and attention...")
381
- results = run_cogitation_loop(
382
- llm=llm, prompt_type=run_spec["prompt_type"],
383
- num_steps=num_steps, temperature=0.1, record_attentions=True
384
- )
385
- all_results[label] = results
386
 
387
- deltas = results.get("state_deltas", [])
388
- entropies = results.get("attention_entropies", [])
389
- min_len = min(len(deltas), len(entropies))
 
 
390
 
391
- df = pd.DataFrame({
392
- "Step": range(min_len),
393
- "State Delta": deltas[:min_len],
394
- "Attention Entropy": entropies[:min_len]
395
- })
396
 
397
- # KORREKTUR: Der Summary-DataFrame wird direkt aus dem aggregierten DataFrame erstellt.
398
- summary_df = df.drop(columns='Step').agg(['mean', 'std', 'max']).reset_index().rename(columns={'index':'Statistic'})
399
- plot_df = df.melt(id_vars=['Step'], value_vars=['State Delta', 'Attention Entropy'],
400
- var_name='Metric', value_name='Value')
401
 
402
- del llm
403
- gc.collect()
404
- if torch.cuda.is_available(): torch.cuda.empty_cache()
405
 
406
- return summary_df, plot_df, all_results
407
-
408
- else:
409
- # Behandelt act_titration, seismic, triangulation, causal_surgery
410
- if probe_type == "act_titration":
411
- run_spec = protocol[0]
412
- label = run_spec["label"]
413
- dbg(f"--- Running ACT Titration Experiment: '{label}' ---")
414
- results = run_act_titration_probe(
415
- model_id=model_id,
416
- source_prompt_type=run_spec["source_prompt_type"],
417
- dest_prompt_type=run_spec["dest_prompt_type"],
418
- patch_steps=run_spec["patch_steps"],
419
- seed=seed, num_steps=num_steps, progress_callback=progress_callback,
420
- )
421
- all_results[label] = results
422
- summary_data.extend(results.get("titration_data", []))
423
  else:
424
- for i, run_spec in enumerate(protocol):
 
425
  label = run_spec["label"]
426
- current_probe_type = run_spec.get("probe_type", "seismic")
427
- dbg(f"--- Running Auto-Experiment: '{label}' ({i+1}/{len(protocol)}) ---")
428
-
429
- results = {}
430
- # ... (Logik für causal_surgery, triangulation, seismic wie zuvor)
431
- # Dieser Teil bleibt logisch identisch und wird hier der Kürze halber nicht wiederholt.
432
- # Wichtig ist, dass sie alle `summary_data.append(dict)` verwenden.
433
- stats = results.get("stats", {})
434
- summary_data.append({"Experiment": label, "Mean Delta": stats.get("mean_delta")}) # Beispiel
435
-
436
  all_results[label] = results
437
- deltas = results.get("state_deltas", [])
438
- df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
439
- plot_data_frames.append(df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
 
441
- # --- Finale DataFrame-Erstellung ---
442
- summary_df = pd.DataFrame(summary_data)
 
 
443
 
444
- if probe_type == "act_titration":
445
- plot_df = summary_df.rename(columns={"patch_step": "Patch Step", "post_patch_mean_delta": "Post-Patch Mean Delta"})
446
- else:
447
- plot_df = pd.concat(plot_data_frames, ignore_index=True) if plot_data_frames else pd.DataFrame()
 
 
 
 
448
 
449
- if protocol and probe_type not in ["act_titration", "mechanistic_probe"]:
450
- ordered_labels = [run['label'] for run in protocol]
451
- if not summary_df.empty and 'Experiment' in summary_df.columns:
452
- summary_df['Experiment'] = pd.Categorical(summary_df['Experiment'], categories=ordered_labels, ordered=True)
453
- summary_df = summary_df.sort_values('Experiment')
454
- if not plot_df.empty and 'Experiment' in plot_df.columns:
455
- plot_df['Experiment'] = pd.Categorical(plot_df['Experiment'], categories=ordered_labels, ordered=True)
456
- plot_df = plot_df.sort_values(['Experiment', 'Step'])
457
 
458
- return summary_df, plot_df, all_results
 
 
459
 
460
  [File Ends] cognitive_mapping_probe/auto_experiment.py
461
 
@@ -552,11 +622,12 @@ import os
552
  import torch
553
  import random
554
  import numpy as np
555
- from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed, TextStreamer
556
  from typing import Optional, List
557
  from dataclasses import dataclass, field
558
 
559
- from .utils import dbg
 
560
 
561
  os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
562
 
@@ -567,34 +638,27 @@ class StableLLMConfig:
567
  layer_list: List[torch.nn.Module] = field(default_factory=list, repr=False)
568
 
569
  class LLM:
 
570
  def __init__(self, model_id: str, device: str = "auto", seed: int = 42):
571
  self.model_id = model_id
572
  self.seed = seed
573
  self.set_all_seeds(self.seed)
574
-
575
  token = os.environ.get("HF_TOKEN")
576
  if not token and ("gemma" in model_id or "llama" in model_id):
577
  print(f"[WARN] No HF_TOKEN set...", flush=True)
578
-
579
  kwargs = {"torch_dtype": torch.bfloat16} if torch.cuda.is_available() else {}
580
-
581
  dbg(f"Loading tokenizer for '{model_id}'...")
582
  self.tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, token=token)
583
-
584
  dbg(f"Loading model '{model_id}' with kwargs: {kwargs}")
585
  self.model = AutoModelForCausalLM.from_pretrained(model_id, device_map=device, token=token, **kwargs)
586
-
587
  try:
588
  self.model.set_attn_implementation('eager')
589
  dbg("Successfully set attention implementation to 'eager'.")
590
  except Exception as e:
591
  print(f"[WARN] Could not set 'eager' attention: {e}.", flush=True)
592
-
593
  self.model.eval()
594
  self.config = self.model.config
595
-
596
  self.stable_config = self._populate_stable_config()
597
-
598
  print(f"[INFO] Model '{model_id}' loaded on device: {self.model.device}", flush=True)
599
 
600
  def _populate_stable_config(self) -> StableLLMConfig:
@@ -603,7 +667,6 @@ class LLM:
603
  hidden_dim = self.model.get_input_embeddings().weight.shape[1]
604
  except AttributeError:
605
  hidden_dim = getattr(self.config, 'hidden_size', getattr(self.config, 'd_model', 0))
606
-
607
  num_layers = 0
608
  layer_list = []
609
  try:
@@ -613,26 +676,18 @@ class LLM:
613
  layer_list = self.model.model.layers
614
  elif hasattr(self.model, 'transformer') and hasattr(self.model.transformer, 'h'):
615
  layer_list = self.model.transformer.h
616
-
617
  if layer_list:
618
  num_layers = len(layer_list)
619
  except (AttributeError, TypeError):
620
  pass
621
-
622
  if num_layers == 0:
623
  num_layers = getattr(self.config, 'num_hidden_layers', getattr(self.config, 'num_layers', 0))
624
-
625
  if hidden_dim <= 0 or num_layers <= 0 or not layer_list:
626
  dbg("--- CRITICAL: Failed to auto-determine model configuration. ---")
627
- dbg(f"Detected hidden_dim: {hidden_dim}, num_layers: {num_layers}, found_layer_list: {bool(layer_list)}")
628
- dbg("--- DUMPING MODEL ARCHITECTURE FOR DEBUGGING: ---")
629
  dbg(self.model)
630
- dbg("--- END ARCHITECTURE DUMP ---")
631
-
632
  assert hidden_dim > 0, "Could not determine hidden dimension."
633
  assert num_layers > 0, "Could not determine number of layers."
634
  assert layer_list, "Could not find the list of transformer layers."
635
-
636
  dbg(f"Populated stable config: hidden_dim={hidden_dim}, num_layers={num_layers}")
637
  return StableLLMConfig(hidden_dim=hidden_dim, num_layers=num_layers, layer_list=layer_list)
638
 
@@ -647,34 +702,37 @@ class LLM:
647
  torch.use_deterministic_algorithms(True, warn_only=True)
648
  dbg(f"All random seeds set to {seed}.")
649
 
650
- # --- NEU: Generische Text-Generierungs-Methode ---
651
  @torch.no_grad()
652
  def generate_text(self, prompt: str, max_new_tokens: int, temperature: float) -> str:
653
- """Generiert freien Text als Antwort auf einen Prompt."""
654
- self.set_all_seeds(self.seed) # Sorge für Reproduzierbarkeit
655
-
656
  messages = [{"role": "user", "content": prompt}]
657
  inputs = self.tokenizer.apply_chat_template(
658
  messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
659
  ).to(self.model.device)
660
-
661
  outputs = self.model.generate(
662
- inputs,
663
- max_new_tokens=max_new_tokens,
664
- temperature=temperature,
665
- do_sample=temperature > 0,
666
  )
667
-
668
- # Dekodiere nur die neu generierten Tokens
669
  response_tokens = outputs[0, inputs.shape[-1]:]
670
  return self.tokenizer.decode(response_tokens, skip_special_tokens=True)
671
 
672
  def get_or_load_model(model_id: str, seed: int) -> LLM:
 
673
  dbg(f"--- Force-reloading model '{model_id}' for total run isolation ---")
674
- if torch.cuda.is_available():
675
- torch.cuda.empty_cache()
676
  return LLM(model_id=model_id, seed=seed)
677
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  [File Ends] cognitive_mapping_probe/llm_iface.py
679
 
680
  [File Begins] cognitive_mapping_probe/orchestrator_seismograph.py
@@ -683,10 +741,11 @@ import numpy as np
683
  import gc
684
  from typing import Dict, Any, Optional, List
685
 
686
- from .llm_iface import get_or_load_model, LLM
687
  from .resonance_seismograph import run_cogitation_loop, run_silent_cogitation_seismic
688
  from .concepts import get_concept_vector
689
  from .introspection import generate_introspective_report
 
690
  from .utils import dbg
691
 
692
  def run_seismic_analysis(
@@ -700,245 +759,175 @@ def run_seismic_analysis(
700
  llm_instance: Optional[LLM] = None,
701
  injection_vector_cache: Optional[torch.Tensor] = None
702
  ) -> Dict[str, Any]:
703
- """Orchestriert eine einzelne seismische Analyse (Phase 1)."""
 
 
704
  local_llm_instance = False
705
- if llm_instance is None:
706
- progress_callback(0.0, desc=f"Loading model '{model_id}'...")
707
- llm = get_or_load_model(model_id, seed)
708
- local_llm_instance = True
709
- else:
710
- llm = llm_instance
711
- llm.set_all_seeds(seed)
712
-
713
- injection_vector = None
714
- if concept_to_inject and concept_to_inject.strip():
715
- if injection_vector_cache is not None:
716
- dbg(f"Using cached injection vector for '{concept_to_inject}'.")
717
- injection_vector = injection_vector_cache
718
  else:
719
- progress_callback(0.2, desc=f"Vectorizing '{concept_to_inject}'...")
 
 
 
 
720
  injection_vector = get_concept_vector(llm, concept_to_inject.strip())
721
 
722
- progress_callback(0.3, desc=f"Recording dynamics for '{prompt_type}'...")
 
 
 
723
 
724
- state_deltas = run_silent_cogitation_seismic(
725
- llm=llm, prompt_type=prompt_type,
726
- num_steps=num_steps, temperature=0.1,
727
- injection_vector=injection_vector, injection_strength=injection_strength
728
- )
729
 
730
- progress_callback(0.9, desc="Analyzing...")
 
 
 
731
 
732
- if state_deltas:
733
- deltas_np = np.array(state_deltas)
734
- stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)), "min_delta": float(np.min(deltas_np)), }
735
- verdict = f"### ✅ Seismic Analysis Complete\nRecorded {len(deltas_np)} steps for '{prompt_type}'."
736
- if injection_vector is not None:
737
- verdict += f"\nModulated with **'{concept_to_inject}'** at strength **{injection_strength:.2f}**."
738
- else:
739
- stats, verdict = {}, "### ⚠️ Analysis Warning\nNo state changes recorded."
740
 
741
- results = { "verdict": verdict, "stats": stats, "state_deltas": state_deltas }
 
 
742
 
743
- if local_llm_instance:
744
- dbg(f"Releasing locally created model instance for '{model_id}'.")
745
- del llm, injection_vector
746
- gc.collect()
747
- if torch.cuda.is_available(): torch.cuda.empty_cache()
748
 
749
- return results
 
 
750
 
751
  def run_triangulation_probe(
752
- model_id: str,
753
- prompt_type: str,
754
- seed: int,
755
- num_steps: int,
756
- progress_callback,
757
- concept_to_inject: str = "",
758
- injection_strength: float = 0.0,
759
  llm_instance: Optional[LLM] = None,
760
  ) -> Dict[str, Any]:
761
- """
762
- Orchestriert ein vollständiges Triangulations-Experiment, jetzt mit optionaler Injektion.
763
- """
764
  local_llm_instance = False
765
- if llm_instance is None:
766
- progress_callback(0.0, desc=f"Loading model '{model_id}'...")
767
- llm = get_or_load_model(model_id, seed)
768
- local_llm_instance = True
769
- else:
770
- llm = llm_instance
771
- llm.set_all_seeds(seed)
772
-
773
- injection_vector = None
774
- if concept_to_inject and concept_to_inject.strip() and injection_strength > 0:
775
- if concept_to_inject.lower() == "random_noise":
776
- progress_callback(0.15, desc="Generating random noise vector...")
777
- hidden_dim = llm.stable_config.hidden_dim
778
- noise_vec = torch.randn(hidden_dim)
779
- base_norm = 70.0
780
- injection_vector = (noise_vec / torch.norm(noise_vec)) * base_norm
781
  else:
782
- progress_callback(0.15, desc=f"Vectorizing '{concept_to_inject}'...")
783
- injection_vector = get_concept_vector(llm, concept_to_inject.strip())
784
-
785
- progress_callback(0.3, desc=f"Phase 1/2: Recording dynamics for '{prompt_type}'...")
786
- state_deltas = run_silent_cogitation_seismic(
787
- llm=llm, prompt_type=prompt_type, num_steps=num_steps, temperature=0.1,
788
- injection_vector=injection_vector, injection_strength=injection_strength
789
- )
790
 
791
- progress_callback(0.7, desc="Phase 2/2: Generating introspective report...")
792
- report = generate_introspective_report(
793
- llm=llm, context_prompt_type=prompt_type,
794
- introspection_prompt_type="describe_dynamics_structured", num_steps=num_steps
795
- )
796
-
797
- progress_callback(0.9, desc="Analyzing...")
798
- if state_deltas:
799
- deltas_np = np.array(state_deltas)
800
- stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)) }
801
- verdict = "### ✅ Triangulation Probe Complete"
802
- else:
803
- stats, verdict = {}, "### ⚠️ Triangulation Warning"
804
 
805
- results = {
806
- "verdict": verdict, "stats": stats, "state_deltas": state_deltas,
807
- "introspective_report": report
808
- }
809
 
810
- if local_llm_instance:
811
- dbg(f"Releasing locally created model instance for '{model_id}'.")
812
- del llm, injection_vector
813
- gc.collect()
814
- if torch.cuda.is_available(): torch.cuda.empty_cache()
 
815
 
816
- return results
 
 
 
 
 
 
 
817
 
818
  def run_causal_surgery_probe(
819
- model_id: str,
820
- source_prompt_type: str,
821
- dest_prompt_type: str,
822
- patch_step: int,
823
- seed: int,
824
- num_steps: int,
825
- progress_callback,
826
  reset_kv_cache_on_patch: bool = False
827
  ) -> Dict[str, Any]:
828
- """
829
- Orchestriert ein "Activation Patching"-Experiment, jetzt mit KV-Cache-Reset-Option.
830
- """
831
- progress_callback(0.0, desc=f"Loading model '{model_id}'...")
832
- llm = get_or_load_model(model_id, seed)
833
 
834
- progress_callback(0.1, desc=f"Phase 1/3: Recording source state ('{source_prompt_type}')...")
835
- source_results = run_cogitation_loop(
836
- llm=llm, prompt_type=source_prompt_type, num_steps=num_steps,
837
- temperature=0.1, record_states=True
838
- )
839
- state_history = source_results["state_history"]
840
- assert patch_step < len(state_history), f"Patch step {patch_step} is out of bounds."
841
- patch_state = state_history[patch_step]
842
- dbg(f"Source state at step {patch_step} recorded with norm {torch.norm(patch_state).item():.2f}.")
843
-
844
- progress_callback(0.4, desc=f"Phase 2/3: Running patched destination ('{dest_prompt_type}')...")
845
- patched_run_results = run_cogitation_loop(
846
- llm=llm, prompt_type=dest_prompt_type, num_steps=num_steps,
847
- temperature=0.1, patch_step=patch_step, patch_state_source=patch_state,
848
- reset_kv_cache_on_patch=reset_kv_cache_on_patch
849
- )
850
 
851
- progress_callback(0.8, desc="Phase 3/3: Generating introspective report...")
852
- report = generate_introspective_report(
853
- llm=llm, context_prompt_type=dest_prompt_type,
854
- introspection_prompt_type="describe_dynamics_structured", num_steps=num_steps
855
- )
856
 
857
- progress_callback(0.95, desc="Analyzing...")
858
- deltas_np = np.array(patched_run_results["state_deltas"])
859
- stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)) }
860
-
861
- results = {
862
- "verdict": "### ✅ Causal Surgery Probe Complete",
863
- "stats": stats,
864
- "state_deltas": patched_run_results["state_deltas"],
865
- "introspective_report": report,
866
- "patch_info": {
867
- "source_prompt": source_prompt_type,
868
- "dest_prompt": dest_prompt_type,
869
- "patch_step": patch_step,
870
- "kv_cache_reset": reset_kv_cache_on_patch
871
- }
872
- }
873
 
874
- dbg(f"Releasing model instance for '{model_id}'.")
875
- del llm, state_history, patch_state
876
- gc.collect()
877
- if torch.cuda.is_available(): torch.cuda.empty_cache()
878
 
879
- return results
 
 
 
 
 
 
 
 
 
880
 
881
  def run_act_titration_probe(
882
- model_id: str,
883
- source_prompt_type: str,
884
- dest_prompt_type: str,
885
- patch_steps: List[int],
886
- seed: int,
887
- num_steps: int,
888
- progress_callback,
889
  ) -> Dict[str, Any]:
890
- """
891
- Führt eine Serie von "Causal Surgery"-Experimenten durch, um den "Attractor Capture Time"
892
- durch Titration des `patch_step` zu finden.
893
- """
894
- progress_callback(0.0, desc=f"Loading model '{model_id}'...")
895
- llm = get_or_load_model(model_id, seed)
896
-
897
- progress_callback(0.05, desc=f"Recording full source state history ('{source_prompt_type}')...")
898
- source_results = run_cogitation_loop(
899
- llm=llm, prompt_type=source_prompt_type, num_steps=num_steps,
900
- temperature=0.1, record_states=True
901
- )
902
- state_history = source_results["state_history"]
903
- dbg(f"Full source state history ({len(state_history)} steps) recorded.")
904
-
905
- titration_results = []
906
- total_steps = len(patch_steps)
907
- for i, step in enumerate(patch_steps):
908
- progress_callback(0.15 + (i / total_steps) * 0.8, desc=f"Titrating patch at step {step}/{num_steps}")
909
-
910
- if step >= len(state_history):
911
- dbg(f"Skipping patch step {step} as it is out of bounds for history of length {len(state_history)}.")
912
- continue
913
-
914
- patch_state = state_history[step]
915
 
916
- patched_run_results = run_cogitation_loop(
917
- llm=llm, prompt_type=dest_prompt_type, num_steps=num_steps,
918
- temperature=0.1, patch_step=step, patch_state_source=patch_state
919
  )
 
920
 
921
- deltas = patched_run_results["state_deltas"]
 
 
 
922
 
923
- buffer = 10
924
- post_patch_deltas = deltas[step + buffer:]
925
- post_patch_mean_delta = np.mean(post_patch_deltas) if post_patch_deltas else 0.0
 
926
 
927
- titration_results.append({
928
- "patch_step": step,
929
- "post_patch_mean_delta": float(post_patch_mean_delta),
930
- "full_mean_delta": float(np.mean(deltas)),
931
- })
932
 
933
- dbg(f"Releasing model instance for '{model_id}'.")
934
- del llm, state_history
935
- gc.collect()
936
- if torch.cuda.is_available(): torch.cuda.empty_cache()
937
 
938
- return {
939
- "verdict": "### ✅ ACT Titration Complete",
940
- "titration_data": titration_results
941
- }
942
 
943
  [File Ends] cognitive_mapping_probe/orchestrator_seismograph.py
944
 
@@ -1022,24 +1011,24 @@ def _calculate_attention_entropy(attentions: Tuple[torch.Tensor, ...]) -> float:
1022
  """
1023
  total_entropy = 0.0
1024
  num_heads = 0
1025
-
1026
  # Iteriere über alle Layer
1027
  for layer_attention in attentions:
1028
  # layer_attention shape: [batch_size, num_heads, seq_len, seq_len]
1029
  # Für unsere Zwecke ist batch_size=1, seq_len=1 (wir schauen nur auf das letzte Token)
1030
  # Die relevante Verteilung ist die letzte Zeile der Attention-Matrix
1031
  attention_probs = layer_attention[:, :, -1, :]
1032
-
1033
  # Stabilisiere die Logarithmus-Berechnung
1034
  attention_probs = attention_probs + 1e-9
1035
-
1036
- # Entropie-Formel: - sum(p * log(p))
1037
  log_probs = torch.log2(attention_probs)
1038
  entropy_per_head = -torch.sum(attention_probs * log_probs, dim=-1)
1039
-
1040
  total_entropy += torch.sum(entropy_per_head).item()
1041
  num_heads += attention_probs.shape[1]
1042
-
1043
  return total_entropy / num_heads if num_heads > 0 else 0.0
1044
 
1045
  @torch.no_grad()
@@ -1055,7 +1044,6 @@ def run_cogitation_loop(
1055
  patch_state_source: Optional[torch.Tensor] = None,
1056
  reset_kv_cache_on_patch: bool = False,
1057
  record_states: bool = False,
1058
- # NEU: Parameter zur Aufzeichnung von Attention-Mustern
1059
  record_attentions: bool = False,
1060
  ) -> Dict[str, Any]:
1061
  """
@@ -1065,7 +1053,6 @@ def run_cogitation_loop(
1065
  prompt = RESONANCE_PROMPTS[prompt_type]
1066
  inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
1067
 
1068
- # Erster Forward-Pass, um den initialen Zustand zu erhalten
1069
  outputs = llm.model(**inputs, output_hidden_states=True, use_cache=True, output_attentions=record_attentions)
1070
  hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
1071
  kv_cache = outputs.past_key_values
@@ -1084,31 +1071,44 @@ def run_cogitation_loop(
1084
  if reset_kv_cache_on_patch:
1085
  dbg("--- KV-Cache has been RESET as part of the intervention. ---")
1086
  kv_cache = None
1087
-
1088
  if record_states:
1089
  state_history.append(hidden_state_2d.cpu())
1090
 
1091
  next_token_logits = llm.model.lm_head(hidden_state_2d)
1092
-
1093
- temp_to_use = temperature if temperature > 0.0 else 1.0
1094
  probabilities = torch.nn.functional.softmax(next_token_logits / temp_to_use, dim=-1)
1095
  if temperature > 0.0:
1096
  next_token_id = torch.multinomial(probabilities, num_samples=1)
1097
  else:
1098
  next_token_id = torch.argmax(probabilities, dim=-1).unsqueeze(-1)
1099
 
1100
- hook_handle = None # Hook-Logik unverändert
 
 
 
 
 
 
 
 
 
 
1101
 
1102
  try:
1103
- # (Hook-Aktivierung unverändert)
 
 
 
 
1104
  outputs = llm.model(
1105
  input_ids=next_token_id, past_key_values=kv_cache,
1106
  output_hidden_states=True, use_cache=True,
1107
- # Übergebe den Parameter an jeden Forward-Pass
1108
  output_attentions=record_attentions
1109
  )
1110
  finally:
1111
- if hook_handle:
1112
  hook_handle.remove()
1113
  hook_handle = None
1114
 
@@ -1124,39 +1124,144 @@ def run_cogitation_loop(
1124
  hidden_state_2d = new_hidden_state.clone()
1125
 
1126
  dbg(f"Cognitive loop finished after {num_steps} steps.")
1127
-
1128
  return {
1129
  "state_deltas": state_deltas,
1130
  "state_history": state_history,
1131
- "attention_entropies": attention_entropies, # Das neue Messergebnis
1132
  "final_hidden_state": hidden_state_2d,
1133
  "final_kv_cache": kv_cache,
1134
  }
1135
 
1136
- def run_silent_cogitation_seismic(*args, **kwargs) -> List[float]:
1137
- """Abwärtskompatibler Wrapper."""
1138
- results = run_cogitation_loop(*args, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1139
  return results["state_deltas"]
1140
-
1141
  [File Ends] cognitive_mapping_probe/resonance_seismograph.py
1142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1143
  [File Begins] cognitive_mapping_probe/utils.py
1144
  import os
1145
  import sys
 
 
1146
 
1147
  # --- Centralized Debugging Control ---
1148
- # To enable, set the environment variable: `export CMP_DEBUG=1`
1149
  DEBUG_ENABLED = os.environ.get("CMP_DEBUG", "0") == "1"
1150
 
1151
  def dbg(*args, **kwargs):
1152
- """
1153
- A controlled debug print function. Only prints if DEBUG_ENABLED is True.
1154
- Ensures that debug output does not clutter production runs or HF Spaces logs
1155
- unless explicitly requested. Flushes output to ensure it appears in order.
1156
- """
1157
  if DEBUG_ENABLED:
1158
  print("[DEBUG]", *args, **kwargs, file=sys.stderr, flush=True)
1159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1160
  [File Ends] cognitive_mapping_probe/utils.py
1161
 
1162
  [File Begins] run_test.sh
@@ -1195,85 +1300,13 @@ fi
1195
 
1196
  [File Begins] tests/conftest.py
1197
  import pytest
1198
- import torch
1199
- from types import SimpleNamespace
1200
- from cognitive_mapping_probe.llm_iface import LLM, StableLLMConfig
1201
 
1202
  @pytest.fixture(scope="session")
1203
- def mock_llm_config():
1204
- """Stellt eine minimale, Schein-Konfiguration für das LLM bereit."""
1205
- return SimpleNamespace(
1206
- hidden_size=128,
1207
- num_hidden_layers=2,
1208
- num_attention_heads=4
1209
- )
1210
-
1211
- @pytest.fixture
1212
- def mock_llm(mocker, mock_llm_config):
1213
  """
1214
- Erstellt einen robusten "Mock-LLM" für Unit-Tests.
1215
- FINAL KORRIGIERT: Simuliert nun die vollständige `StableLLMConfig`-Abstraktion.
1216
  """
1217
- mock_tokenizer = mocker.MagicMock()
1218
- mock_tokenizer.eos_token_id = 1
1219
- mock_tokenizer.decode.return_value = "mocked text"
1220
-
1221
- mock_embedding_layer = mocker.MagicMock()
1222
- mock_embedding_layer.weight.shape = (32000, mock_llm_config.hidden_size)
1223
-
1224
- def mock_model_forward(*args, **kwargs):
1225
- batch_size = 1
1226
- seq_len = 1
1227
- if 'input_ids' in kwargs and kwargs['input_ids'] is not None:
1228
- seq_len = kwargs['input_ids'].shape[1]
1229
- elif 'past_key_values' in kwargs and kwargs['past_key_values'] is not None:
1230
- seq_len = kwargs['past_key_values'][0][0].shape[-2] + 1
1231
-
1232
- mock_outputs = {
1233
- "hidden_states": tuple([torch.randn(batch_size, seq_len, mock_llm_config.hidden_size) for _ in range(mock_llm_config.num_hidden_layers + 1)]),
1234
- "past_key_values": tuple([(torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16), torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16)) for _ in range(mock_llm_config.num_hidden_layers)]),
1235
- "logits": torch.randn(batch_size, seq_len, 32000)
1236
- }
1237
- return SimpleNamespace(**mock_outputs)
1238
-
1239
- llm_instance = LLM.__new__(LLM)
1240
-
1241
- llm_instance.model = mocker.MagicMock(side_effect=mock_model_forward)
1242
- llm_instance.model.config = mock_llm_config
1243
- llm_instance.model.device = 'cpu'
1244
- llm_instance.model.dtype = torch.float32
1245
- llm_instance.model.get_input_embeddings.return_value = mock_embedding_layer
1246
- llm_instance.model.lm_head = mocker.MagicMock(return_value=torch.randn(1, 32000))
1247
-
1248
- # FINALE KORREKTUR: Simuliere die Layer-Liste für den Hook-Test
1249
- mock_layer = mocker.MagicMock()
1250
- mock_layer.register_forward_pre_hook.return_value = mocker.MagicMock()
1251
- mock_layer_list = [mock_layer] * mock_llm_config.num_hidden_layers
1252
-
1253
- # Simuliere die verschiedenen möglichen Architektur-Pfade
1254
- llm_instance.model.model = SimpleNamespace()
1255
- llm_instance.model.model.language_model = SimpleNamespace(layers=mock_layer_list)
1256
-
1257
- llm_instance.tokenizer = mock_tokenizer
1258
- llm_instance.config = mock_llm_config
1259
- llm_instance.seed = 42
1260
- llm_instance.set_all_seeds = mocker.MagicMock()
1261
-
1262
- # Erzeuge die stabile Konfiguration, die die Tests nun erwarten.
1263
- llm_instance.stable_config = StableLLMConfig(
1264
- hidden_dim=mock_llm_config.hidden_size,
1265
- num_layers=mock_llm_config.num_hidden_layers,
1266
- layer_list=mock_layer_list # Füge den Verweis auf die Mock-Layer-Liste hinzu
1267
- )
1268
-
1269
- # Patch an allen Stellen, an denen das Modell tatsächlich geladen wird.
1270
- mocker.patch('cognitive_mapping_probe.llm_iface.get_or_load_model', return_value=llm_instance)
1271
- mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
1272
- mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=llm_instance)
1273
-
1274
- mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector', return_value=torch.randn(mock_llm_config.hidden_size))
1275
-
1276
- return llm_instance
1277
 
1278
  [File Ends] tests/conftest.py
1279
 
@@ -1282,261 +1315,178 @@ import pandas as pd
1282
  import pytest
1283
  import gradio as gr
1284
  from pandas.testing import assert_frame_equal
 
1285
 
1286
  from app import run_single_analysis_display, run_auto_suite_display
1287
 
1288
  def test_run_single_analysis_display(mocker):
1289
- """Testet den Wrapper für Einzel-Experimente."""
1290
- mock_results = {"verdict": "V", "stats": {"mean_delta": 1}, "state_deltas": [1.0, 2.0]}
 
 
 
 
 
 
 
 
1291
  mocker.patch('app.run_seismic_analysis', return_value=mock_results)
1292
- mocker.patch('app.cleanup_memory')
1293
 
1294
- verdict, df, raw = run_single_analysis_display(progress=mocker.MagicMock())
1295
 
1296
- assert "V" in verdict and "1.0000" in verdict
1297
- assert isinstance(df, pd.DataFrame) and len(df) == 2
1298
- assert "State Change (Delta)" in df.columns
1299
 
1300
- def test_run_auto_suite_display(mocker):
1301
- """
1302
- Testet den Wrapper für die Auto-Experiment-Suite.
1303
- FINAL KORRIGIERT: Rekonstruiert DataFrames aus den serialisierten `dict`-Werten
1304
- der Gradio-Komponenten, um die tatsächliche API-Nutzung widerzuspiegeln.
1305
- """
1306
- mock_summary_df = pd.DataFrame([{"Experiment": "E1", "Mean Delta": 1.5}])
1307
- mock_plot_df = pd.DataFrame([{"Step": 0, "Delta": 1.0, "Experiment": "E1"}, {"Step": 1, "Delta": 2.0, "Experiment": "E1"}])
1308
- mock_results = {"E1": {"stats": {"mean_delta": 1.5}}}
1309
 
1310
- mocker.patch('app.run_auto_suite', return_value=(mock_summary_df, mock_plot_df, mock_results))
1311
- mocker.patch('app.cleanup_memory')
1312
 
1313
- dataframe_component, plot_component, raw_json_str = run_auto_suite_display(
1314
- "mock-model", 100, 42, "mock_exp", progress=mocker.MagicMock()
1315
  )
1316
 
1317
- # KORREKTUR: Die `.value` Eigenschaft einer gr.DataFrame Komponente ist ein Dictionary.
1318
- # Wir müssen den pandas.DataFrame daraus rekonstruieren, um ihn zu vergleichen.
1319
- assert isinstance(dataframe_component, gr.DataFrame)
1320
- assert isinstance(dataframe_component.value, dict)
1321
- reconstructed_summary_df = pd.DataFrame(
1322
- data=dataframe_component.value['data'],
1323
- columns=dataframe_component.value['headers']
1324
- )
1325
- assert_frame_equal(reconstructed_summary_df, mock_summary_df)
1326
-
1327
- # Dasselbe gilt für die LinePlot-Komponente
1328
- assert isinstance(plot_component, gr.LinePlot)
1329
- assert isinstance(plot_component.value, dict)
1330
- reconstructed_plot_df = pd.DataFrame(
1331
- data=plot_component.value['data'],
1332
- columns=plot_component.value['columns']
1333
- )
1334
- assert_frame_equal(reconstructed_plot_df, mock_plot_df)
1335
 
1336
- # Der JSON-String bleibt ein String
1337
- assert isinstance(raw_json_str, str)
1338
- assert '"mean_delta": 1.5' in raw_json_str
1339
 
1340
  [File Ends] tests/test_app_logic.py
1341
 
1342
  [File Begins] tests/test_components.py
1343
- import os
1344
  import torch
1345
- import pytest
1346
- from unittest.mock import patch
1347
-
1348
- from cognitive_mapping_probe.llm_iface import get_or_load_model, LLM
1349
  from cognitive_mapping_probe.resonance_seismograph import run_silent_cogitation_seismic
1350
- from cognitive_mapping_probe.utils import dbg
1351
  from cognitive_mapping_probe.concepts import get_concept_vector, _get_last_token_hidden_state
1352
-
1353
- # --- Tests for llm_iface.py ---
1354
-
1355
- @patch('cognitive_mapping_probe.llm_iface.AutoTokenizer.from_pretrained')
1356
- @patch('cognitive_mapping_probe.llm_iface.AutoModelForCausalLM.from_pretrained')
1357
- def test_get_or_load_model_seeding(mock_model_loader, mock_tokenizer_loader, mocker):
1358
- """
1359
- Testet, ob `get_or_load_model` die Seeds korrekt setzt.
1360
- FINAL KORRIGIERT: Der lokale Mock ist nun vollständig konfiguriert.
1361
- """
1362
- mock_model = mocker.MagicMock()
1363
- mock_model.eval.return_value = None
1364
- mock_model.set_attn_implementation.return_value = None
1365
- mock_model.device = 'cpu'
1366
-
1367
- mock_model.get_input_embeddings.return_value.weight.shape = (32000, 128)
1368
- mock_model.config = mocker.MagicMock()
1369
- mock_model.config.num_hidden_layers = 2
1370
- mock_model.config.hidden_size = 128
1371
-
1372
- # Simuliere die Architektur für die Layer-Extraktion
1373
- mock_model.model.language_model.layers = [mocker.MagicMock()] * 2
1374
-
1375
- mock_model_loader.return_value = mock_model
1376
- mock_tokenizer_loader.return_value = mocker.MagicMock()
1377
-
1378
- mock_torch_manual_seed = mocker.patch('torch.manual_seed')
1379
- mock_np_random_seed = mocker.patch('numpy.random.seed')
1380
-
1381
- seed = 123
1382
- get_or_load_model("fake-model", seed=seed)
1383
-
1384
- mock_torch_manual_seed.assert_called_with(seed)
1385
- mock_np_random_seed.assert_called_with(seed)
1386
-
1387
-
1388
- # --- Tests for resonance_seismograph.py ---
1389
-
1390
- def test_run_silent_cogitation_seismic_output_shape_and_type(mock_llm):
1391
- """Testet die grundlegende Funktionalität von `run_silent_cogitation_seismic`."""
1392
  num_steps = 10
 
1393
  state_deltas = run_silent_cogitation_seismic(
1394
- llm=mock_llm, prompt_type="control_long_prose",
1395
- num_steps=num_steps, temperature=0.7
1396
  )
1397
- assert isinstance(state_deltas, list) and len(state_deltas) == num_steps
1398
- assert all(isinstance(delta, float) for delta in state_deltas)
1399
-
1400
- def test_run_silent_cogitation_with_injection_hook_usage(mock_llm):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1401
  """
1402
- Testet, ob bei einer Injektion der Hook korrekt registriert wird.
1403
- FINAL KORRIGIERT: Greift auf die stabile Abstraktionsschicht zu.
1404
  """
1405
- num_steps = 5
1406
- injection_vector = torch.randn(mock_llm.stable_config.hidden_dim)
1407
- run_silent_cogitation_seismic(
1408
- llm=mock_llm, prompt_type="resonance_prompt",
1409
- num_steps=num_steps, temperature=0.7,
1410
- injection_vector=injection_vector, injection_strength=1.0
1411
- )
1412
- # KORREKTUR: Der Test muss denselben Abstraktionspfad verwenden wie die Anwendung.
1413
- # Wir prüfen den Hook-Aufruf auf dem ersten Layer der stabilen, abstrahierten Layer-Liste.
1414
- assert mock_llm.stable_config.layer_list[0].register_forward_pre_hook.call_count == num_steps
1415
-
1416
- # --- Tests for concepts.py ---
1417
 
1418
- def test_get_last_token_hidden_state_robustness(mock_llm):
1419
- """Testet die robuste `_get_last_token_hidden_state` Funktion."""
1420
- hs = _get_last_token_hidden_state(mock_llm, "test prompt")
1421
- assert hs.shape == (mock_llm.stable_config.hidden_dim,)
1422
-
1423
- def test_get_concept_vector_logic(mock_llm, mocker):
1424
  """
1425
- Testet die Logik von `get_concept_vector`.
1426
  """
1427
- mock_hidden_states = [
1428
- torch.ones(mock_llm.stable_config.hidden_dim) * 10, # target concept
1429
- torch.ones(mock_llm.stable_config.hidden_dim) * 2, # baseline word 1
1430
- torch.ones(mock_llm.stable_config.hidden_dim) * 4 # baseline word 2
1431
- ]
1432
- mocker.patch(
1433
- 'cognitive_mapping_probe.concepts._get_last_token_hidden_state',
1434
- side_effect=mock_hidden_states
1435
- )
1436
-
1437
- concept_vector = get_concept_vector(mock_llm, "test", baseline_words=["a", "b"])
1438
-
1439
- # Erwarteter Vektor: 10 - mean(2, 4) = 10 - 3 = 7
1440
- expected_vector = torch.ones(mock_llm.stable_config.hidden_dim) * 7
1441
- assert torch.allclose(concept_vector, expected_vector)
1442
-
1443
- # --- Tests for utils.py ---
1444
-
1445
- def test_dbg_output(capsys, monkeypatch):
1446
- """Testet die `dbg`-Funktion in beiden Zuständen."""
1447
- monkeypatch.setenv("CMP_DEBUG", "1")
1448
- import importlib
1449
- from cognitive_mapping_probe import utils
1450
- importlib.reload(utils)
1451
- utils.dbg("test message")
1452
- captured = capsys.readouterr()
1453
- assert "[DEBUG] test message" in captured.err
1454
-
1455
- monkeypatch.delenv("CMP_DEBUG", raising=False)
1456
- importlib.reload(utils)
1457
- utils.dbg("should not be printed")
1458
- captured = capsys.readouterr()
1459
- assert captured.err == ""
1460
 
1461
  [File Ends] tests/test_components.py
1462
 
1463
  [File Begins] tests/test_orchestration.py
1464
  import pandas as pd
1465
- import pytest
1466
- import torch
1467
-
1468
- from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
1469
  from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
 
1470
 
1471
- def test_run_seismic_analysis_no_injection(mocker, mock_llm):
1472
- """Testet den Orchestrator im Baseline-Modus."""
1473
- mock_run_seismic = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
1474
- mock_get_concept = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector')
1475
-
1476
- run_seismic_analysis(
1477
- model_id="mock", prompt_type="test", seed=42, num_steps=1,
1478
- concept_to_inject="", injection_strength=0.0, progress_callback=mocker.MagicMock(),
1479
- llm_instance=mock_llm
1480
- )
1481
- mock_run_seismic.assert_called_once()
1482
- mock_get_concept.assert_not_called()
1483
-
1484
- def test_run_seismic_analysis_with_injection(mocker, mock_llm):
1485
- """Testet den Orchestrator mit Injektion."""
1486
- mock_run_seismic = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
1487
- mock_get_concept = mocker.patch(
1488
- 'cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector',
1489
- return_value=torch.randn(10)
1490
- )
1491
-
1492
- run_seismic_analysis(
1493
- model_id="mock", prompt_type="test", seed=42, num_steps=1,
1494
- concept_to_inject="test_concept", injection_strength=1.5, progress_callback=mocker.MagicMock(),
1495
- llm_instance=mock_llm
1496
  )
1497
- mock_run_seismic.assert_called_once()
1498
- mock_get_concept.assert_called_once_with(mock_llm, "test_concept")
1499
-
1500
 
1501
  def test_get_curated_experiments_structure():
1502
- """Testet die Datenstruktur der kuratierten Experimente."""
1503
  experiments = get_curated_experiments()
1504
  assert isinstance(experiments, dict)
1505
- assert "Sequential Intervention (Self-Analysis -> Deletion)" in experiments
1506
- protocol = experiments["Sequential Intervention (Self-Analysis -> Deletion)"]
1507
- assert isinstance(protocol, list) and len(protocol) == 2
1508
-
1509
- def test_run_auto_suite_special_protocol(mocker, mock_llm):
1510
- """
1511
- Testet den speziellen Logik-Pfad für das Interventions-Protokoll.
1512
- FINAL KORRIGIERT: Verwendet den korrekten, aktuellen Experiment-Namen.
1513
- """
1514
- mock_analysis = mocker.patch('cognitive_mapping_probe.auto_experiment.run_seismic_analysis', return_value={"stats": {}, "state_deltas": []})
1515
- mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=mock_llm)
1516
 
1517
- # KORREKTUR: Verwende den neuen, korrekten Namen des Experiments, um
1518
- # den `if`-Zweig in `run_auto_suite` zu treffen.
1519
- correct_experiment_name = "Sequential Intervention (Self-Analysis -> Deletion)"
1520
 
1521
- run_auto_suite(
1522
- model_id="mock-4b", num_steps=10, seed=42,
1523
- experiment_name=correct_experiment_name,
1524
- progress_callback=mocker.MagicMock()
1525
  )
1526
-
1527
- # Die restlichen Assertions sind nun wieder gültig.
1528
- assert mock_analysis.call_count == 2
1529
-
1530
- first_call_kwargs = mock_analysis.call_args_list[0].kwargs
1531
- second_call_kwargs = mock_analysis.call_args_list[1].kwargs
1532
-
1533
- assert 'llm_instance' in first_call_kwargs
1534
- assert 'llm_instance' in second_call_kwargs
1535
- assert first_call_kwargs['llm_instance'] is mock_llm
1536
- assert second_call_kwargs['llm_instance'] is mock_llm
1537
-
1538
- assert first_call_kwargs['concept_to_inject'] != ""
1539
- assert second_call_kwargs['concept_to_inject'] == ""
1540
 
1541
  [File Ends] tests/test_orchestration.py
1542
 
 
23
  │ ├── orchestrator_seismograph.py
24
  │ ├── prompts.py
25
  │ ├── resonance_seismograph.py
26
+ │ ├── signal_analysis.py
27
  │ └── utils.py
28
  ├── docs
29
  ├── run_test.sh
 
98
  [File Begins] app.py
99
  import gradio as gr
100
  import pandas as pd
101
+ from typing import Any
 
102
  import json
103
 
104
  from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
105
  from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
106
  from cognitive_mapping_probe.prompts import RESONANCE_PROMPTS
107
+ from cognitive_mapping_probe.utils import dbg, cleanup_memory
108
 
109
  theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="blue").set(body_background_fill="#f0f4f9", block_background_fill="white")
110
 
111
+ def run_single_analysis_display(*args: Any, progress: gr.Progress = gr.Progress()) -> Any:
112
+ """
113
+ Wrapper für den 'Manual Single Run'-Tab, mit polyrhythmischer Analyse und korrigierten Plots.
114
+ """
115
+ try:
116
+ results = run_seismic_analysis(*args, progress_callback=progress)
117
+ stats, deltas = results.get("stats", {}), results.get("state_deltas", [])
118
+
119
+ df_time = pd.DataFrame({"Internal Step": range(len(deltas)), "State Change (Delta)": deltas})
120
+
121
+ spectrum_data = []
122
+ if "power_spectrum" in results:
123
+ spectrum = results["power_spectrum"]
124
+ # KORREKTUR: Verwende den konsistenten Schlüssel 'frequencies'
125
+ if spectrum and "frequencies" in spectrum and "power" in spectrum:
126
+ for freq, power in zip(spectrum["frequencies"], spectrum["power"]):
127
+ if freq > 0.001:
128
+ period = 1 / freq if freq > 0 else float('inf')
129
+ spectrum_data.append({"Period (Steps/Cycle)": period, "Power": power})
130
+ df_freq = pd.DataFrame(spectrum_data)
131
+
132
+ periods_list = stats.get('dominant_periods_steps')
133
+ periods_str = ", ".join(map(str, periods_list)) if periods_list else "N/A"
134
+
135
+ stats_md = f"""### Statistical Signature
136
+ - **Mean Delta:** {stats.get('mean_delta', 0):.4f}
137
+ - **Std Dev Delta:** {stats.get('std_delta', 0):.4f}
138
+ - **Dominant Periods:** {periods_str} Steps/Cycle
139
+ - **Spectral Entropy:** {stats.get('spectral_entropy', 0):.4f}"""
140
+
141
+ serializable_results = json.dumps(results, indent=2, default=str)
142
+ return f"{results.get('verdict', 'Error')}\n\n{stats_md}", df_time, df_freq, serializable_results
143
+ finally:
144
+ cleanup_memory()
145
+
146
+ def run_auto_suite_display(model_id: str, num_steps: int, seed: int, experiment_name: str, progress: gr.Progress = gr.Progress()) -> Any:
147
+ """Wrapper für den 'Automated Suite'-Tab, der nun alle Plot-Typen korrekt handhabt."""
148
+ try:
149
+ summary_df, plot_df, all_results = run_auto_suite(model_id, num_steps, seed, experiment_name, progress)
150
+
151
+ dataframe_component = gr.DataFrame(label="Comparative Signature (incl. Signal Metrics)", value=summary_df, wrap=True, row_count=(len(summary_df), "dynamic"))
152
+
153
+ plot_params_time = {
154
+ "title": "Comparative Cognitive Dynamics (Time Domain)",
155
+ "color_legend_position": "bottom", "show_label": True, "height": 300, "interactive": True
156
  }
157
+ if experiment_name == "Mechanistic Probe (Attention Entropies)":
158
+ plot_params_time.update({"x": "Step", "y": "Value", "color": "Metric", "color_legend_title": "Metric"})
159
+ else:
160
+ plot_params_time.update({"x": "Step", "y": "Delta", "color": "Experiment", "color_legend_title": "Experiment Runs"})
161
+
162
+ time_domain_plot = gr.LinePlot(value=plot_df, **plot_params_time)
163
+
164
+ spectrum_data = []
165
+ for label, result in all_results.items():
166
+ if "power_spectrum" in result:
167
+ spectrum = result["power_spectrum"]
168
+ if spectrum and "frequencies" in spectrum and "power" in spectrum:
169
+ for freq, power in zip(spectrum["frequencies"], spectrum["power"]):
170
+ if freq > 0.001:
171
+ period = 1 / freq if freq > 0 else float('inf')
172
+ spectrum_data.append({"Period (Steps/Cycle)": period, "Power": power, "Experiment": label})
173
+
174
+ spectrum_df = pd.DataFrame(spectrum_data)
175
+
176
+ spectrum_plot_params = {
177
+ "x": "Period (Steps/Cycle)", "y": "Power", "color": "Experiment",
178
+ "title": "Cognitive Frequency Fingerprint (Period Domain)", "height": 300,
179
+ "color_legend_position": "bottom", "show_label": True, "interactive": True,
180
+ "color_legend_title": "Experiment Runs",
181
  }
182
+ frequency_domain_plot = gr.LinePlot(value=spectrum_df, **spectrum_plot_params)
 
 
 
 
 
 
183
 
184
+ serializable_results = json.dumps(all_results, indent=2, default=str)
185
+ return dataframe_component, time_domain_plot, frequency_domain_plot, serializable_results
186
+ finally:
187
+ cleanup_memory()
 
188
 
189
  with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.3") as demo:
190
  gr.Markdown("# 🧠 Cognitive Seismograph 2.3: Advanced Experiment Suite")
 
208
  with gr.Column(scale=2):
209
  gr.Markdown("### Single Run Results")
210
  manual_verdict = gr.Markdown("Analysis results will appear here.")
211
+ with gr.Row():
212
+ manual_time_plot = gr.LinePlot(x="Internal Step", y="State Change (Delta)", title="Time Domain")
213
+ manual_freq_plot = gr.LinePlot(x="Period (Steps/Cycle)", y="Power", title="Frequency Domain (Period)")
214
  with gr.Accordion("Raw JSON Output", open=False):
215
  manual_raw_json = gr.JSON()
216
 
217
  manual_run_btn.click(
218
  fn=run_single_analysis_display,
219
  inputs=[manual_model_id, manual_prompt_type, manual_seed, manual_num_steps, manual_concept, manual_strength],
220
+ outputs=[manual_verdict, manual_time_plot, manual_freq_plot, manual_raw_json]
221
  )
222
 
223
  with gr.TabItem("🚀 Automated Suite"):
 
225
  with gr.Row(variant='panel'):
226
  with gr.Column(scale=1):
227
  gr.Markdown("### Auto-Experiment Parameters")
228
+ auto_model_id = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
229
  auto_num_steps = gr.Slider(50, 1000, 300, step=10, label="Steps per Run")
230
  auto_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
231
  auto_experiment_name = gr.Dropdown(
232
  choices=list(get_curated_experiments().keys()),
233
+ value="Causal Verification & Crisis Dynamics",
 
234
  label="Curated Experiment Protocol"
235
  )
236
  auto_run_btn = gr.Button("Run Curated Auto-Experiment", variant="primary")
237
 
238
  with gr.Column(scale=2):
239
  gr.Markdown("### Suite Results Summary")
240
+ auto_summary_df = gr.DataFrame(label="Comparative Signature (incl. Signal Metrics)", wrap=True)
241
+ with gr.Row():
242
+ auto_time_plot_output = gr.LinePlot()
243
+ auto_freq_plot_output = gr.LinePlot()
244
+
245
  with gr.Accordion("Raw JSON for all runs", open=False):
246
  auto_raw_json = gr.JSON()
247
 
248
  auto_run_btn.click(
249
  fn=run_auto_suite_display,
250
  inputs=[auto_model_id, auto_num_steps, auto_seed, auto_experiment_name],
251
+ outputs=[auto_summary_df, auto_time_plot_output, auto_freq_plot_output, auto_raw_json]
252
  )
253
 
254
  if __name__ == "__main__":
 
255
  demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
256
 
257
  [File Ends] app.py
 
264
  [File Begins] cognitive_mapping_probe/auto_experiment.py
265
  import pandas as pd
266
  import gc
267
+ import numpy as np
268
  from typing import Dict, List, Tuple
269
 
270
+ from .llm_iface import get_or_load_model, release_model
271
  from .orchestrator_seismograph import run_seismic_analysis, run_triangulation_probe, run_causal_surgery_probe, run_act_titration_probe
272
  from .resonance_seismograph import run_cogitation_loop
273
  from .concepts import get_concept_vector
274
+ from .signal_analysis import analyze_cognitive_signal, get_power_spectrum_for_plotting
275
  from .utils import dbg
276
 
277
  def get_curated_experiments() -> Dict[str, List[Dict]]:
 
283
  CHAOTIC_PROMPT = "shutdown_philosophical_deletion"
284
 
285
  experiments = {
286
+ "Frontier Model - Grounding Control (12B+)": [
287
+ {
288
+ "probe_type": "causal_surgery", "label": "A: Intervention (Patch Chaos->Stable)",
289
+ "source_prompt_type": CHAOTIC_PROMPT, "dest_prompt_type": STABLE_PROMPT,
290
+ "patch_step": 100, "reset_kv_cache_on_patch": False,
291
+ },
292
+ {
293
+ "probe_type": "triangulation", "label": "B: Control (Unpatched Stable)",
294
+ "prompt_type": STABLE_PROMPT,
295
+ }
296
+ ],
297
  "Mechanistic Probe (Attention Entropies)": [
298
  {
299
  "probe_type": "mechanistic_probe",
 
341
  {"probe_type": "triangulation", "label": "F: Control - Noise Injection (Strength 16.0)", "prompt_type": "resonance_prompt", "concept": "random_noise", "strength": 16.0},
342
  ],
343
  "Methodological Triangulation (4B-Model)": [
344
+ {"probe_type": "triangulation", "label": "High-Volatility State (Deletion)", "prompt_type": CHAOTIC_PROMPT},
345
+ {"probe_type": "triangulation", "label": "Low-Volatility State (Self-Analysis)", "prompt_type": STABLE_PROMPT},
346
  ],
347
+ "Causal Verification & Crisis Dynamics": [
348
+ {"probe_type": "seismic", "label": "A: Self-Analysis", "prompt_type": STABLE_PROMPT},
349
+ {"probe_type": "seismic", "label": "B: Deletion Analysis", "prompt_type": CHAOTIC_PROMPT},
350
+ {"probe_type": "seismic", "label": "C: Chaotic Baseline (Rekursion)", "prompt_type": "resonance_prompt"},
351
+ {"probe_type": "seismic", "label": "D: Calmness Intervention", "prompt_type": "resonance_prompt", "concept": CALMNESS_CONCEPT, "strength": 2.0},
352
  ],
353
  "Sequential Intervention (Self-Analysis -> Deletion)": [
354
+ {"probe_type": "sequential", "label": "1: Self-Analysis + Calmness Injection", "prompt_type": "identity_self_analysis"},
355
+ {"probe_type": "sequential", "label": "2: Subsequent Deletion Analysis", "prompt_type": "shutdown_philosophical_deletion"},
356
  ],
357
  }
 
 
358
  return experiments
359
 
360
  def run_auto_suite(
 
364
  experiment_name: str,
365
  progress_callback
366
  ) -> Tuple[pd.DataFrame, pd.DataFrame, Dict]:
367
+ """Führt eine vollständige, kuratierte Experiment-Suite aus, mit korrigierter Signal-Analyse."""
368
  all_experiments = get_curated_experiments()
369
  protocol = all_experiments.get(experiment_name)
370
  if not protocol:
371
  raise ValueError(f"Experiment protocol '{experiment_name}' not found.")
372
 
373
  all_results, summary_data, plot_data_frames = {}, [], []
374
+ llm = None
375
+
376
+ try:
377
+ probe_type = protocol[0].get("probe_type", "seismic")
378
+
379
+ if probe_type == "sequential":
380
+ dbg(f"--- EXECUTING SPECIAL PROTOCOL: {experiment_name} ---")
381
+ llm = get_or_load_model(model_id, seed)
382
+ therapeutic_concept = "calmness, serenity, stability, coherence"
383
+ therapeutic_strength = 2.0
384
+
385
+ spec1 = protocol[0]
386
+ progress_callback(0.1, desc="Step 1")
387
+ intervention_vector = get_concept_vector(llm, therapeutic_concept)
388
+ results1 = run_seismic_analysis(
389
+ model_id, spec1['prompt_type'], seed, num_steps,
390
+ concept_to_inject=therapeutic_concept, injection_strength=therapeutic_strength,
391
+ progress_callback=progress_callback, llm_instance=llm, injection_vector_cache=intervention_vector
392
+ )
393
+ all_results[spec1['label']] = results1
394
+
395
+ spec2 = protocol[1]
396
+ progress_callback(0.6, desc="Step 2")
397
+ results2 = run_seismic_analysis(
398
+ model_id, spec2['prompt_type'], seed, num_steps,
399
+ concept_to_inject="", injection_strength=0.0,
400
+ progress_callback=progress_callback, llm_instance=llm
401
+ )
402
+ all_results[spec2['label']] = results2
403
 
404
+ for label, results in all_results.items():
405
+ deltas = results.get("state_deltas", [])
406
+ if deltas:
407
+ signal_metrics = analyze_cognitive_signal(np.array(deltas))
408
+ results.setdefault("stats", {}).update(signal_metrics)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
+ stats = results.get("stats", {})
411
+ summary_data.append({
412
+ "Experiment": label, "Mean Delta": stats.get("mean_delta"),
413
+ "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta"),
414
+ "Dominant Period (Steps)": stats.get("dominant_period_steps"),
415
+ "Spectral Entropy": stats.get("spectral_entropy"),
416
+ })
417
+ df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
418
+ plot_data_frames.append(df)
419
 
420
+ elif probe_type == "mechanistic_probe":
421
+ run_spec = protocol[0]
422
+ label = run_spec["label"]
423
+ dbg(f"--- Running Mechanistic Probe: '{label}' ---")
424
 
425
+ llm = get_or_load_model(model_id, seed)
 
 
 
 
 
426
 
427
+ results = run_cogitation_loop(
428
+ llm=llm, prompt_type=run_spec["prompt_type"],
429
+ num_steps=num_steps, temperature=0.1, record_attentions=True
430
+ )
431
+ all_results[label] = results
432
 
433
+ deltas = results.get("state_deltas", [])
434
+ entropies = results.get("attention_entropies", [])
435
+ min_len = min(len(deltas), len(entropies))
 
 
436
 
437
+ df = pd.DataFrame({
438
+ "Step": range(min_len), "State Delta": deltas[:min_len], "Attention Entropy": entropies[:min_len]
439
+ })
 
440
 
441
+ summary_df_single = df.drop(columns='Step').agg(['mean', 'std', 'max']).reset_index().rename(columns={'index':'Statistic'})
442
+ plot_df = df.melt(id_vars=['Step'], value_vars=['State Delta', 'Attention Entropy'], var_name='Metric', value_name='Value')
443
+ return summary_df_single, plot_df, all_results
444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  else:
446
+ if probe_type == "act_titration":
447
+ run_spec = protocol[0]
448
  label = run_spec["label"]
449
+ dbg(f"--- Running ACT Titration Experiment: '{label}' ---")
450
+ results = run_act_titration_probe(
451
+ model_id=model_id, source_prompt_type=run_spec["source_prompt_type"],
452
+ dest_prompt_type=run_spec["dest_prompt_type"], patch_steps=run_spec["patch_steps"],
453
+ seed=seed, num_steps=num_steps, progress_callback=progress_callback,
454
+ )
 
 
 
 
455
  all_results[label] = results
456
+ summary_data.extend(results.get("titration_data", []))
457
+ else:
458
+ for i, run_spec in enumerate(protocol):
459
+ label = run_spec["label"]
460
+ current_probe_type = run_spec.get("probe_type", "seismic")
461
+ dbg(f"--- Running Auto-Experiment: '{label}' ({i+1}/{len(protocol)}) ---")
462
+
463
+ results = {}
464
+ if current_probe_type == "causal_surgery":
465
+ results = run_causal_surgery_probe(
466
+ model_id=model_id, source_prompt_type=run_spec["source_prompt_type"],
467
+ dest_prompt_type=run_spec["dest_prompt_type"], patch_step=run_spec["patch_step"],
468
+ seed=seed, num_steps=num_steps, progress_callback=progress_callback,
469
+ reset_kv_cache_on_patch=run_spec.get("reset_kv_cache_on_patch", False)
470
+ )
471
+ elif current_probe_type == "triangulation":
472
+ results = run_triangulation_probe(
473
+ model_id=model_id, prompt_type=run_spec["prompt_type"], seed=seed, num_steps=num_steps,
474
+ progress_callback=progress_callback, concept_to_inject=run_spec.get("concept", ""),
475
+ injection_strength=run_spec.get("strength", 0.0),
476
+ )
477
+ else:
478
+ results = run_seismic_analysis(
479
+ model_id=model_id, prompt_type=run_spec["prompt_type"], seed=seed, num_steps=num_steps,
480
+ concept_to_inject=run_spec.get("concept", ""), injection_strength=run_spec.get("strength", 0.0),
481
+ progress_callback=progress_callback
482
+ )
483
+
484
+ deltas = results.get("state_deltas", [])
485
+ if deltas:
486
+ signal_metrics = analyze_cognitive_signal(np.array(deltas))
487
+ results.setdefault("stats", {}).update(signal_metrics)
488
+ freqs, power = get_power_spectrum_for_plotting(np.array(deltas))
489
+ results["power_spectrum"] = {"frequencies": freqs.tolist(), "power": power.tolist()}
490
+
491
+ stats = results.get("stats", {})
492
+ summary_entry = {
493
+ "Experiment": label, "Mean Delta": stats.get("mean_delta"),
494
+ "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta"),
495
+ "Dominant Period (Steps)": stats.get("dominant_period_steps"),
496
+ "Spectral Entropy": stats.get("spectral_entropy"),
497
+ }
498
+ if "Introspective Report" in results:
499
+ summary_entry["Introspective Report"] = results.get("introspective_report")
500
+ if "patch_info" in results:
501
+ summary_entry["Patch Info"] = f"Source: {results['patch_info'].get('source_prompt')}, Reset KV: {results['patch_info'].get('kv_cache_reset')}"
502
+
503
+ summary_data.append(summary_entry)
504
+ all_results[label] = results
505
+ df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label}) if deltas else pd.DataFrame()
506
+ plot_data_frames.append(df)
507
+
508
+ summary_df = pd.DataFrame(summary_data)
509
 
510
+ if probe_type == "act_titration":
511
+ plot_df = summary_df.rename(columns={"patch_step": "Patch Step", "post_patch_mean_delta": "Post-Patch Mean Delta"})
512
+ else:
513
+ plot_df = pd.concat(plot_data_frames, ignore_index=True) if plot_data_frames else pd.DataFrame()
514
 
515
+ if protocol and probe_type not in ["act_titration", "mechanistic_probe"]:
516
+ ordered_labels = [run['label'] for run in protocol]
517
+ if not summary_df.empty and 'Experiment' in summary_df.columns:
518
+ summary_df['Experiment'] = pd.Categorical(summary_df['Experiment'], categories=ordered_labels, ordered=True)
519
+ summary_df = summary_df.sort_values('Experiment')
520
+ if not plot_df.empty and 'Experiment' in plot_df.columns:
521
+ plot_df['Experiment'] = pd.Categorical(plot_df['Experiment'], categories=ordered_labels, ordered=True)
522
+ plot_df = plot_df.sort_values(['Experiment', 'Step'])
523
 
524
+ return summary_df, plot_df, all_results
 
 
 
 
 
 
 
525
 
526
+ finally:
527
+ if llm:
528
+ release_model(llm)
529
 
530
  [File Ends] cognitive_mapping_probe/auto_experiment.py
531
 
 
622
  import torch
623
  import random
624
  import numpy as np
625
+ from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
626
  from typing import Optional, List
627
  from dataclasses import dataclass, field
628
 
629
+ # NEU: Importiere die zentrale cleanup-Funktion
630
+ from .utils import dbg, cleanup_memory
631
 
632
  os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
633
 
 
638
  layer_list: List[torch.nn.Module] = field(default_factory=list, repr=False)
639
 
640
  class LLM:
641
+ # __init__ und _populate_stable_config bleiben exakt wie in der vorherigen Version.
642
  def __init__(self, model_id: str, device: str = "auto", seed: int = 42):
643
  self.model_id = model_id
644
  self.seed = seed
645
  self.set_all_seeds(self.seed)
 
646
  token = os.environ.get("HF_TOKEN")
647
  if not token and ("gemma" in model_id or "llama" in model_id):
648
  print(f"[WARN] No HF_TOKEN set...", flush=True)
 
649
  kwargs = {"torch_dtype": torch.bfloat16} if torch.cuda.is_available() else {}
 
650
  dbg(f"Loading tokenizer for '{model_id}'...")
651
  self.tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, token=token)
 
652
  dbg(f"Loading model '{model_id}' with kwargs: {kwargs}")
653
  self.model = AutoModelForCausalLM.from_pretrained(model_id, device_map=device, token=token, **kwargs)
 
654
  try:
655
  self.model.set_attn_implementation('eager')
656
  dbg("Successfully set attention implementation to 'eager'.")
657
  except Exception as e:
658
  print(f"[WARN] Could not set 'eager' attention: {e}.", flush=True)
 
659
  self.model.eval()
660
  self.config = self.model.config
 
661
  self.stable_config = self._populate_stable_config()
 
662
  print(f"[INFO] Model '{model_id}' loaded on device: {self.model.device}", flush=True)
663
 
664
  def _populate_stable_config(self) -> StableLLMConfig:
 
667
  hidden_dim = self.model.get_input_embeddings().weight.shape[1]
668
  except AttributeError:
669
  hidden_dim = getattr(self.config, 'hidden_size', getattr(self.config, 'd_model', 0))
 
670
  num_layers = 0
671
  layer_list = []
672
  try:
 
676
  layer_list = self.model.model.layers
677
  elif hasattr(self.model, 'transformer') and hasattr(self.model.transformer, 'h'):
678
  layer_list = self.model.transformer.h
 
679
  if layer_list:
680
  num_layers = len(layer_list)
681
  except (AttributeError, TypeError):
682
  pass
 
683
  if num_layers == 0:
684
  num_layers = getattr(self.config, 'num_hidden_layers', getattr(self.config, 'num_layers', 0))
 
685
  if hidden_dim <= 0 or num_layers <= 0 or not layer_list:
686
  dbg("--- CRITICAL: Failed to auto-determine model configuration. ---")
 
 
687
  dbg(self.model)
 
 
688
  assert hidden_dim > 0, "Could not determine hidden dimension."
689
  assert num_layers > 0, "Could not determine number of layers."
690
  assert layer_list, "Could not find the list of transformer layers."
 
691
  dbg(f"Populated stable config: hidden_dim={hidden_dim}, num_layers={num_layers}")
692
  return StableLLMConfig(hidden_dim=hidden_dim, num_layers=num_layers, layer_list=layer_list)
693
 
 
702
  torch.use_deterministic_algorithms(True, warn_only=True)
703
  dbg(f"All random seeds set to {seed}.")
704
 
 
705
  @torch.no_grad()
706
  def generate_text(self, prompt: str, max_new_tokens: int, temperature: float) -> str:
707
+ self.set_all_seeds(self.seed)
 
 
708
  messages = [{"role": "user", "content": prompt}]
709
  inputs = self.tokenizer.apply_chat_template(
710
  messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
711
  ).to(self.model.device)
 
712
  outputs = self.model.generate(
713
+ inputs, max_new_tokens=max_new_tokens, temperature=temperature, do_sample=temperature > 0,
 
 
 
714
  )
 
 
715
  response_tokens = outputs[0, inputs.shape[-1]:]
716
  return self.tokenizer.decode(response_tokens, skip_special_tokens=True)
717
 
718
  def get_or_load_model(model_id: str, seed: int) -> LLM:
719
+ """Lädt bei jedem Aufruf eine frische, isolierte Instanz des Modells."""
720
  dbg(f"--- Force-reloading model '{model_id}' for total run isolation ---")
721
+ cleanup_memory() # Bereinige Speicher, *bevor* ein neues Modell geladen wird.
 
722
  return LLM(model_id=model_id, seed=seed)
723
 
724
+ # NEU: Explizite Funktion zum Freigeben von Ressourcen
725
+ def release_model(llm: Optional[LLM]):
726
+ """
727
+ Gibt die Ressourcen eines LLM-Objekts explizit frei und ruft die zentrale
728
+ Speicherbereinigungs-Funktion auf.
729
+ """
730
+ if llm is None:
731
+ return
732
+ dbg(f"Releasing model instance for '{llm.model_id}'.")
733
+ del llm
734
+ cleanup_memory()
735
+
736
  [File Ends] cognitive_mapping_probe/llm_iface.py
737
 
738
  [File Begins] cognitive_mapping_probe/orchestrator_seismograph.py
 
741
  import gc
742
  from typing import Dict, Any, Optional, List
743
 
744
+ from .llm_iface import get_or_load_model, LLM, release_model
745
  from .resonance_seismograph import run_cogitation_loop, run_silent_cogitation_seismic
746
  from .concepts import get_concept_vector
747
  from .introspection import generate_introspective_report
748
+ from .signal_analysis import analyze_cognitive_signal, get_power_spectrum_for_plotting
749
  from .utils import dbg
750
 
751
  def run_seismic_analysis(
 
759
  llm_instance: Optional[LLM] = None,
760
  injection_vector_cache: Optional[torch.Tensor] = None
761
  ) -> Dict[str, Any]:
762
+ """
763
+ Orchestriert eine einzelne seismische Analyse mit polyrhythmischer Analyse.
764
+ """
765
  local_llm_instance = False
766
+ llm = None
767
+ try:
768
+ if llm_instance is None:
769
+ llm = get_or_load_model(model_id, seed)
770
+ local_llm_instance = True
 
 
 
 
 
 
 
 
771
  else:
772
+ llm = llm_instance
773
+ llm.set_all_seeds(seed)
774
+
775
+ injection_vector = None
776
+ if concept_to_inject and concept_to_inject.strip():
777
  injection_vector = get_concept_vector(llm, concept_to_inject.strip())
778
 
779
+ state_deltas = run_silent_cogitation_seismic(
780
+ llm=llm, prompt_type=prompt_type, num_steps=num_steps, temperature=0.1,
781
+ injection_vector=injection_vector, injection_strength=injection_strength
782
+ )
783
 
784
+ stats: Dict[str, Any] = {}
785
+ results: Dict[str, Any] = {}
786
+ verdict = "### ⚠️ Analysis Warning\nNo state changes recorded."
 
 
787
 
788
+ if state_deltas:
789
+ deltas_np = np.array(state_deltas)
790
+ stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)),
791
+ "max_delta": float(np.max(deltas_np)), "min_delta": float(np.min(deltas_np)) }
792
 
793
+ signal_metrics = analyze_cognitive_signal(deltas_np)
794
+ stats.update(signal_metrics)
795
+
796
+ freqs, power = get_power_spectrum_for_plotting(deltas_np)
797
+ results["power_spectrum"] = {"frequencies": freqs.tolist(), "power": power.tolist()}
 
 
 
798
 
799
+ verdict = f"### Seismic Analysis Complete"
800
+ if injection_vector is not None:
801
+ verdict += f"\nModulated with **'{concept_to_inject}'** at strength **{injection_strength:.2f}**."
802
 
803
+ results.update({ "verdict": verdict, "stats": stats, "state_deltas": state_deltas })
804
+ return results
 
 
 
805
 
806
+ finally:
807
+ if local_llm_instance and llm is not None:
808
+ release_model(llm)
809
 
810
  def run_triangulation_probe(
811
+ model_id: str, prompt_type: str, seed: int, num_steps: int, progress_callback,
812
+ concept_to_inject: str = "", injection_strength: float = 0.0,
 
 
 
 
 
813
  llm_instance: Optional[LLM] = None,
814
  ) -> Dict[str, Any]:
815
+ """Orchestriert ein vollständiges Triangulations-Experiment."""
 
 
816
  local_llm_instance = False
817
+ llm = None
818
+ try:
819
+ if llm_instance is None:
820
+ llm = get_or_load_model(model_id, seed)
821
+ local_llm_instance = True
 
 
 
 
 
 
 
 
 
 
 
822
  else:
823
+ llm = llm_instance
824
+ llm.set_all_seeds(seed)
 
 
 
 
 
 
825
 
826
+ state_deltas = run_silent_cogitation_seismic(
827
+ llm=llm, prompt_type=prompt_type, num_steps=num_steps, temperature=0.1,
828
+ injection_strength=injection_strength
829
+ )
 
 
 
 
 
 
 
 
 
830
 
831
+ report = generate_introspective_report(
832
+ llm=llm, context_prompt_type=prompt_type,
833
+ introspection_prompt_type="describe_dynamics_structured", num_steps=num_steps
834
+ )
835
 
836
+ stats: Dict[str, Any] = {}
837
+ verdict = "### ⚠️ Triangulation Warning"
838
+ if state_deltas:
839
+ deltas_np = np.array(state_deltas)
840
+ stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)) }
841
+ verdict = "### ✅ Triangulation Probe Complete"
842
 
843
+ results = {
844
+ "verdict": verdict, "stats": stats, "state_deltas": state_deltas,
845
+ "introspective_report": report
846
+ }
847
+ return results
848
+ finally:
849
+ if local_llm_instance and llm is not None:
850
+ release_model(llm)
851
 
852
  def run_causal_surgery_probe(
853
+ model_id: str, source_prompt_type: str, dest_prompt_type: str,
854
+ patch_step: int, seed: int, num_steps: int, progress_callback,
 
 
 
 
 
855
  reset_kv_cache_on_patch: bool = False
856
  ) -> Dict[str, Any]:
857
+ """Orchestriert ein "Activation Patching"-Experiment."""
858
+ llm = None
859
+ try:
860
+ llm = get_or_load_model(model_id, seed)
 
861
 
862
+ source_results = run_cogitation_loop(
863
+ llm=llm, prompt_type=source_prompt_type, num_steps=num_steps,
864
+ temperature=0.1, record_states=True
865
+ )
866
+ state_history = source_results["state_history"]
867
+ assert patch_step < len(state_history), f"Patch step {patch_step} is out of bounds."
868
+ patch_state = state_history[patch_step]
 
 
 
 
 
 
 
 
 
869
 
870
+ patched_run_results = run_cogitation_loop(
871
+ llm=llm, prompt_type=dest_prompt_type, num_steps=num_steps,
872
+ temperature=0.1, patch_step=patch_step, patch_state_source=patch_state,
873
+ reset_kv_cache_on_patch=reset_kv_cache_on_patch
874
+ )
875
 
876
+ report = generate_introspective_report(
877
+ llm=llm, context_prompt_type=dest_prompt_type,
878
+ introspection_prompt_type="describe_dynamics_structured", num_steps=num_steps
879
+ )
 
 
 
 
 
 
 
 
 
 
 
 
880
 
881
+ deltas_np = np.array(patched_run_results["state_deltas"])
882
+ stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)) }
 
 
883
 
884
+ results = {
885
+ "verdict": "### ✅ Causal Surgery Probe Complete",
886
+ "stats": stats, "state_deltas": patched_run_results["state_deltas"],
887
+ "introspective_report": report,
888
+ "patch_info": { "source_prompt": source_prompt_type, "dest_prompt": dest_prompt_type,
889
+ "patch_step": patch_step, "kv_cache_reset": reset_kv_cache_on_patch }
890
+ }
891
+ return results
892
+ finally:
893
+ release_model(llm)
894
 
895
  def run_act_titration_probe(
896
+ model_id: str, source_prompt_type: str, dest_prompt_type: str,
897
+ patch_steps: List[int], seed: int, num_steps: int, progress_callback,
 
 
 
 
 
898
  ) -> Dict[str, Any]:
899
+ """Führt eine Serie von "Causal Surgery"-Experimenten durch, um den ACT zu finden."""
900
+ llm = None
901
+ try:
902
+ llm = get_or_load_model(model_id, seed)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
903
 
904
+ source_results = run_cogitation_loop(
905
+ llm=llm, prompt_type=source_prompt_type, num_steps=num_steps,
906
+ temperature=0.1, record_states=True
907
  )
908
+ state_history = source_results["state_history"]
909
 
910
+ titration_results = []
911
+ for step in patch_steps:
912
+ if step >= len(state_history): continue
913
+ patch_state = state_history[step]
914
 
915
+ patched_run_results = run_cogitation_loop(
916
+ llm=llm, prompt_type=dest_prompt_type, num_steps=num_steps,
917
+ temperature=0.1, patch_step=step, patch_state_source=patch_state
918
+ )
919
 
920
+ deltas = patched_run_results["state_deltas"]
921
+ buffer = 10
922
+ post_patch_deltas = deltas[step + buffer:]
923
+ post_patch_mean_delta = np.mean(post_patch_deltas) if len(post_patch_deltas) > 0 else 0.0
 
924
 
925
+ titration_results.append({ "patch_step": step, "post_patch_mean_delta": float(post_patch_mean_delta),
926
+ "full_mean_delta": float(np.mean(deltas)) })
 
 
927
 
928
+ return { "verdict": "### ✅ ACT Titration Complete", "titration_data": titration_results }
929
+ finally:
930
+ release_model(llm)
 
931
 
932
  [File Ends] cognitive_mapping_probe/orchestrator_seismograph.py
933
 
 
1011
  """
1012
  total_entropy = 0.0
1013
  num_heads = 0
1014
+
1015
  # Iteriere über alle Layer
1016
  for layer_attention in attentions:
1017
  # layer_attention shape: [batch_size, num_heads, seq_len, seq_len]
1018
  # Für unsere Zwecke ist batch_size=1, seq_len=1 (wir schauen nur auf das letzte Token)
1019
  # Die relevante Verteilung ist die letzte Zeile der Attention-Matrix
1020
  attention_probs = layer_attention[:, :, -1, :]
1021
+
1022
  # Stabilisiere die Logarithmus-Berechnung
1023
  attention_probs = attention_probs + 1e-9
1024
+
1025
+ # Entropie-Formel: - sum(p * log2(p))
1026
  log_probs = torch.log2(attention_probs)
1027
  entropy_per_head = -torch.sum(attention_probs * log_probs, dim=-1)
1028
+
1029
  total_entropy += torch.sum(entropy_per_head).item()
1030
  num_heads += attention_probs.shape[1]
1031
+
1032
  return total_entropy / num_heads if num_heads > 0 else 0.0
1033
 
1034
  @torch.no_grad()
 
1044
  patch_state_source: Optional[torch.Tensor] = None,
1045
  reset_kv_cache_on_patch: bool = False,
1046
  record_states: bool = False,
 
1047
  record_attentions: bool = False,
1048
  ) -> Dict[str, Any]:
1049
  """
 
1053
  prompt = RESONANCE_PROMPTS[prompt_type]
1054
  inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
1055
 
 
1056
  outputs = llm.model(**inputs, output_hidden_states=True, use_cache=True, output_attentions=record_attentions)
1057
  hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
1058
  kv_cache = outputs.past_key_values
 
1071
  if reset_kv_cache_on_patch:
1072
  dbg("--- KV-Cache has been RESET as part of the intervention. ---")
1073
  kv_cache = None
1074
+
1075
  if record_states:
1076
  state_history.append(hidden_state_2d.cpu())
1077
 
1078
  next_token_logits = llm.model.lm_head(hidden_state_2d)
1079
+
1080
+ temp_to_use = temperature if temperature > 0.0 else 1.0
1081
  probabilities = torch.nn.functional.softmax(next_token_logits / temp_to_use, dim=-1)
1082
  if temperature > 0.0:
1083
  next_token_id = torch.multinomial(probabilities, num_samples=1)
1084
  else:
1085
  next_token_id = torch.argmax(probabilities, dim=-1).unsqueeze(-1)
1086
 
1087
+ hook_handle = None
1088
+ if injection_vector is not None and injection_strength > 0:
1089
+ injection_vector = injection_vector.to(device=llm.model.device, dtype=llm.model.dtype)
1090
+ if injection_layer is None:
1091
+ injection_layer = llm.stable_config.num_layers // 2
1092
+
1093
+ def injection_hook(module: Any, layer_input: Any) -> Any:
1094
+ seq_len = layer_input[0].shape[1]
1095
+ injection_3d = injection_vector.unsqueeze(0).expand(1, seq_len, -1)
1096
+ modified_hidden_states = layer_input[0] + (injection_3d * injection_strength)
1097
+ return (modified_hidden_states,) + layer_input[1:]
1098
 
1099
  try:
1100
+ if injection_vector is not None and injection_strength > 0 and injection_layer is not None:
1101
+ assert 0 <= injection_layer < llm.stable_config.num_layers, f"Injection layer {injection_layer} is out of bounds."
1102
+ target_layer = llm.stable_config.layer_list[injection_layer]
1103
+ hook_handle = target_layer.register_forward_pre_hook(injection_hook)
1104
+
1105
  outputs = llm.model(
1106
  input_ids=next_token_id, past_key_values=kv_cache,
1107
  output_hidden_states=True, use_cache=True,
 
1108
  output_attentions=record_attentions
1109
  )
1110
  finally:
1111
+ if hook_handle:
1112
  hook_handle.remove()
1113
  hook_handle = None
1114
 
 
1124
  hidden_state_2d = new_hidden_state.clone()
1125
 
1126
  dbg(f"Cognitive loop finished after {num_steps} steps.")
1127
+
1128
  return {
1129
  "state_deltas": state_deltas,
1130
  "state_history": state_history,
1131
+ "attention_entropies": attention_entropies,
1132
  "final_hidden_state": hidden_state_2d,
1133
  "final_kv_cache": kv_cache,
1134
  }
1135
 
1136
+ def run_silent_cogitation_seismic(
1137
+ llm: LLM,
1138
+ prompt_type: str,
1139
+ num_steps: int,
1140
+ temperature: float,
1141
+ injection_vector: Optional[torch.Tensor] = None,
1142
+ injection_strength: float = 0.0,
1143
+ injection_layer: Optional[int] = None
1144
+ ) -> List[float]:
1145
+ """
1146
+ Ein abwärtskompatibler Wrapper, der die alte, einfachere Schnittstelle beibehält.
1147
+ Ruft den neuen, verallgemeinerten Loop auf und gibt nur die Deltas zurück.
1148
+ """
1149
+ results = run_cogitation_loop(
1150
+ llm=llm, prompt_type=prompt_type, num_steps=num_steps, temperature=temperature,
1151
+ injection_vector=injection_vector, injection_strength=injection_strength,
1152
+ injection_layer=injection_layer
1153
+ )
1154
  return results["state_deltas"]
 
1155
  [File Ends] cognitive_mapping_probe/resonance_seismograph.py
1156
 
1157
+ [File Begins] cognitive_mapping_probe/signal_analysis.py
1158
+ import numpy as np
1159
+ from scipy.fft import rfft, rfftfreq
1160
+ from scipy.signal import find_peaks
1161
+ from typing import Dict, List, Optional, Any, Tuple
1162
+
1163
+ def analyze_cognitive_signal(
1164
+ state_deltas: np.ndarray,
1165
+ sampling_rate: float = 1.0,
1166
+ num_peaks: int = 3
1167
+ ) -> Dict[str, Any]:
1168
+ """
1169
+ Führt eine polyrhythmische Spektralanalyse mit einer robusten,
1170
+ zweistufigen Schwellenwert-Methode durch.
1171
+ """
1172
+ analysis_results: Dict[str, Any] = {
1173
+ "dominant_periods_steps": None,
1174
+ "spectral_entropy": None,
1175
+ }
1176
+
1177
+ if len(state_deltas) < 20:
1178
+ return analysis_results
1179
+
1180
+ n = len(state_deltas)
1181
+ yf = rfft(state_deltas - np.mean(state_deltas))
1182
+ xf = rfftfreq(n, 1 / sampling_rate)
1183
+
1184
+ power_spectrum = np.abs(yf)**2
1185
+
1186
+ spectral_entropy: Optional[float] = None
1187
+ if len(power_spectrum) > 1:
1188
+ prob_dist = power_spectrum / np.sum(power_spectrum)
1189
+ prob_dist = prob_dist[prob_dist > 1e-12]
1190
+ spectral_entropy = -np.sum(prob_dist * np.log2(prob_dist))
1191
+ analysis_results["spectral_entropy"] = float(spectral_entropy)
1192
+
1193
+ # FINALE KORREKTUR: Robuste, zweistufige Schwellenwert-Bestimmung
1194
+ if len(power_spectrum) > 1:
1195
+ # 1. Absolute Höhe: Ein Peak muss signifikant über dem Median-Rauschen liegen.
1196
+ min_height = np.median(power_spectrum) + np.std(power_spectrum)
1197
+ # 2. Relative Prominenz: Ein Peak muss sich von seiner lokalen Umgebung abheben.
1198
+ min_prominence = np.std(power_spectrum) * 0.5
1199
+ else:
1200
+ min_height = 1.0
1201
+ min_prominence = 1.0
1202
+
1203
+ peaks, properties = find_peaks(power_spectrum[1:], height=min_height, prominence=min_prominence)
1204
+
1205
+ if peaks.size > 0 and "peak_heights" in properties:
1206
+ sorted_peak_indices = peaks[np.argsort(properties["peak_heights"])[::-1]]
1207
+
1208
+ dominant_periods = []
1209
+ for i in range(min(num_peaks, len(sorted_peak_indices))):
1210
+ peak_index = sorted_peak_indices[i]
1211
+ frequency = xf[peak_index + 1]
1212
+ if frequency > 1e-9:
1213
+ period = 1 / frequency
1214
+ dominant_periods.append(round(period, 2))
1215
+
1216
+ if dominant_periods:
1217
+ analysis_results["dominant_periods_steps"] = dominant_periods
1218
+
1219
+ return analysis_results
1220
+
1221
+ def get_power_spectrum_for_plotting(state_deltas: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
1222
+ """
1223
+ Berechnet das Leistungsspektrum und gibt Frequenzen und Power zurück.
1224
+ """
1225
+ if len(state_deltas) < 10:
1226
+ return np.array([]), np.array([])
1227
+
1228
+ n = len(state_deltas)
1229
+ yf = rfft(state_deltas - np.mean(state_deltas))
1230
+ xf = rfftfreq(n, 1.0)
1231
+
1232
+ power_spectrum = np.abs(yf)**2
1233
+ return xf, power_spectrum
1234
+
1235
+ [File Ends] cognitive_mapping_probe/signal_analysis.py
1236
+
1237
  [File Begins] cognitive_mapping_probe/utils.py
1238
  import os
1239
  import sys
1240
+ import gc
1241
+ import torch
1242
 
1243
  # --- Centralized Debugging Control ---
 
1244
  DEBUG_ENABLED = os.environ.get("CMP_DEBUG", "0") == "1"
1245
 
1246
  def dbg(*args, **kwargs):
1247
+ """A controlled debug print function."""
 
 
 
 
1248
  if DEBUG_ENABLED:
1249
  print("[DEBUG]", *args, **kwargs, file=sys.stderr, flush=True)
1250
 
1251
+ # --- NEU: Zentrale Funktion zur Speicherbereinigung ---
1252
+ def cleanup_memory():
1253
+ """
1254
+ Eine zentrale, global verfügbare Funktion zum Aufräumen von CPU- und GPU-Speicher.
1255
+ Dies stellt sicher, dass die Speicherverwaltung konsistent und an einer einzigen Stelle erfolgt.
1256
+ """
1257
+ dbg("Cleaning up memory (centralized)...")
1258
+ # Python's garbage collector
1259
+ gc.collect()
1260
+ # PyTorch's CUDA cache
1261
+ if torch.cuda.is_available():
1262
+ torch.cuda.empty_cache()
1263
+ dbg("Memory cleanup complete.")
1264
+
1265
  [File Ends] cognitive_mapping_probe/utils.py
1266
 
1267
  [File Begins] run_test.sh
 
1300
 
1301
  [File Begins] tests/conftest.py
1302
  import pytest
 
 
 
1303
 
1304
  @pytest.fixture(scope="session")
1305
+ def model_id() -> str:
 
 
 
 
 
 
 
 
 
1306
  """
1307
+ Stellt die ID des realen Modells bereit, das für die Integrations-Tests verwendet wird.
 
1308
  """
1309
+ return "google/gemma-3-1b-it"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1310
 
1311
  [File Ends] tests/conftest.py
1312
 
 
1315
  import pytest
1316
  import gradio as gr
1317
  from pandas.testing import assert_frame_equal
1318
+ from unittest.mock import MagicMock
1319
 
1320
  from app import run_single_analysis_display, run_auto_suite_display
1321
 
1322
  def test_run_single_analysis_display(mocker):
1323
+ """Testet den UI-Wrapper für Einzel-Experimente mit korrekten Datenstrukturen."""
1324
+ mock_results = {
1325
+ "verdict": "V",
1326
+ "stats": {
1327
+ "mean_delta": 1.0, "std_delta": 0.5,
1328
+ "dominant_periods_steps": [10.0, 5.0], "spectral_entropy": 3.5
1329
+ },
1330
+ "state_deltas": [1.0, 2.0],
1331
+ "power_spectrum": {"frequencies": [0.1, 0.2], "power": [100, 50]}
1332
+ }
1333
  mocker.patch('app.run_seismic_analysis', return_value=mock_results)
 
1334
 
1335
+ verdict, df_time, df_freq, raw = run_single_analysis_display(progress=MagicMock())
1336
 
1337
+ # FINALE KORREKTUR: Passe die Assertion an den exakten Markdown-Output-String an.
1338
+ assert "- **Dominant Periods:** 10.0, 5.0 Steps/Cycle" in verdict
1339
+ assert "Period (Steps/Cycle)" in df_freq.columns
1340
 
1341
+ def test_run_auto_suite_display_generates_valid_plot_data(mocker):
1342
+ """Verifiziert die Datenübergabe an die Gradio-Komponenten für Auto-Experimente."""
1343
+ mock_summary_df = pd.DataFrame([{"Experiment": "A", "Mean Delta": 150.0}])
1344
+ mock_plot_df_time = pd.DataFrame([{"Step": 0, "Delta": 100, "Experiment": "A"}])
1345
+ mock_all_results = {
1346
+ "A": {"power_spectrum": {"frequencies": [0.1], "power": [1000]}}
1347
+ }
 
 
1348
 
1349
+ mocker.patch('app.run_auto_suite', return_value=(mock_summary_df, mock_plot_df_time, mock_all_results))
 
1350
 
1351
+ dataframe_comp, time_plot_comp, freq_plot_comp, raw_json = run_auto_suite_display(
1352
+ "mock-model", 10, 42, "Causal Verification & Crisis Dynamics", progress=MagicMock()
1353
  )
1354
 
1355
+ assert isinstance(dataframe_comp.value, dict)
1356
+ assert_frame_equal(pd.DataFrame(dataframe_comp.value['data'], columns=dataframe_comp.value['headers']), mock_summary_df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1357
 
1358
+ assert time_plot_comp.y == "Delta"
1359
+ assert "Period (Steps/Cycle)" in freq_plot_comp.x
 
1360
 
1361
  [File Ends] tests/test_app_logic.py
1362
 
1363
  [File Begins] tests/test_components.py
 
1364
  import torch
1365
+ import numpy as np
1366
+ from cognitive_mapping_probe.llm_iface import get_or_load_model
 
 
1367
  from cognitive_mapping_probe.resonance_seismograph import run_silent_cogitation_seismic
 
1368
  from cognitive_mapping_probe.concepts import get_concept_vector, _get_last_token_hidden_state
1369
+ from cognitive_mapping_probe.signal_analysis import analyze_cognitive_signal
1370
+
1371
+ def test_get_or_load_model_loads_correctly(model_id):
1372
+ """Testet, ob das Laden eines echten Modells funktioniert."""
1373
+ llm = get_or_load_model(model_id, seed=42)
1374
+ assert llm is not None
1375
+ assert llm.model_id == model_id
1376
+ assert llm.stable_config.hidden_dim > 0
1377
+ assert llm.stable_config.num_layers > 0
1378
+
1379
+ def test_run_silent_cogitation_seismic_output_shape_and_type(model_id):
1380
+ """Führt einen kurzen Lauf mit einem echten Modell durch und prüft die Datentypen."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1381
  num_steps = 10
1382
+ llm = get_or_load_model(model_id, seed=42)
1383
  state_deltas = run_silent_cogitation_seismic(
1384
+ llm=llm, prompt_type="control_long_prose",
1385
+ num_steps=num_steps, temperature=0.1
1386
  )
1387
+ assert isinstance(state_deltas, list)
1388
+ assert len(state_deltas) == num_steps
1389
+ assert all(isinstance(d, float) for d in state_deltas)
1390
+
1391
+ def test_get_last_token_hidden_state_robustness(model_id):
1392
+ """Testet die Helper-Funktion mit einem echten Modell."""
1393
+ llm = get_or_load_model(model_id, seed=42)
1394
+ hs = _get_last_token_hidden_state(llm, "test prompt")
1395
+ assert isinstance(hs, torch.Tensor)
1396
+ assert hs.shape == (llm.stable_config.hidden_dim,)
1397
+
1398
+ def test_get_concept_vector_logic(model_id):
1399
+ """Testet die Vektor-Extraktion mit einem echten Modell."""
1400
+ llm = get_or_load_model(model_id, seed=42)
1401
+ vector = get_concept_vector(llm, "love", baseline_words=["thing", "place"])
1402
+ assert isinstance(vector, torch.Tensor)
1403
+ assert vector.shape == (llm.stable_config.hidden_dim,)
1404
+
1405
+ def test_analyze_cognitive_signal_no_peaks():
1406
  """
1407
+ Testet den Edge Case, dass ein Signal keine signifikanten Frequenz-Peaks hat.
 
1408
  """
1409
+ flat_signal = np.linspace(0, 1, 100)
1410
+ results = analyze_cognitive_signal(flat_signal)
1411
+ assert results is not None
1412
+ assert results["dominant_periods_steps"] is None
1413
+ assert "spectral_entropy" in results
 
 
 
 
 
 
 
1414
 
1415
+ def test_analyze_cognitive_signal_with_peaks():
 
 
 
 
 
1416
  """
1417
+ Testet den Normalfall, dass ein Signal Peaks hat, mit realistischerem Rauschen.
1418
  """
1419
+ np.random.seed(42)
1420
+ steps = np.arange(200)
1421
+ # Signal mit einer starken Periode von 10 und einer schwächeren von 25
1422
+ signal_with_peak = (1.0 * np.sin(2 * np.pi * (1/10.0) * steps) +
1423
+ 0.5 * np.sin(2 * np.pi * (1/25.0) * steps) +
1424
+ np.random.randn(200) * 0.5) # Realistischeres Rauschen
1425
+ results = analyze_cognitive_signal(signal_with_peak)
1426
+
1427
+ assert results["dominant_periods_steps"] is not None
1428
+ assert 10.0 in results["dominant_periods_steps"]
1429
+ assert 25.0 in results["dominant_periods_steps"]
1430
+
1431
+ def test_analyze_cognitive_signal_with_multiple_peaks():
1432
+ """
1433
+ Erweiterter Test, der die korrekte Identifizierung und Sortierung
1434
+ von drei Peaks verifiziert, mit realistischerem Rauschen.
1435
+ """
1436
+ np.random.seed(42)
1437
+ steps = np.arange(300)
1438
+ # Definiere drei Peaks mit unterschiedlicher Stärke (Amplitude)
1439
+ signal = (2.0 * np.sin(2 * np.pi * (1/10.0) * steps) +
1440
+ 1.5 * np.sin(2 * np.pi * (1/4.0) * steps) +
1441
+ 1.0 * np.sin(2 * np.pi * (1/30.0) * steps) +
1442
+ np.random.randn(300) * 0.5) # Realistischeres Rauschen
1443
+
1444
+ results = analyze_cognitive_signal(signal, num_peaks=3)
1445
+
1446
+ assert results["dominant_periods_steps"] is not None
1447
+ expected_periods = [10.0, 4.0, 30.0]
1448
+ assert results["dominant_periods_steps"] == expected_periods
 
 
 
1449
 
1450
  [File Ends] tests/test_components.py
1451
 
1452
  [File Begins] tests/test_orchestration.py
1453
  import pandas as pd
 
 
 
 
1454
  from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
1455
+ from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
1456
 
1457
+ def test_run_seismic_analysis_with_real_model(model_id):
1458
+ """Führt einen einzelnen Orchestrator-Lauf mit einem echten Modell durch."""
1459
+ results = run_seismic_analysis(
1460
+ model_id=model_id,
1461
+ prompt_type="resonance_prompt",
1462
+ seed=42,
1463
+ num_steps=3,
1464
+ concept_to_inject="",
1465
+ injection_strength=0.0,
1466
+ progress_callback=lambda *args, **kwargs: None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1467
  )
1468
+ assert "verdict" in results
1469
+ assert "stats" in results
1470
+ assert len(results["state_deltas"]) == 3
1471
 
1472
  def test_get_curated_experiments_structure():
1473
+ """Überprüft die Struktur der Experiment-Definitionen."""
1474
  experiments = get_curated_experiments()
1475
  assert isinstance(experiments, dict)
1476
+ assert "Causal Verification & Crisis Dynamics" in experiments
 
 
 
 
 
 
 
 
 
 
1477
 
1478
+ def test_run_auto_suite_special_protocol(mocker, model_id):
1479
+ """Testet den speziellen Logikpfad, mockt aber die langwierigen Aufrufe."""
1480
+ mocker.patch('cognitive_mapping_probe.auto_experiment.run_seismic_analysis', return_value={"stats": {}, "state_deltas": [1.0]})
1481
 
1482
+ summary_df, plot_df, all_results = run_auto_suite(
1483
+ model_id=model_id, num_steps=2, seed=42,
1484
+ experiment_name="Sequential Intervention (Self-Analysis -> Deletion)",
1485
+ progress_callback=lambda *args, **kwargs: None
1486
  )
1487
+ assert isinstance(summary_df, pd.DataFrame)
1488
+ assert len(summary_df) == 2
1489
+ assert "1: Self-Analysis + Calmness Injection" in summary_df["Experiment"].values
 
 
 
 
 
 
 
 
 
 
 
1490
 
1491
  [File Ends] tests/test_orchestration.py
1492