dawidtang commited on
Commit
7221535
·
verified ·
1 Parent(s): f62dd1d

Accept loi extractor config alias

Browse files
Files changed (1) hide show
  1. modeling_vjepa2_fmri_encoder.py +60 -22
modeling_vjepa2_fmri_encoder.py CHANGED
@@ -38,8 +38,6 @@ class HookedFeatureExtractor:
38
  self.layer_names = list(layer_names)
39
  self.ret_type = ret_type
40
  self.spatial_pool = int(spatial_pool)
41
- self.outputs: dict[str, torch.Tensor] = {}
42
- self.hooks = []
43
 
44
  @staticmethod
45
  def _get_layer(model: nn.Module, layer_name: str) -> nn.Module:
@@ -50,24 +48,33 @@ class HookedFeatureExtractor:
50
  raise TypeError(f"{layer_name} did not resolve to a torch module")
51
  return layer
52
 
 
 
 
 
 
 
 
 
 
 
53
  def __call__(self, model: nn.Module, videos: torch.Tensor, **model_kwargs) -> list[torch.Tensor]:
54
- self.outputs = {}
55
- self.hooks = [
56
  self._get_layer(model, name).register_forward_hook(
57
- lambda _module, _inputs, output, name=name: self.outputs.__setitem__(name, output)
58
  )
59
  for name in self.layer_names
60
  ]
61
  try:
62
  model(videos, **model_kwargs)
63
  finally:
64
- for hook in self.hooks:
65
  hook.remove()
66
- self.hooks = []
67
- return [self._process_feature(self.outputs[name]) for name in self.layer_names]
68
 
69
  def _process_feature(self, feature: torch.Tensor) -> torch.Tensor:
70
- batch, _thw, channels = feature.shape
71
  feature = feature.reshape(batch, -1, 14, 14, channels).permute(0, 1, 4, 2, 3)
72
  if self.spatial_pool > 1:
73
  batch, frames, channels, height, width = feature.shape
@@ -90,8 +97,8 @@ class HookedFeatureExtractor:
90
  raise ValueError(f"Unsupported ret_type: {self.ret_type}")
91
 
92
 
93
- class VJEPA2Backbone(nn.Module):
94
- def __init__(self, size: str, image_size: int, normalize_input: bool) -> None:
95
  super().__init__()
96
  self.image_size = int(image_size)
97
  self.normalize_input = bool(normalize_input)
@@ -102,8 +109,13 @@ class VJEPA2Backbone(nn.Module):
102
  "huge": "vjepa2_vit_huge",
103
  "giant": "vjepa2_vit_giant",
104
  }[size]
105
- backbone = torch.hub.load("facebookresearch/vjepa2", hub_name, pretrained=True)
106
- self.backbone = backbone[0] if isinstance(backbone, (list, tuple)) else backbone
 
 
 
 
 
107
 
108
  def forward(self, videos: torch.Tensor, normalize: bool | None = None) -> torch.Tensor:
109
  if videos.ndim != 5:
@@ -132,6 +144,14 @@ class VJEPA2Backbone(nn.Module):
132
  return self.backbone(videos.permute(0, 2, 1, 3, 4))
133
 
134
 
 
 
 
 
 
 
 
 
135
  class VJEPA2FMRIEncoderModel(PreTrainedModel):
136
  config_class = VJEPA2FMRIEncoderConfig
137
  base_model_prefix = "vjepa2_fmri_encoder"
@@ -142,7 +162,7 @@ class VJEPA2FMRIEncoderModel(PreTrainedModel):
142
  self.decoders = nn.ModuleList()
143
  self.register_buffer("decoding_units", torch.empty(0, dtype=torch.long))
144
  self.extractor: HookedFeatureExtractor | None = None
145
- self.vjepa: VJEPA2Backbone | None = None
146
 
147
  @classmethod
148
  def from_pretrained(
@@ -176,7 +196,7 @@ class VJEPA2FMRIEncoderModel(PreTrainedModel):
176
  local_files_only=local_files_only,
177
  )
178
 
