neuralworm commited on
Commit
7dac8c1
·
1 Parent(s): d407fda
cognitive_mapping_probe/concepts.py CHANGED
@@ -18,18 +18,8 @@ def _get_last_token_hidden_state(llm: LLM, prompt: str) -> torch.Tensor:
18
  outputs = llm.model(**inputs, output_hidden_states=True)
19
  last_hidden_state = outputs.hidden_states[-1][0, -1, :].cpu()
20
 
21
- # KORREKTUR: Anstatt auf `llm.config.hidden_size` zuzugreifen, was fragil ist,
22
- # leiten wir die erwartete Größe direkt vom Modell selbst ab. Dies ist robust
23
- # gegenüber API-Änderungen in `transformers`.
24
- try:
25
- # Versuche, die Größe über die Einbettungsschicht zu erhalten, was am stabilsten ist.
26
- expected_size = llm.model.get_input_embeddings().weight.shape[1]
27
- except AttributeError:
28
- # Fallback, falls die Methode nicht existiert, auf den wahrscheinlichen Namen.
29
- # Gemma3Config hat 'hidden_size', aber andere könnten es anders nennen.
30
- expected_size = getattr(llm.config, 'hidden_size', getattr(llm.config, 'd_model', 0))
31
- assert expected_size > 0, "Could not determine hidden size from model config."
32
-
33
 
34
  assert last_hidden_state.shape == (expected_size,), \
35
  f"Hidden state shape mismatch. Expected {(expected_size,)}, got {last_hidden_state.shape}"
