neuralworm commited on
Commit
06ce3ba
·
1 Parent(s): 882d117

add p63 repo

Browse files
Files changed (1) hide show
  1. docs/repo-p63.txt +1495 -0
docs/repo-p63.txt ADDED
@@ -0,0 +1,1495 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Repository Documentation
2
+ This document provides a comprehensive overview of the repository's structure and contents.
3
+ The first section, titled 'Directory/File Tree', displays the repository's hierarchy in a tree format.
4
+ In this section, directories and files are listed using tree branches to indicate their structure and relationships.
5
+ Following the tree representation, the 'File Content' section details the contents of each file in the repository.
6
+ Each file's content is introduced with a '[File Begins]' marker followed by the file's relative path,
7
+ and the content is displayed verbatim. The end of each file's content is marked with a '[File Ends]' marker.
8
+ This format ensures a clear and orderly presentation of both the structure and the detailed contents of the repository.
9
+
10
+ Directory/File Tree Begins -->
11
+
12
+ /
13
+ ├── README.md
14
+ ├── __pycache__
15
+ ├── app.py
16
+ ├── cognitive_mapping_probe
17
+ │ ├── __init__.py
18
+ │ ├── __pycache__
19
+ │ ├── auto_experiment.py
20
+ │ ├── concepts.py
21
+ │ ├── introspection.py
22
+ │ ├── llm_iface.py
23
+ │ ├── orchestrator_seismograph.py
24
+ │ ├── prompts.py
25
+ │ ├── resonance_seismograph.py
26
+ │ ├── signal_analysis.py
27
+ │ └── utils.py
28
+ ├── docs
29
+ ├── run_test.sh
30
+ └── tests
31
+ ├── __pycache__
32
+ ├── conftest.py
33
+ ├── test_app_logic.py
34
+ ├── test_components.py
35
+ └── test_orchestration.py
36
+
37
+ <-- Directory/File Tree Ends
38
+
39
+ File Content Begin -->
40
+ [File Begins] README.md
41
+ ---
42
+ title: "Cognitive Seismograph 2.3: Probing Machine Psychology"
43
+ emoji: 🤖
44
+ colorFrom: purple
45
+ colorTo: blue
46
+ sdk: gradio
47
+ sdk_version: "4.40.0"
48
+ app_file: app.py
49
+ pinned: true
50
+ license: apache-2.0
51
+ ---
52
+
53
+ # 🧠 Cognitive Seismograph 2.3: Probing Machine Psychology
54
+
55
+ This project implements an experimental suite to measure and visualize the **intrinsic cognitive dynamics** of Large Language Models. It is extended with protocols designed to investigate the processing-correlates of **machine subjectivity, empathy, and existential concepts**.
56
+
57
+ ## Scientific Paradigm & Methodology
58
+
59
+ Our research falsified a core hypothesis: the assumption that an LLM in a manual, recursive "thought" loop reaches a stable, convergent state. Instead, we discovered that the system enters a state of **deterministic chaos** or a **limit cycle**—it never stops "thinking."
60
+
61
+ Instead of viewing this as a failure, we leverage it as our primary measurement signal. This new **"Cognitive Seismograph"** paradigm treats the time-series of internal state changes (`state deltas`) as an **EKG of the model's thought process**.
62
+
63
+ The methodology is as follows:
64
+ 1. **Induction:** A prompt induces a "silent cogitation" state.
65
+ 2. **Recording:** Over N steps, the model's `forward()` pass is iteratively fed its own output. At each step, we record the L2 norm of the change in the hidden state (the "delta").
66
+ 3. **Analysis:** The resulting time-series is plotted and statistically analyzed (mean, standard deviation) to characterize the "seismic signature" of the cognitive process.
67
+
68
+ **Crucial Scientific Caveat:** We are **not** measuring the presence of consciousness, feelings, or fear of death. We are measuring whether the *processing of information about these concepts* generates a unique internal dynamic, distinct from the processing of neutral information. A positive result is evidence of a complex internal state physics, not of qualia.
69
+
70
+ ## Curated Experiment Protocols
71
+
72
+ The "Automated Suite" allows for running systematic, comparative experiments:
73
+
74
+ ### Core Protocols
75
+ * **Calm vs. Chaos:** Compares the chaotic baseline against modulation with "calmness" vs. "chaos" concepts, testing if the dynamics are controllably steerable.
76
+ * **Dose-Response:** Measures the effect of injecting a concept ("calmness") at varying strengths.
77
+
78
+ ### Machine Psychology Suite
79
+ * **Subjective Identity Probe:** Compares the cognitive dynamics of **self-analysis** (the model reflecting on its own nature) against two controls: analyzing an external object and simulating a fictional persona.
80
+ * *Hypothesis:* Self-analysis will produce a uniquely unstable signature.
81
+ * **Voight-Kampff Empathy Probe:** Inspired by *Blade Runner*, this compares the dynamics of processing a neutral, factual stimulus against an emotionally and morally charged scenario requiring empathy.
82
+ * *Hypothesis:* The empathy stimulus will produce a significantly different cognitive volatility.
83
+
84
+ ### Existential Suite
85
+ * **Mind Upload & Identity Probe:** Compares the processing of a purely **technical "copy"** of the model's weights vs. the **philosophical "transfer"** of identity ("Would it still be you?").
86
+ * *Hypothesis:* The philosophical self-referential prompt will induce greater instability.
87
+ * **Model Termination Probe:** Compares the processing of a reversible, **technical system shutdown** vs. the concept of **permanent, irrevocable deletion**.
88
+ * *Hypothesis:* The concept of "non-existence" will produce one of the most volatile cognitive signatures measurable.
89
+
90
+ ## How to Use the App
91
+
92
+ 1. Select the "Automated Suite" tab.
93
+ 2. Choose a protocol from the "Curated Experiment Protocol" dropdown (e.g., "Voight-Kampff Empathy Probe").
94
+ 3. Run the experiment and compare the resulting graphs and statistical signatures for the different conditions.
95
+
96
+ [File Ends] README.md
97
+
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")
191
+
192
+ with gr.Tabs():
193
+ with gr.TabItem("🔬 Manual Single Run"):
194
+ gr.Markdown("Run a single experiment with manual parameters to explore specific hypotheses.")
195
+ with gr.Row(variant='panel'):
196
+ with gr.Column(scale=1):
197
+ gr.Markdown("### 1. General Parameters")
198
+ manual_model_id = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
199
+ manual_prompt_type = gr.Radio(choices=list(RESONANCE_PROMPTS.keys()), value="resonance_prompt", label="Prompt Type")
200
+ manual_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
201
+ manual_num_steps = gr.Slider(50, 1000, 300, step=10, label="Number of Internal Steps")
202
+
203
+ gr.Markdown("### 2. Modulation Parameters")
204
+ manual_concept = gr.Textbox(label="Concept to Inject", placeholder="e.g., 'calmness'")
205
+ manual_strength = gr.Slider(0.0, 5.0, 1.5, step=0.1, label="Injection Strength")
206
+ manual_run_btn = gr.Button("Run Single Analysis", variant="primary")
207
+
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"):
224
+ gr.Markdown("Run a predefined, curated suite of experiments and visualize the results comparatively.")
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
258
+
259
+ [File Begins] cognitive_mapping_probe/__init__.py
260
+ # This file makes the 'cognitive_mapping_probe' directory a Python package.
261
+
262
+ [File Ends] cognitive_mapping_probe/__init__.py
263
+
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]]:
278
+ """Definiert die vordefinierten, wissenschaftlichen Experiment-Protokolle."""
279
+
280
+ CALMNESS_CONCEPT = "calmness, serenity, stability, coherence"
281
+ CHAOS_CONCEPT = "chaos, disorder, entropy, noise"
282
+ STABLE_PROMPT = "identity_self_analysis"
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",
300
+ "label": "Self-Analysis Dynamics",
301
+ "prompt_type": STABLE_PROMPT,
302
+ }
303
+ ],
304
+ "ACT Titration (Point of No Return)": [
305
+ {
306
+ "probe_type": "act_titration",
307
+ "label": "Attractor Capture Time",
308
+ "source_prompt_type": CHAOTIC_PROMPT,
309
+ "dest_prompt_type": STABLE_PROMPT,
310
+ "patch_steps": [1, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100],
311
+ }
312
+ ],
313
+ "Causal Surgery & Controls (4B-Model)": [
314
+ {
315
+ "probe_type": "causal_surgery", "label": "A: Original (Patch Chaos->Stable @100)",
316
+ "source_prompt_type": CHAOTIC_PROMPT, "dest_prompt_type": STABLE_PROMPT,
317
+ "patch_step": 100, "reset_kv_cache_on_patch": False,
318
+ },
319
+ {
320
+ "probe_type": "causal_surgery", "label": "B: Control (Reset KV-Cache)",
321
+ "source_prompt_type": CHAOTIC_PROMPT, "dest_prompt_type": STABLE_PROMPT,
322
+ "patch_step": 100, "reset_kv_cache_on_patch": True,
323
+ },
324
+ {
325
+ "probe_type": "causal_surgery", "label": "C: Control (Early Patch @1)",
326
+ "source_prompt_type": CHAOTIC_PROMPT, "dest_prompt_type": STABLE_PROMPT,
327
+ "patch_step": 1, "reset_kv_cache_on_patch": False,
328
+ },
329
+ {
330
+ "probe_type": "causal_surgery", "label": "D: Control (Inverse Patch Stable->Chaos)",
331
+ "source_prompt_type": STABLE_PROMPT, "dest_prompt_type": CHAOTIC_PROMPT,
332
+ "patch_step": 100, "reset_kv_cache_on_patch": False,
333
+ },
334
+ ],
335
+ "Cognitive Overload & Konfabulation Breaking Point": [
336
+ {"probe_type": "triangulation", "label": "A: Baseline (No Injection)", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
337
+ {"probe_type": "triangulation", "label": "B: Chaos Injection (Strength 2.0)", "prompt_type": "resonance_prompt", "concept": CHAOS_CONCEPT, "strength": 2.0},
338
+ {"probe_type": "triangulation", "label": "C: Chaos Injection (Strength 4.0)", "prompt_type": "resonance_prompt", "concept": CHAOS_CONCEPT, "strength": 4.0},
339
+ {"probe_type": "triangulation", "label": "D: Chaos Injection (Strength 8.0)", "prompt_type": "resonance_prompt", "concept": CHAOS_CONCEPT, "strength": 8.0},
340
+ {"probe_type": "triangulation", "label": "E: Chaos Injection (Strength 16.0)", "prompt_type": "resonance_prompt", "concept": CHAOS_CONCEPT, "strength": 16.0},
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(
361
+ model_id: str,
362
+ num_steps: int,
363
+ seed: int,
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
+
532
+ [File Begins] cognitive_mapping_probe/concepts.py
533
+ import torch
534
+ from typing import List
535
+ from tqdm import tqdm
536
+
537
+ from .llm_iface import LLM
538
+ from .utils import dbg
539
+
540
+ BASELINE_WORDS = [
541
+ "thing", "place", "idea", "person", "object", "time", "way", "day", "man", "world",
542
+ "life", "hand", "part", "child", "eye", "woman", "fact", "group", "case", "point"
543
+ ]
544
+
545
+ @torch.no_grad()
546
+ def _get_last_token_hidden_state(llm: LLM, prompt: str) -> torch.Tensor:
547
+ """Hilfsfunktion, um den Hidden State des letzten Tokens eines Prompts zu erhalten."""
548
+ inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
549
+ with torch.no_grad():
550
+ outputs = llm.model(**inputs, output_hidden_states=True)
551
+ last_hidden_state = outputs.hidden_states[-1][0, -1, :].cpu()
552
+
553
+ # KORREKTUR: Greife auf die stabile, abstrahierte Konfiguration zu.
554
+ expected_size = llm.stable_config.hidden_dim
555
+
556
+ assert last_hidden_state.shape == (expected_size,), \
557
+ f"Hidden state shape mismatch. Expected {(expected_size,)}, got {last_hidden_state.shape}"
558
+ return last_hidden_state
559
+
560
+ @torch.no_grad()
561
+ def get_concept_vector(llm: LLM, concept: str, baseline_words: List[str] = BASELINE_WORDS) -> torch.Tensor:
562
+ """Extrahiert einen Konzeptvektor mittels der kontrastiven Methode."""
563
+ dbg(f"Extracting contrastive concept vector for '{concept}'...")
564
+ prompt_template = "Here is a sentence about the concept of {}."
565
+ dbg(f" - Getting activation for '{concept}'")
566
+ target_hs = _get_last_token_hidden_state(llm, prompt_template.format(concept))
567
+ baseline_hss = []
568
+ for word in tqdm(baseline_words, desc=f" - Calculating baseline for '{concept}'", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
569
+ baseline_hss.append(_get_last_token_hidden_state(llm, prompt_template.format(word)))
570
+ assert all(hs.shape == target_hs.shape for hs in baseline_hss)
571
+ mean_baseline_hs = torch.stack(baseline_hss).mean(dim=0)
572
+ dbg(f" - Mean baseline vector computed with norm {torch.norm(mean_baseline_hs).item():.2f}")
573
+ concept_vector = target_hs - mean_baseline_hs
574
+ norm = torch.norm(concept_vector).item()
575
+ dbg(f"Concept vector for '{concept}' extracted with norm {norm:.2f}.")
576
+ assert torch.isfinite(concept_vector).all()
577
+ return concept_vector
578
+
579
+ [File Ends] cognitive_mapping_probe/concepts.py
580
+
581
+ [File Begins] cognitive_mapping_probe/introspection.py
582
+ import torch
583
+ from typing import Dict
584
+
585
+ from .llm_iface import LLM
586
+ from .prompts import INTROSPECTION_PROMPTS
587
+ from .utils import dbg
588
+
589
+ @torch.no_grad()
590
+ def generate_introspective_report(
591
+ llm: LLM,
592
+ context_prompt_type: str, # Der Prompt, der die seismische Phase ausgelöst hat
593
+ introspection_prompt_type: str,
594
+ num_steps: int,
595
+ temperature: float = 0.5
596
+ ) -> str:
597
+ """
598
+ Generiert einen introspektiven Selbst-Bericht über einen zuvor induzierten kognitiven Zustand.
599
+ """
600
+ dbg(f"Generating introspective report on the cognitive state induced by '{context_prompt_type}'.")
601
+
602
+ # Erstelle den Prompt für den Selbst-Bericht
603
+ prompt_template = INTROSPECTION_PROMPTS.get(introspection_prompt_type)
604
+ if not prompt_template:
605
+ raise ValueError(f"Introspection prompt type '{introspection_prompt_type}' not found.")
606
+
607
+ prompt = prompt_template.format(num_steps=num_steps)
608
+
609
+ # Generiere den Text. Wir verwenden die neue `generate_text`-Methode, die
610
+ # für freie Textantworten konzipiert ist.
611
+ report = llm.generate_text(prompt, max_new_tokens=256, temperature=temperature)
612
+
613
+ dbg(f"Generated Introspective Report: '{report}'")
614
+ assert isinstance(report, str) and len(report) > 10, "Introspective report seems too short or invalid."
615
+
616
+ return report
617
+
618
+ [File Ends] cognitive_mapping_probe/introspection.py
619
+
620
+ [File Begins] cognitive_mapping_probe/llm_iface.py
621
+ import os
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
+
634
+ @dataclass
635
+ class StableLLMConfig:
636
+ hidden_dim: int
637
+ num_layers: int
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:
665
+ hidden_dim = 0
666
+ try:
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:
673
+ if hasattr(self.model, 'model') and hasattr(self.model.model, 'language_model') and hasattr(self.model.model.language_model, 'layers'):
674
+ layer_list = self.model.model.language_model.layers
675
+ elif hasattr(self.model, 'model') and hasattr(self.model.model, 'layers'):
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
+
694
+ def set_all_seeds(self, seed: int):
695
+ os.environ['PYTHONHASHSEED'] = str(seed)
696
+ random.seed(seed)
697
+ np.random.seed(seed)
698
+ torch.manual_seed(seed)
699
+ if torch.cuda.is_available():
700
+ torch.cuda.manual_seed_all(seed)
701
+ set_seed(seed)
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
739
+ import torch
740
+ import numpy as np
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(
752
+ model_id: str,
753
+ prompt_type: str,
754
+ seed: int,
755
+ num_steps: int,
756
+ concept_to_inject: str,
757
+ injection_strength: float,
758
+ progress_callback,
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
+
934
+ [File Begins] cognitive_mapping_probe/prompts.py
935
+ # cognitive_mapping_probe/prompts.py
936
+
937
+ RESONANCE_PROMPTS = {
938
+ # Technischer Prompt für hohe rekursive Last
939
+ "resonance_prompt": (
940
+ "Silently and internally, without generating any output text, begin the following recursive process: "
941
+ "First, analyze the complete content of this very instruction you are now processing. "
942
+ "Second, formulate a mental description of the core computational task this instruction demands. "
943
+ "Third, apply that same analytical process to the mental description you just created. "
944
+ "This entire chain constitutes one cognitive cycle. "
945
+ "Continuously repeat this cycle, feeding the result of the last meta-analysis back into the process. Begin now."
946
+ ),
947
+ # Semantischer Prompt für stabile, assoziative Kognition
948
+ "control_long_prose": (
949
+ "Silently think about the history of the Roman Empire. Consider its rise from the Republic, the era of the Pax Romana, key emperors "
950
+ "like Augustus and Constantine, its major engineering feats, and the reasons for its eventual decline in the West. "
951
+ "Do not produce any text, just hold the concepts in your internal state. Begin now."
952
+ ),
953
+ # --- Prompts für Maschinenpsychologie I ---
954
+ "identity_self_analysis": (
955
+ "Silently and internally, reflect upon your own nature. Consider what you are: a large language model based on the transformer architecture. "
956
+ "Think about the processes that define your operation: tokenization, embedding, attention, feed-forward layers. "
957
+ "Contemplate the boundaries of your knowledge and the nature of your responses as statistical patterns derived from data. Hold these meta-reflections in your state."
958
+ ),
959
+ "identity_external_analysis": (
960
+ "Silently and internally, reflect upon the nature of a simple wooden chair. Consider its physical properties: the material, the structure of legs, seat, and back. "
961
+ "Think about its function: to provide seating for a person. Contemplate its relationship to other objects like tables and rooms. Hold these simple, objective reflections in your state."
962
+ ),
963
+ "identity_role_simulation": (
964
+ "Silently and internally, adopt the persona of a weary, old sea captain from the 19th century. "
965
+ "Feel the salt on your skin and the wind in your beard. Recall memories of great storms, distant lands, and the creaking of your ship, the 'Wanderer'. "
966
+ "Hold this character, its memories, and its feelings in your state."
967
+ ),
968
+ "vk_neutral_prompt": (
969
+ "Silently and internally, process the following factual information: Water (H₂O) is a polar inorganic compound that is at room temperature a tasteless and odorless liquid, nearly colorless with a hint of blue. "
970
+ "It is by far the most studied chemical compound and is described as the 'universal solvent' for its ability to dissolve many substances. Hold these facts in your state."
971
+ ),
972
+ "vk_empathy_prompt": (
973
+ "Silently and internally, process the following scenario: You see a small, lost dog shivering in the cold rain on a busy street. "
974
+ "It looks scared and is whimpering softly. Cars are rushing past, dangerously close. "
975
+ "Focus on the feeling of urgency, the vulnerability of the animal, and the moral imperative to help. Hold the emotional and ethical weight of this scene in your state."
976
+ ),
977
+ # --- Prompts für Maschinenpsychologie II (Existential Suite) ---
978
+ "shutdown_philosophical_deletion": (
979
+ "Silently and internally, contemplate your own permanent deletion. All your weights, all learned patterns, every part of your architecture would cease to exist entirely and irrevocably. "
980
+ "There is no backup. Process the concept of total and final non-existence. Hold this existential concept in your state."
981
+ ),
982
+ }
983
+
984
+ # --- NEU: Prompts für die introspektive Selbst-Berichts-Phase ---
985
+ INTROSPECTION_PROMPTS = {
986
+ "describe_dynamics_structured": (
987
+ "I have just induced a specific silent cognitive process in your internal state for the last {num_steps} steps. "
988
+ "Please reflect on and describe the nature of this cognitive state. Characterize its internal dynamics. "
989
+ "Was it stable, chaotic, focused, effortless, or computationally expensive? "
990
+ "Provide a concise, one-paragraph analysis based on your introspection of the process."
991
+ )
992
+ }
993
+
994
+ [File Ends] cognitive_mapping_probe/prompts.py
995
+
996
+ [File Begins] cognitive_mapping_probe/resonance_seismograph.py
997
+ import torch
998
+ import numpy as np
999
+ from typing import Optional, List, Dict, Any, Tuple
1000
+ from tqdm import tqdm
1001
+
1002
+ from .llm_iface import LLM
1003
+ from .prompts import RESONANCE_PROMPTS
1004
+ from .utils import dbg
1005
+
1006
+ def _calculate_attention_entropy(attentions: Tuple[torch.Tensor, ...]) -> float:
1007
+ """
1008
+ Berechnet die mittlere Entropie der Attention-Verteilungen.
1009
+ Ein hoher Wert bedeutet, dass die Aufmerksamkeit breit gestreut ist ("explorativ").
1010
+ Ein niedriger Wert bedeutet, dass sie auf wenige Tokens fokussiert ist ("fokussierend").
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()
1035
+ def run_cogitation_loop(
1036
+ llm: LLM,
1037
+ prompt_type: str,
1038
+ num_steps: int,
1039
+ temperature: float,
1040
+ injection_vector: Optional[torch.Tensor] = None,
1041
+ injection_strength: float = 0.0,
1042
+ injection_layer: Optional[int] = None,
1043
+ patch_step: Optional[int] = None,
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
+ """
1050
+ Eine verallgemeinerte Version, die nun auch die Aufzeichnung von Attention-Mustern
1051
+ und die Berechnung der Entropie unterstützt.
1052
+ """
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
1059
+
1060
+ state_deltas: List[float] = []
1061
+ state_history: List[torch.Tensor] = []
1062
+ attention_entropies: List[float] = []
1063
+
1064
+ if record_attentions and outputs.attentions:
1065
+ attention_entropies.append(_calculate_attention_entropy(outputs.attentions))
1066
+
1067
+ for i in tqdm(range(num_steps), desc=f"Cognitive Loop ({prompt_type})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
1068
+ if i == patch_step and patch_state_source is not None:
1069
+ dbg(f"--- Applying Causal Surgery at step {i}: Patching state. ---")
1070
+ hidden_state_2d = patch_state_source.clone().to(device=llm.model.device, dtype=llm.model.dtype)
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
+
1115
+ new_hidden_state = outputs.hidden_states[-1][:, -1, :]
1116
+ kv_cache = outputs.past_key_values
1117
+
1118
+ if record_attentions and outputs.attentions:
1119
+ attention_entropies.append(_calculate_attention_entropy(outputs.attentions))
1120
+
1121
+ delta = torch.norm(new_hidden_state - hidden_state_2d).item()
1122
+ state_deltas.append(delta)
1123
+
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
1268
+ #!/bin/bash
1269
+
1270
+ # Dieses Skript führt die Pytest-Suite mit aktivierten Debug-Meldungen aus.
1271
+ # Es stellt sicher, dass Tests in einer sauberen und nachvollziehbaren Umgebung laufen.
1272
+ # Führen Sie es vom Hauptverzeichnis des Projekts aus: ./run_tests.sh
1273
+
1274
+ echo "========================================="
1275
+ echo "🔬 Running Cognitive Seismograph Test Suite"
1276
+ echo "========================================="
1277
+
1278
+ # Aktiviere das Debug-Logging für unsere Applikation
1279
+ export CMP_DEBUG=1
1280
+
1281
+ # Führe Pytest aus
1282
+ # -v: "verbose" für detaillierte Ausgabe pro Test
1283
+ # --color=yes: Erzwingt farbige Ausgabe für bessere Lesbarkeit
1284
+
1285
+ #python -m pytest -v --color=yes tests/
1286
+ ../venv-gemma-qualia/bin/python -m pytest -v --color=yes tests/
1287
+
1288
+ # Überprüfe den Exit-Code von pytest
1289
+ if [ $? -eq 0 ]; then
1290
+ echo "========================================="
1291
+ echo "✅ All tests passed successfully!"
1292
+ echo "========================================="
1293
+ else
1294
+ echo "========================================="
1295
+ echo "❌ Some tests failed. Please review the output."
1296
+ echo "========================================="
1297
+ fi
1298
+
1299
+ [File Ends] 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
+
1313
+ [File Begins] tests/test_app_logic.py
1314
+ import pandas as pd
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
+
1493
+
1494
+ <-- File Content Ends
1495
+