179
- checkpoint_path = cls._resolve_checkpoint_path(
180
  pretrained_model_name_or_path,
181
  filename=config.checkpoint_filename,
182
  revision=revision,
@@ -197,22 +217,31 @@ class VJEPA2FMRIEncoderModel(PreTrainedModel):
197
  vjepa_size = config.vjepa_size if vjepa_size is None else vjepa_size
198
  normalize_input = config.normalize_input if normalize_input is None else bool(normalize_input)
199
  if load_vjepa:
 
 
 
 
 
 
 
 
200
  extractor_config = checkpoint["extractor_config"]
201
  model.extractor = HookedFeatureExtractor(
202
- layer_names=extractor_config["layer_names"],
203
  ret_type=extractor_config.get("ret_type", "chw"),
204
  spatial_pool=extractor_config.get("spatial_pool", 14),
205
  )
206
- model.vjepa = VJEPA2Backbone(
207
  size=vjepa_size,
208
  image_size=config.image_size,
209
  normalize_input=normalize_input,
 
210
  )
211
  model.eval()
212
  return model
213
 
214
  @staticmethod
215
- def _resolve_checkpoint_path(
216
  pretrained_model_name_or_path: str | os.PathLike[str],
217
  *,
218
  filename: str,
@@ -223,10 +252,10 @@ class VJEPA2FMRIEncoderModel(PreTrainedModel):
223
  ) -> str:
224
  path = Path(pretrained_model_name_or_path)
225
  if path.exists():
226
- checkpoint_path = path / filename if path.is_dir() else path
227
- if not checkpoint_path.exists():
228
- raise FileNotFoundError(f"Missing checkpoint file: {checkpoint_path}")
229
- return str(checkpoint_path)
230
 
231
  from huggingface_hub import hf_hub_download
232
 
@@ -240,6 +269,15 @@ class VJEPA2FMRIEncoderModel(PreTrainedModel):
240
  local_files_only=local_files_only,
241
  )
242
 
 
 
 
 
 
 
 
 
 
243
  def forward_features(self, features: list[torch.Tensor]) -> torch.Tensor:
244
  if len(features) != len(self.decoders):
245
  raise ValueError(f"Expected {len(self.decoders)} feature tensors, got {len(features)}")
 
38
  self.layer_names = list(layer_names)
39
  self.ret_type = ret_type
40
  self.spatial_pool = int(spatial_pool)
 
 
41
 
42
  @staticmethod
43
  def _get_layer(model: nn.Module, layer_name: str) -> nn.Module:
 
48
  raise TypeError(f"{layer_name} did not resolve to a torch module")
49
  return layer
50
 
51
+ @staticmethod
52
+ def _unwrap_output(output: Any) -> torch.Tensor:
53
+ if isinstance(output, (list, tuple)):
54
+ if len(output) == 0:
55
+ raise ValueError("Received an empty feature tuple.")
56
+ output = output[0]
57
+ if not torch.is_tensor(output):
58
+ raise TypeError(f"Expected tensor feature output, got {type(output)!r}")
59
+ return output
60
+
61
  def __call__(self, model: nn.Module, videos: torch.Tensor, **model_kwargs) -> list[torch.Tensor]:
62
+ outputs: dict[str, torch.Tensor] = {}
63
+ hooks = [
64
  self._get_layer(model, name).register_forward_hook(
65
+ lambda _module, _inputs, output, name=name: outputs.__setitem__(name, self._unwrap_output(output))
66
  )
67
  for name in self.layer_names
68
  ]
69
  try:
70
  model(videos, **model_kwargs)
71
  finally:
72
+ for hook in hooks:
73
  hook.remove()
74
+ return [self._process_feature(outputs[name]) for name in self.layer_names]
 
75
 
76
  def _process_feature(self, feature: torch.Tensor) -> torch.Tensor:
77
+ batch, tokens, channels = feature.shape
78
  feature = feature.reshape(batch, -1, 14, 14, channels).permute(0, 1, 4, 2, 3)
79
  if self.spatial_pool > 1:
80
  batch, frames, channels, height, width = feature.shape
 
97
  raise ValueError(f"Unsupported ret_type: {self.ret_type}")
98
 
99
 
100
+ class LocalVJEPA2Backbone(nn.Module):
101
+ def __init__(self, size: str, image_size: int, normalize_input: bool, checkpoint_path: str) -> None:
102
  super().__init__()
103
  self.image_size = int(image_size)
104
  self.normalize_input = bool(normalize_input)
 
109
  "huge": "vjepa2_vit_huge",
110
  "giant": "vjepa2_vit_giant",