@@ -44,7 +34,7 @@ def get_concept_vector(llm: LLM, concept: str, baseline_words: List[str] = BASEL
44
  target_hs = _get_last_token_hidden_state(llm, prompt_template.format(concept))
45
  baseline_hss = []
46
  for word in tqdm(baseline_words, desc=f" - Calculating baseline for '{concept}'", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
47
- baseline_hss.append(_get_last_token_hidden_state(llm, prompt_template.format(word))) # Korrigiert: Verwende 'word', nicht 'concept'
48
  assert all(hs.shape == target_hs.shape for hs in baseline_hss)
49
  mean_baseline_hs = torch.stack(baseline_hss).mean(dim=0)
50
  dbg(f" - Mean baseline vector computed with norm {torch.norm(mean_baseline_hs).item():.2f}")
 
18
  outputs = llm.model(**inputs, output_hidden_states=True)
19
  last_hidden_state = outputs.hidden_states[-1][0, -1, :].cpu()
20
 
21
+ # KORREKTUR: Greife auf die stabile, abstrahierte Konfiguration zu.
22
+ expected_size = llm.stable_config.hidden_dim
 
 
 
 
 
 
 
 
 
 
23
 
24
  assert last_hidden_state.shape == (expected_size,), \
25
  f"Hidden state shape mismatch. Expected {(expected_size,)}, got {last_hidden_state.shape}"
 
34
  target_hs = _get_last_token_hidden_state(llm, prompt_template.format(concept))
35
  baseline_hss = []
36
  for word in tqdm(baseline_words, desc=f" - Calculating baseline for '{concept}'", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
37
+ baseline_hss.append(_get_last_token_hidden_state(llm, prompt_template.format(word)))
38
  assert all(hs.shape == target_hs.shape for hs in baseline_hss)
39
  mean_baseline_hs = torch.stack(baseline_hss).mean(dim=0)
40
  dbg(f" - Mean baseline vector computed with norm {torch.norm(mean_baseline_hs).item():.2f}")
cognitive_mapping_probe/llm_iface.py CHANGED
@@ -4,12 +4,22 @@ import random
4
  import numpy as np
5
  from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
6
  from typing import Optional
 
7
 
8
  from .utils import dbg
9
 
10
  # Ensure deterministic CuBLAS operations for reproducibility on GPU
11
  os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
12
 
 
 
 
 
 
 
 
 
 
13
  class LLM:
14
  """
15
  Eine robuste, bereinigte Schnittstelle zum Laden und Interagieren mit einem Sprachmodell.
@@ -39,9 +49,33 @@ class LLM:
39
  print(f"[WARN] Could not set 'eager' attention: {e}.", flush=True)
40
 
41
  self.model.eval()
42
- self.config = self.model.config
 
 
 
 
43
  print(f"[INFO] Model '{model_id}' loaded on device: {self.model.device}", flush=True)
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def set_all_seeds(self, seed: int):
46
  """Setzt alle relevanten Seeds für maximale Reproduzierbarkeit."""
47
  os.environ['PYTHONHASHSEED'] = str(seed)
 
4
  import numpy as np
5
  from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
6
  from typing import Optional
7
+ from dataclasses import dataclass
8
 
9
  from .utils import dbg
10
 
11
  # Ensure deterministic CuBLAS operations for reproducibility on GPU
12
  os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
13
 
14
+ @dataclass
15
+ class StableLLMConfig:
16
+ """
17
+ Eine stabile, interne Abstraktionsschicht für Modell-Konfigurationen.
18
+ Macht unseren Code unabhängig von den sich ändernden Attributnamen in `transformers`.
19
+ """
20
+ hidden_dim: int
21
+ num_layers: int
22
+
23
  class LLM:
24
  """
25
  Eine robuste, bereinigte Schnittstelle zum Laden und Interagieren mit einem Sprachmodell.
 
49
  print(f"[WARN] Could not set 'eager' attention: {e}.", flush=True)
50
 
51
  self.model.eval()
52
+ self.config = self.model.config # Behalte den Zugriff auf die Originalkonfiguration
53
+
54
+ # --- NEU: Befülle die stabile Konfigurations-Abstraktion ---
55
+ self.stable_config = self._populate_stable_config()
56
+
57
  print(f"[INFO] Model '{model_id}' loaded on device: {self.model.device}", flush=True)
58
 
59
+ def _populate_stable_config(self) -> StableLLMConfig:
60
+ """
61
+ Liest die volatile `transformers`-Konfiguration aus und befüllt unsere stabile Datenklasse.
62
+ """
63
+ # Robuste Methode für hidden_dim
64
+ try:
65
+ hidden_dim = self.model.get_input_embeddings().weight.shape[1]
66
+ except AttributeError:
67
+ hidden_dim = getattr(self.config, 'hidden_size', getattr(self.config, 'd_model', 0))
68
+
69
+ # Robuste Methode für num_layers
70
+ num_layers = getattr(self.config, 'num_hidden_layers', getattr(self.config, 'num_layers', 0))
71
+
72
+ # Assertions zur Sicherstellung der wissenschaftlichen Validität
73
+ assert hidden_dim > 0, "Could not determine hidden dimension from model config."
74
+ assert num_layers > 0, "Could not determine number of layers from model config."
75
+
76
+ dbg(f"Populated stable config: hidden_dim={hidden_dim}, num_layers={num_layers}")
77
+ return StableLLMConfig(hidden_dim=hidden_dim, num_layers=num_layers)
78
+
79
  def set_all_seeds(self, seed: int):
80
  """Setzt alle relevanten Seeds für maximale Reproduzierbarkeit."""
81
  os.environ['PYTHONHASHSEED'] = str(seed)
cognitive_mapping_probe/resonance_seismograph.py CHANGED
@@ -36,25 +36,38 @@ def run_silent_cogitation_seismic(
36
  if injection_vector is not None and injection_strength > 0:
37
  injection_vector = injection_vector.to(device=llm.model.device, dtype=llm.model.dtype)
38
  if injection_layer is None:
39
- injection_layer = llm.config.num_hidden_layers // 2
 
40
 
41
  dbg(f"Injection enabled: Layer {injection_layer}, Strength {injection_strength:.2f}")
42
 
43
  def injection_hook(module, layer_input):
44
  # Der Hook operiert auf dem Input, der bereits 3D ist [batch, seq_len, hidden_dim]
45
- injection_3d = injection_vector.unsqueeze(0).unsqueeze(0)
 
 
 
46
  modified_hidden_states = layer_input[0] + (injection_3d * injection_strength)
47
  return (modified_hidden_states,) + layer_input[1:]
48
 
49
  for i in tqdm(range(num_steps), desc=f"Recording Dynamics (Temp {temperature:.2f})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
50
  next_token_logits = llm.model.lm_head(hidden_state_2d)
51
 
52
- probabilities = torch.nn.functional.softmax(next_token_logits / temperature, dim=-1)
53
- next_token_id = torch.multinomial(probabilities, num_samples=1)
 
 
 
 
 
 
 
54
 
55
  try:
56
  # Aktiviere den Hook vor dem forward-Pass
57
  if injection_vector is not None and injection_strength > 0:
 
 
58
  target_layer = llm.model.model.layers[injection_layer]
59
  hook_handle = target_layer.register_forward_pre_hook(injection_hook)
60
 
 
36
  if injection_vector is not None and injection_strength > 0:
37
  injection_vector = injection_vector.to(device=llm.model.device, dtype=llm.model.dtype)
38
  if injection_layer is None:
39
+ # KORREKTUR: Greife auf die stabile, abstrahierte Konfiguration zu.
40
+ injection_layer = llm.stable_config.num_layers // 2
41
 
42
  dbg(f"Injection enabled: Layer {injection_layer}, Strength {injection_strength:.2f}")
43
 
44
  def injection_hook(module, layer_input):
45
  # Der Hook operiert auf dem Input, der bereits 3D ist [batch, seq_len, hidden_dim]
46
+ # Stelle sicher, dass der Vektor korrekt auf die Sequenzlänge des Inputs gebroadcastet wird.
47
+ # Normalerweise ist die seq_len hier 1.
48
+ seq_len = layer_input[0].shape[1]
49
+ injection_3d = injection_vector.unsqueeze(0).expand(1, seq_len, -1)
50
  modified_hidden_states = layer_input[0] + (injection_3d * injection_strength)
51
  return (modified_hidden_states,) + layer_input[1:]
52
 
53
  for i in tqdm(range(num_steps), desc=f"Recording Dynamics (Temp {temperature:.2f})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
54
  next_token_logits = llm.model.lm_head(hidden_state_2d)
55
 
56
+ # Verwende eine minimale Temperatur, um deterministisches Verhalten bei temp=0 zu gewährleisten
57
+ temp_to_use = temperature if temperature > 0.0 else 1.0
58
+ probabilities = torch.nn.functional.softmax(next_token_logits / temp_to_use, dim=-1)
59
+
60
+ if temperature > 0.0:
61
+ next_token_id = torch.multinomial(probabilities, num_samples=1)
62
+ else:
63
+ next_token_id = torch.argmax(probabilities, dim=-1).unsqueeze(-1)
64
+
65
 
66
  try:
67
  # Aktiviere den Hook vor dem forward-Pass
68
  if injection_vector is not None and injection_strength > 0:
69
+ # Stelle sicher, dass der Layer-Index gültig ist.
70
+ assert 0 <= injection_layer < llm.stable_config.num_layers, f"Injection layer {injection_layer} is out of bounds."
71
  target_layer = llm.model.model.layers[injection_layer]
72
  hook_handle = target_layer.register_forward_pre_hook(injection_hook)
73
 
tests/conftest.py CHANGED
@@ -1,11 +1,12 @@
1
  import pytest
2
  import torch
3
  from types import SimpleNamespace
4
- from cognitive_mapping_probe.llm_iface import LLM
5
 
6
  @pytest.fixture(scope="session")
7
  def mock_llm_config():
8
  """Stellt eine minimale, Schein-Konfiguration für das LLM bereit."""
 
9
  return SimpleNamespace(
10
  hidden_size=128,
11
  num_hidden_layers=2,
@@ -16,16 +17,14 @@ def mock_llm_config():
16
  def mock_llm(mocker, mock_llm_config):
17
  """
18
  Erstellt einen robusten "Mock-LLM" für Unit-Tests.
19
- FINAL KORRIGIERT: Die Patch-Anweisungen sind nun korrekt auf die tatsächlichen
20
- Import-Pfade in den zu testenden Modulen ausgerichtet.
21
  """
22
  mock_tokenizer = mocker.MagicMock()
23
  mock_tokenizer.eos_token_id = 1
24
  mock_tokenizer.decode.return_value = "mocked text"
25
 
26
- # Definiere eine stabile Mock-Funktion für die Eingabe-Embeddings
27
  mock_embedding_layer = mocker.MagicMock()
28
- mock_embedding_layer.weight.shape = (32000, mock_llm_config.hidden_size) # (vocab_size, hidden_size)
29
 
30
  def mock_model_forward(*args, **kwargs):
31
  batch_size = 1
@@ -42,13 +41,12 @@ def mock_llm(mocker, mock_llm_config):
42
  }
43
  return SimpleNamespace(**mock_outputs)
44
 
45
- llm_instance = LLM.__new__(LLM) # Erzeuge Instanz ohne __init__ aufzurufen
46
 
47
  llm_instance.model = mocker.MagicMock(side_effect=mock_model_forward)
48
  llm_instance.model.config = mock_llm_config
49
  llm_instance.model.device = 'cpu'
50
  llm_instance.model.dtype = torch.float32
51
- # Füge die gemockte Embedding-Funktion hinzu, um den Test in `concepts.py` zu bestehen
52
  llm_instance.model.get_input_embeddings.return_value = mock_embedding_layer
53
 
54
  mock_layer = mocker.MagicMock()
@@ -57,18 +55,22 @@ def mock_llm(mocker, mock_llm_config):
57
  llm_instance.model.lm_head = mocker.MagicMock(return_value=torch.randn(1, 32000))
58
 
59
  llm_instance.tokenizer = mock_tokenizer
60
- llm_instance.config = mock_llm_config # Wichtig, da `concepts.py` darauf zugreift
61
  llm_instance.seed = 42
62
  llm_instance.set_all_seeds = mocker.MagicMock()
63
 
 
 
 
 
 
 
64
  # Patch an allen Stellen, an denen das Modell tatsächlich geladen wird.
65
- # Dies stellt sicher, dass kein Test versucht, ein echtes Modell herunterzuladen.
66
  mocker.patch('cognitive_mapping_probe.llm_iface.get_or_load_model', return_value=llm_instance)
67
  mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
68
  mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=llm_instance)
69
 
70
- # Mocke `get_concept_vector`, um zu verhindern, dass es im Orchestrator-Test ausgeführt wird,
71
- # da wir es in `test_components.py` separat testen.
72
- mocker.patch('cognitive_mapping_probe.concepts.get_concept_vector', return_value=torch.randn(mock_llm_config.hidden_size))
73
 
74
  return llm_instance
 
1
  import pytest
2
  import torch
3
  from types import SimpleNamespace
4
+ from cognitive_mapping_probe.llm_iface import LLM, StableLLMConfig
5
 
6
  @pytest.fixture(scope="session")
7
  def mock_llm_config():
8
  """Stellt eine minimale, Schein-Konfiguration für das LLM bereit."""
9
+ # Diese Fixture repräsentiert die *volatile* transformers-Konfiguration
10
  return SimpleNamespace(
11
  hidden_size=128,
12
  num_hidden_layers=2,
 
17
  def mock_llm(mocker, mock_llm_config):
18
  """
19
  Erstellt einen robusten "Mock-LLM" für Unit-Tests.
20
+ FINAL KORRIGIERT: Simuliert nun auch die `stable_config`-Abstraktionsschicht.
 
21
  """
22
  mock_tokenizer = mocker.MagicMock()
23
  mock_tokenizer.eos_token_id = 1
24
  mock_tokenizer.decode.return_value = "mocked text"
25
 
 
26
  mock_embedding_layer = mocker.MagicMock()
27
+ mock_embedding_layer.weight.shape = (32000, mock_llm_config.hidden_size)
28
 
29
  def mock_model_forward(*args, **kwargs):
30
  batch_size = 1
 
41
  }
42
  return SimpleNamespace(**mock_outputs)
43
 
44
+ llm_instance = LLM.__new__(LLM)
45
 
46
  llm_instance.model = mocker.MagicMock(side_effect=mock_model_forward)
47
  llm_instance.model.config = mock_llm_config
48
  llm_instance.model.device = 'cpu'
49
  llm_instance.model.dtype = torch.float32
 
50
  llm_instance.model.get_input_embeddings.return_value = mock_embedding_layer
51
 
52
  mock_layer = mocker.MagicMock()
 
55
  llm_instance.model.lm_head = mocker.MagicMock(return_value=torch.randn(1, 32000))
56
 
57
  llm_instance.tokenizer = mock_tokenizer
58
+ llm_instance.config = mock_llm_config # Die originale, volatile config
59
  llm_instance.seed = 42
60
  llm_instance.set_all_seeds = mocker.MagicMock()
61
 
62
+ # KORREKTUR: Erzeuge die stabile Konfiguration, die die Tests nun erwarten.
63
+ llm_instance.stable_config = StableLLMConfig(
64
+ hidden_dim=mock_llm_config.hidden_size,
65
+ num_layers=mock_llm_config.num_hidden_layers
66
+ )
67
+
68
  # Patch an allen Stellen, an denen das Modell tatsächlich geladen wird.
 
69
  mocker.patch('cognitive_mapping_probe.llm_iface.get_or_load_model', return_value=llm_instance)
70
  mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
71
  mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=llm_instance)
72
 
73
+ # Mocke `get_concept_vector`, um zu verhindern, dass es im Orchestrator-Test ausgeführt wird.
74
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector', return_value=torch.randn(mock_llm_config.hidden_size))
 
75
 
76
  return llm_instance