111
  }[size]
112
+ backbone = torch.hub.load("facebookresearch/vjepa2", hub_name, pretrained=False)
113
+ backbone, predictor = backbone if isinstance(backbone, (list, tuple)) else (backbone, None)
114
+ state_dict = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
115
+ backbone.load_state_dict(_clean_backbone_key(state_dict["target_encoder"]), strict=False)
116
+ if predictor is not None and "predictor" in state_dict:
117
+ predictor.load_state_dict(_clean_backbone_key(state_dict["predictor"]), strict=False)
118
+ self.backbone = backbone
119
 
120
  def forward(self, videos: torch.Tensor, normalize: bool | None = None) -> torch.Tensor:
121
  if videos.ndim != 5:
 
144
  return self.backbone(videos.permute(0, 2, 1, 3, 4))
145
 
146
 
147
+ def _clean_backbone_key(state_dict: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
148
+ cleaned = {}
149
+ for key, value in state_dict.items():
150
+ key = key.replace("module.", "").replace("backbone.", "")
151
+ cleaned[key] = value
152
+ return cleaned
153
+
154
+
155
  class VJEPA2FMRIEncoderModel(PreTrainedModel):
156
  config_class = VJEPA2FMRIEncoderConfig
157
  base_model_prefix = "vjepa2_fmri_encoder"
 
162
  self.decoders = nn.ModuleList()
163
  self.register_buffer("decoding_units", torch.empty(0, dtype=torch.long))
164
  self.extractor: HookedFeatureExtractor | None = None
165
+ self.vjepa: LocalVJEPA2Backbone | None = None
166
 
167
  @classmethod
168
  def from_pretrained(
 
196
  local_files_only=local_files_only,
197
  )
198
 
199
+ checkpoint_path = cls._resolve_file_path(
200
  pretrained_model_name_or_path,
201
  filename=config.checkpoint_filename,
202
  revision=revision,
 
217
  vjepa_size = config.vjepa_size if vjepa_size is None else vjepa_size
218
  normalize_input = config.normalize_input if normalize_input is None else bool(normalize_input)
219
  if load_vjepa:
220
+ backbone_path = cls._resolve_file_path(
221
+ pretrained_model_name_or_path,
222
+ filename=config.backbone_filename,
223
+ revision=revision,
224
+ token=token,
225
+ cache_dir=cache_dir,
226
+ local_files_only=local_files_only,
227
+ )
228
  extractor_config = checkpoint["extractor_config"]
229
  model.extractor = HookedFeatureExtractor(
230
+ layer_names=cls._resolve_layer_names(extractor_config),
231
  ret_type=extractor_config.get("ret_type", "chw"),
232
  spatial_pool=extractor_config.get("spatial_pool", 14),
233
  )
234
+ model.vjepa = LocalVJEPA2Backbone(
235
  size=vjepa_size,
236
  image_size=config.image_size,
237
  normalize_input=normalize_input,
238
+ checkpoint_path=backbone_path,
239
  )
240
  model.eval()
241
  return model
242
 
243
  @staticmethod
244
+ def _resolve_file_path(
245
  pretrained_model_name_or_path: str | os.PathLike[str],
246
  *,
247
  filename: str,
 
252
  ) -> str:
253
  path = Path(pretrained_model_name_or_path)
254
  if path.exists():
255
+ file_path = path / filename if path.is_dir() else path
256
+ if not file_path.exists():
257
+ raise FileNotFoundError(f"Missing file: {file_path}")
258
+ return str(file_path)
259
 
260
  from huggingface_hub import hf_hub_download
261
 
 
269
  local_files_only=local_files_only,
270
  )
271
 
272
+ @staticmethod
273
+ def _resolve_layer_names(extractor_config: dict[str, Any]) -> list[str]:
274
+ layer_names = extractor_config.get("layer_names")
275
+ if layer_names is None:
276
+ layer_names = extractor_config.get("loi")
277
+ if layer_names is None:
278
+ raise KeyError("extractor_config must contain `layer_names` or `loi`.")
279
+ return list(layer_names)
280
+
281
  def forward_features(self, features: list[torch.Tensor]) -> torch.Tensor:
282
  if len(features) != len(self.decoders):
283
  raise ValueError(f"Expected {len(self.decoders)} feature tensors, got {len(features)}")