Coverage for transformer_lens/HookedRootModule.py: 63%

179 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-10-10 05:10 +0000

1"""HookedRootModule. 

2 

3Base class extending :class:`torch.nn.Module` with hook-based introspection 

4utilities: wrap any module's tensors in :class:`HookPoint` wrappers, call ``setup()``, 

5and ``run_with_hooks`` / ``run_with_cache`` work on it. This is permanent 

6infrastructure — it survives the 4.0 removal of the legacy model classes and is 

7the supported way to add TransformerLens-style hooks to arbitrary ``nn.Module`` objects. 

8Lives in its own module so that downstream code (e.g. :class:`ActivationCache`) 

9can type-hint against it without the broader ``hook_points`` import surface. 

10""" 

11 

12from __future__ import annotations 

13 

14import logging 

15from collections.abc import Callable, Iterable 

16from contextlib import contextmanager 

17from functools import partial 

18from typing import Any, Literal, Optional, Union, cast 

19 

20import torch 

21import torch.nn as nn 

22from torch import Tensor 

23 

24from transformer_lens.hook_points import ( 

25 DeviceType, 

26 HookFunction, 

27 HookIntrospectionMixin, 

28 HookPoint, 

29 NamesFilter, 

30) 

31from transformer_lens.utilities import Slice, SliceInput 

32from transformer_lens.utilities import remove_batch_dim as drop_batch_dim 

33from transformer_lens.utilities import warn_if_mps 

34 

35 

36class HookedRootModule(HookIntrospectionMixin, nn.Module): 

37 """A class building on nn.Module to interface nicely with HookPoints. 

38 

39 Adds various nice utilities, most notably run_with_hooks to run the model with temporary hooks, 

40 and run_with_cache to run the model on some input and return a cache of all activations. 

41 

42 Notes: 

43 

44 The main footgun with PyTorch hooking is that hooks are GLOBAL state. If you add a hook to the 

45 module, and then run it a bunch of times, the hooks persist. If you debug a broken hook and add 

46 the fixed version, the broken one is still there. To solve this, run_with_hooks will remove 

47 hooks at the end by default, and I recommend using the API of this and run_with_cache. If you 

48 want to add hooks into global state, I recommend being intentional about this, and I recommend 

49 using reset_hooks liberally in your code to remove any accidentally remaining global state. 

50 

51 The main time this goes wrong is when you want to use backward hooks (to cache or intervene on 

52 gradients). In this case, you need to keep the hooks around as global state until you've run 

53 loss.backward() (and so need to disable the reset_hooks_end flag on run_with_hooks) 

54 """ 

55 

56 name: Optional[str] 

57 mod_dict: dict[str, nn.Module] 

58 hook_dict: dict[str, HookPoint] 

59 

60 def __init__(self, *args: Any): 

61 super().__init__() 

62 self.is_caching = False 

63 self.context_level = 0 

64 

65 def setup(self): 

66 """ 

67 Sets up model. 

68 

69 This function must be called in the model's `__init__` method AFTER defining all layers. It 

70 adds a parameter to each module containing its name, and builds a dictionary mapping module 

71 names to the module instances. It also initializes a hook dictionary for modules of type 

72 "HookPoint". 

73 """ 

74 self.mod_dict = {} 

75 self.hook_dict = {} 

76 for name, module in self.named_modules(): 

77 if name == "": 

78 continue 

79 module.name = name 

80 self.mod_dict[name] = module 

81 # TODO: is the bottom line the same as "if "HookPoint" in str(type(module)):" 

82 if isinstance(module, HookPoint): 82 ↛ 76line 82 didn't jump to line 76 because the condition on line 82 was always true

83 self.hook_dict[name] = module 

84 

85 def hook_points(self): 

86 return self.hook_dict.values() 

87 

88 def remove_all_hook_fns( 

89 self, 

90 direction: Literal["fwd", "bwd", "both"] = "both", 

91 including_permanent: bool = False, 

92 level: Optional[int] = None, 

93 ): 

94 for hp in self.hook_points(): 

95 hp.remove_hooks(direction, including_permanent=including_permanent, level=level) 

96 

97 def clear_contexts(self): 

98 for hp in self.hook_points(): 

99 hp.clear_context() 

100 

101 def reset_hooks( 

102 self, 

103 clear_contexts: bool = True, 

104 direction: Literal["fwd", "bwd", "both"] = "both", 

105 including_permanent: bool = False, 

106 level: Optional[int] = None, 

107 ): 

108 if clear_contexts: 

109 self.clear_contexts() 

110 self.remove_all_hook_fns(direction, including_permanent, level=level) 

111 self.is_caching = False 

112 

113 def check_and_add_hook( 

114 self, 

115 hook_point: HookPoint, 

116 hook_point_name: str, 

117 hook: HookFunction, 

118 dir: Literal["fwd", "bwd"] = "fwd", 

119 is_permanent: bool = False, 

120 level: Optional[int] = None, 

121 prepend: bool = False, 

122 ) -> None: 

123 """Runs checks on the hook, and then adds it to the hook point""" 

124 

125 self.check_hooks_to_add( 

126 hook_point, 

127 hook_point_name, 

128 hook, 

129 dir=dir, 

130 is_permanent=is_permanent, 

131 prepend=prepend, 

132 ) 

133 hook_point.add_hook(hook, dir=dir, is_permanent=is_permanent, level=level, prepend=prepend) 

134 

135 def check_hooks_to_add( 

136 self, 

137 hook_point: HookPoint, 

138 hook_point_name: str, 

139 hook: HookFunction, 

140 dir: Literal["fwd", "bwd"] = "fwd", 

141 is_permanent: bool = False, 

142 prepend: bool = False, 

143 ) -> None: 

144 """Override this function to add checks on which hooks should be added""" 

145 pass 

146 

147 def add_hook( 

148 self, 

149 name: Union[str, Callable[[str], bool]], 

150 hook: HookFunction, 

151 dir: Literal["fwd", "bwd"] = "fwd", 

152 is_permanent: bool = False, 

153 level: Optional[int] = None, 

154 prepend: bool = False, 

155 ) -> None: 

156 if isinstance(name, str): 

157 hook_point = self.mod_dict[name] 

158 assert isinstance( 

159 hook_point, HookPoint 

160 ) # TODO does adding assert meaningfully slow down performance? I've added them for type checking purposes. 

161 self.check_and_add_hook( 

162 hook_point, 

163 name, 

164 hook, 

165 dir=dir, 

166 is_permanent=is_permanent, 

167 level=level, 

168 prepend=prepend, 

169 ) 

170 else: 

171 # Otherwise, name is a Boolean function on names 

172 for hook_point_name, hp in self.hook_dict.items(): 

173 if name(hook_point_name): 

174 self.check_and_add_hook( 

175 hp, 

176 hook_point_name, 

177 hook, 

178 dir=dir, 

179 is_permanent=is_permanent, 

180 level=level, 

181 prepend=prepend, 

182 ) 

183 

184 def add_perma_hook( 

185 self, 

186 name: Union[str, Callable[[str], bool]], 

187 hook: HookFunction, 

188 dir: Literal["fwd", "bwd"] = "fwd", 

189 ) -> None: 

190 self.add_hook(name, hook, dir=dir, is_permanent=True) 

191 

192 def _enable_hook_with_name(self, name: str, hook: Callable, dir: Literal["fwd", "bwd"]): 

193 """This function takes a key for the mod_dict and enables the related hook for that module 

194 

195 Args: 

196 name (str): The module name 

197 hook (Callable): The hook to add 

198 dir (Literal["fwd", "bwd"]): The direction for the hook 

199 """ 

200 hook_point_module = self.mod_dict[name] 

201 if not hasattr(hook_point_module, "add_hook"): 201 ↛ 202line 201 didn't jump to line 202 because the condition on line 201 was never true

202 raise TypeError(f"Expected a module with add_hook, got {type(hook_point_module)}") 

203 if isinstance(hook_point_module, torch.Tensor): 203 ↛ 204line 203 didn't jump to line 204 because the condition on line 203 was never true

204 raise TypeError( 

205 "Module set as Tensor for some reason!" 

206 ) # mypy seems to think these could be tensors after a torch update no idea why, or if this is possible 

207 module_with_hook = cast(HookPoint, hook_point_module) 

208 module_with_hook.add_hook(hook, dir=dir, level=self.context_level) 

209 

210 def _enable_hooks_for_points( 

211 self, 

212 hook_points: Iterable[tuple[str, HookPoint]], 

213 enabled: Callable, 

214 hook: Callable, 

215 dir: Literal["fwd", "bwd"], 

216 ): 

217 """Enables hooks for a list of points 

218 

219 Args: 

220 hook_points (Dict[str, HookPoint]): The hook points 

221 enabled (Callable): _description_ 

222 hook (Callable): _description_ 

223 dir (Literal["fwd", "bwd"]): _description_ 

224 """ 

225 for hook_name, hook_point in hook_points: 

226 if enabled(hook_name): 

227 hook_point.add_hook(hook, dir=dir, level=self.context_level) 

228 

229 def _enable_hook(self, name: Union[str, Callable], hook: Callable, dir: Literal["fwd", "bwd"]): 

230 """Enables an individual hook on a hook point 

231 

232 Args: 

233 name (str): The name of the hook 

234 hook (Callable): The actual hook 

235 dir (Literal["fwd", "bwd"], optional): The direction of the hook. Defaults to "fwd". 

236 """ 

237 if isinstance(name, str): 237 ↛ 240line 237 didn't jump to line 240 because the condition on line 237 was always true

238 self._enable_hook_with_name(name=name, hook=hook, dir=dir) 

239 else: 

240 self._enable_hooks_for_points( 

241 hook_points=self.hook_dict.items(), enabled=name, hook=hook, dir=dir 

242 ) 

243 

244 @contextmanager 

245 def hooks( 

246 self, 

247 fwd_hooks: list[tuple[Union[str, Callable], Callable]] = [], 

248 bwd_hooks: list[tuple[Union[str, Callable], Callable]] = [], 

249 reset_hooks_end: bool = True, 

250 clear_contexts: bool = False, 

251 ): 

252 """ 

253 A context manager for adding temporary hooks to the model. 

254 

255 Args: 

256 fwd_hooks: List[Tuple[name, hook]], where name is either the name of a hook point or a 

257 Boolean function on hook names and hook is the function to add to that hook point. 

258 bwd_hooks: Same as fwd_hooks, but for the backward pass. 

259 reset_hooks_end (bool): If True, removes all hooks added by this context manager when the context manager exits. 

260 clear_contexts (bool): If True, clears hook contexts whenever hooks are reset. 

261 

262 Example: 

263 

264 .. code-block:: python 

265 

266 with model.hooks(fwd_hooks=my_hooks): 

267 hooked_loss = model(text, return_type="loss") 

268 """ 

269 try: 

270 self.context_level += 1 

271 

272 for name, hook in fwd_hooks: 

273 self._enable_hook(name=name, hook=hook, dir="fwd") 

274 for name, hook in bwd_hooks: 274 ↛ 275line 274 didn't jump to line 275 because the loop on line 274 never started

275 self._enable_hook(name=name, hook=hook, dir="bwd") 

276 yield self 

277 finally: 

278 if reset_hooks_end: 278 ↛ 282line 278 didn't jump to line 282 because the condition on line 278 was always true

279 self.reset_hooks( 

280 clear_contexts, including_permanent=False, level=self.context_level 

281 ) 

282 self.context_level -= 1 

283 

284 def run_with_hooks( 

285 self, 

286 *model_args: Any, # TODO: unsure about whether or not this Any typing is correct or not; may need to be replaced with something more specific? 

287 fwd_hooks: list[tuple[Union[str, Callable], Callable]] = [], 

288 bwd_hooks: list[tuple[Union[str, Callable], Callable]] = [], 

289 reset_hooks_end: bool = True, 

290 clear_contexts: bool = False, 

291 **model_kwargs: Any, 

292 ): 

293 """ 

294 Runs the model with specified forward and backward hooks. 

295 

296 Args: 

297 fwd_hooks (List[Tuple[Union[str, Callable], Callable]]): A list of (name, hook), where name is 

298 either the name of a hook point or a boolean function on hook names, and hook is the 

299 function to add to that hook point. Hooks with names that evaluate to True are added 

300 respectively. 

301 bwd_hooks (List[Tuple[Union[str, Callable], Callable]]): Same as fwd_hooks, but for the 

302 backward pass. 

303 reset_hooks_end (bool): If True, all hooks are removed at the end, including those added 

304 during this run. Default is True. 

305 clear_contexts (bool): If True, clears hook contexts whenever hooks are reset. Default is 

306 False. 

307 *model_args: Positional arguments for the model. 

308 **model_kwargs: Keyword arguments for the model's forward function. See your related 

309 models forward pass for details as to what sort of arguments you can pass through. 

310 

311 Note: 

312 If you want to use backward hooks, set `reset_hooks_end` to False, so the backward hooks 

313 remain active. This function only runs a forward pass. 

314 """ 

315 if len(bwd_hooks) > 0 and reset_hooks_end: 

316 logging.warning( 

317 "WARNING: Hooks will be reset at the end of run_with_hooks. This removes the backward hooks before a backward pass can occur." 

318 ) 

319 

320 with self.hooks(fwd_hooks, bwd_hooks, reset_hooks_end, clear_contexts) as hooked_model: 

321 return hooked_model.forward(*model_args, **model_kwargs) 

322 

323 def add_caching_hooks( 

324 self, 

325 names_filter: NamesFilter = None, 

326 incl_bwd: bool = False, 

327 device: DeviceType = None, # TODO: unsure about whether or not this device typing is correct or not? 

328 remove_batch_dim: bool = False, 

329 cache: Optional[dict] = None, 

330 ) -> dict: 

331 """Adds hooks to the model to cache activations. Note: It does NOT actually run the model to get activations, that must be done separately. 

332 

333 Args: 

334 names_filter (NamesFilter, optional): Which activations to cache. Can be a list of strings (hook names) or a filter function mapping hook names to booleans. Defaults to lambda name: True. 

335 incl_bwd (bool, optional): Whether to also do backwards hooks. Defaults to False. 

336 device (_type_, optional): The device to store on. Defaults to same device as model. 

337 remove_batch_dim (bool, optional): Whether to remove the batch dimension (only works for batch_size==1). A leading dimension that isn't size 1 is left in place, so a larger batch keeps its batch dimension. Defaults to False. 

338 cache (Optional[dict], optional): The cache to store activations in, a new dict is created by default. Defaults to None. 

339 

340 Returns: 

341 cache (dict): The cache where activations will be stored. 

342 """ 

343 if device is not None: 343 ↛ 344line 343 didn't jump to line 344 because the condition on line 343 was never true

344 warn_if_mps(device) 

345 if cache is None: 345 ↛ 348line 345 didn't jump to line 348 because the condition on line 345 was always true

346 cache = {} 

347 

348 if names_filter is None: 348 ↛ 350line 348 didn't jump to line 350 because the condition on line 348 was always true

349 names_filter = lambda name: True 

350 elif isinstance(names_filter, str): 

351 filter_str = names_filter 

352 names_filter = lambda name: name == filter_str 

353 elif isinstance(names_filter, list): 

354 filter_list = names_filter 

355 names_filter = lambda name: name in filter_list 

356 

357 assert callable(names_filter), "names_filter must be a callable" 

358 

359 self.is_caching = True 

360 

361 def save_hook(tensor: Tensor, hook: HookPoint, is_backward: bool): 

362 assert hook.name is not None 

363 hook_name = hook.name 

364 if is_backward: 364 ↛ 365line 364 didn't jump to line 365 because the condition on line 364 was never true

365 hook_name += "_grad" 

366 stored = tensor.detach().to(device) 

367 cache[hook_name] = drop_batch_dim(stored) if remove_batch_dim else stored 

368 

369 for name, hp in self.hook_dict.items(): 

370 if names_filter(name): 370 ↛ 369line 370 didn't jump to line 369 because the condition on line 370 was always true

371 hp.add_hook(partial(save_hook, is_backward=False), "fwd") 

372 if incl_bwd: 372 ↛ 373line 372 didn't jump to line 373 because the condition on line 372 was never true

373 hp.add_hook(partial(save_hook, is_backward=True), "bwd") 

374 return cache 

375 

376 def run_with_cache( 

377 self, 

378 *model_args: Any, 

379 names_filter: NamesFilter = None, 

380 device: DeviceType = None, 

381 remove_batch_dim: bool = False, 

382 incl_bwd: bool = False, 

383 reset_hooks_end: bool = True, 

384 clear_contexts: bool = False, 

385 pos_slice: Optional[Union[Slice, SliceInput]] = None, 

386 **model_kwargs: Any, 

387 ): 

388 """ 

389 Runs the model and returns the model output and a Cache object. 

390 

391 Args: 

392 *model_args: Positional arguments for the model. 

393 names_filter (NamesFilter, optional): A filter for which activations to cache. Accepts None, str, 

394 list of str, or a function that takes a string and returns a bool. Defaults to None, which 

395 means cache everything. 

396 device (str or torch.Device, optional): The device to cache activations on. Defaults to the 

397 model device. WARNING: Setting a different device than the one used by the model leads to 

398 significant performance degradation. 

399 remove_batch_dim (bool, optional): If True, removes the batch dimension when caching. Only 

400 makes sense with batch_size=1 inputs: an AssertionError is raised when the first 

401 positional input has a larger batch. Defaults to False. 

402 incl_bwd (bool, optional): If True, calls backward on the model output and caches gradients 

403 as well. Assumes that the model outputs a scalar (e.g., return_type="loss"). Custom loss 

404 functions are not supported. Defaults to False. 

405 reset_hooks_end (bool, optional): If True, removes all hooks added by this function at the 

406 end of the run. Defaults to True. 

407 clear_contexts (bool, optional): If True, clears hook contexts whenever hooks are reset. 

408 Defaults to False. 

409 pos_slice: 

410 The slice to apply to the cache output. Defaults to None, do nothing. 

411 **model_kwargs: Keyword arguments for the model's forward function. See your related 

412 models forward pass for details as to what sort of arguments you can pass through. 

413 

414 Returns: 

415 tuple: A tuple containing the model output and a Cache object. 

416 

417 """ 

418 

419 if remove_batch_dim and model_args and isinstance(model_args[0], Tensor): 419 ↛ 425line 419 didn't jump to line 425 because the condition on line 419 was always true

420 batch_size = model_args[0].shape[0] if model_args[0].ndim > 0 else 1 

421 assert ( 

422 batch_size == 1 

423 ), f"Cannot remove batch dimension from cache with batch size {batch_size}" 

424 

425 pos_slice = Slice.unwrap(pos_slice) 

426 

427 cache_dict, fwd, bwd = self.get_caching_hooks( 

428 names_filter, 

429 incl_bwd, 

430 device, 

431 remove_batch_dim=remove_batch_dim, 

432 pos_slice=pos_slice, 

433 ) 

434 

435 with self.hooks( 

436 fwd_hooks=fwd, 

437 bwd_hooks=bwd, 

438 reset_hooks_end=reset_hooks_end, 

439 clear_contexts=clear_contexts, 

440 ): 

441 model_out = self(*model_args, **model_kwargs) 

442 if incl_bwd: 442 ↛ 443line 442 didn't jump to line 443 because the condition on line 442 was never true

443 model_out.backward() 

444 

445 return model_out, cache_dict 

446 

447 def get_caching_hooks( 

448 self, 

449 names_filter: NamesFilter = None, 

450 incl_bwd: bool = False, 

451 device: DeviceType = None, 

452 remove_batch_dim: bool = False, 

453 cache: Optional[dict] = None, 

454 pos_slice: Optional[Union[Slice, SliceInput]] = None, 

455 ) -> tuple[dict, list, list]: 

456 """Creates hooks to cache activations. Note: It does not add the hooks to the model. 

457 

458 Args: 

459 names_filter (NamesFilter, optional): Which activations to cache. Can be a list of strings (hook names) or a filter function mapping hook names to booleans. Defaults to lambda name: True. 

460 incl_bwd (bool, optional): Whether to also do backwards hooks. Defaults to False. 

461 device (_type_, optional): The device to store on. Keeps on the same device as the layer if None. 

462 remove_batch_dim (bool, optional): Whether to remove the batch dimension (only works for batch_size==1). A leading dimension that isn't size 1 is left in place, so a larger batch keeps its batch dimension. Defaults to False. 

463 cache (Optional[dict], optional): The cache to store activations in, a new dict is created by default. Defaults to None. 

464 

465 Returns: 

466 cache (dict): The cache where activations will be stored. 

467 fwd_hooks (list): The forward hooks. 

468 bwd_hooks (list): The backward hooks. Empty if incl_bwd is False. 

469 """ 

470 if device is not None: 470 ↛ 471line 470 didn't jump to line 471 because the condition on line 470 was never true

471 warn_if_mps(device) 

472 if cache is None: 472 ↛ 475line 472 didn't jump to line 475 because the condition on line 472 was always true

473 cache = {} 

474 

475 pos_slice = Slice.unwrap(pos_slice) 

476 

477 if names_filter is None: 477 ↛ 479line 477 didn't jump to line 479 because the condition on line 477 was always true

478 names_filter = lambda name: True 

479 elif isinstance(names_filter, str): 

480 filter_str = names_filter 

481 names_filter = lambda name: name == filter_str 

482 elif isinstance(names_filter, list): 

483 filter_list = names_filter 

484 names_filter = lambda name: name in filter_list 

485 elif callable(names_filter): 

486 names_filter = names_filter 

487 else: 

488 raise ValueError("names_filter must be a string, list of strings, or function") 

489 assert callable(names_filter) # Callable[[str], bool] 

490 

491 self.is_caching = True 

492 

493 def save_hook(tensor: Tensor, hook: HookPoint, is_backward: bool = False): 

494 # for attention heads the pos dimension is the third from last 

495 if hook.name is None: 495 ↛ 496line 495 didn't jump to line 496 because the condition on line 495 was never true

496 raise RuntimeError("Hook should have been provided a name") 

497 

498 hook_name = hook.name 

499 if is_backward: 499 ↛ 500line 499 didn't jump to line 500 because the condition on line 499 was never true

500 hook_name += "_grad" 

501 resid_stream = tensor.detach().to(device) 

502 if remove_batch_dim: 502 ↛ 505line 502 didn't jump to line 505 because the condition on line 502 was always true

503 resid_stream = drop_batch_dim(resid_stream) 

504 

505 if ( 505 ↛ 512line 505 didn't jump to line 512 because the condition on line 505 was never true

506 hook.name.endswith("hook_q") 

507 or hook.name.endswith("hook_k") 

508 or hook.name.endswith("hook_v") 

509 or hook.name.endswith("hook_z") 

510 or hook.name.endswith("hook_result") 

511 ): 

512 pos_dim = -3 

513 else: 

514 # for all other components the pos dimension is the second from last 

515 # including the attn scores where the dest token is the second from last 

516 pos_dim = -2 

517 

518 if ( 518 ↛ 521line 518 didn't jump to line 521 because the condition on line 518 was never true

519 resid_stream.dim() >= -pos_dim 

520 ): # check if the residual stream has a pos dimension before trying to slice 

521 resid_stream = pos_slice.apply(resid_stream, dim=pos_dim) 

522 cache[hook_name] = resid_stream 

523 

524 fwd_hooks = [] 

525 bwd_hooks = [] 

526 for name, _ in self.hook_dict.items(): 

527 if names_filter(name): 527 ↛ 526line 527 didn't jump to line 526 because the condition on line 527 was always true

528 fwd_hooks.append((name, partial(save_hook, is_backward=False))) 

529 if incl_bwd: 529 ↛ 530line 529 didn't jump to line 530 because the condition on line 529 was never true

530 bwd_hooks.append((name, partial(save_hook, is_backward=True))) 

531 

532 return cache, fwd_hooks, bwd_hooks 

533 

534 def cache_all( 

535 self, 

536 cache: Optional[dict], 

537 incl_bwd: bool = False, 

538 device: DeviceType = None, 

539 remove_batch_dim: bool = False, 

540 ): 

541 logging.warning( 

542 "cache_all is deprecated and will eventually be removed, use add_caching_hooks or run_with_cache" 

543 ) 

544 self.add_caching_hooks( 

545 names_filter=lambda name: True, 

546 cache=cache, 

547 incl_bwd=incl_bwd, 

548 device=device, 

549 remove_batch_dim=remove_batch_dim, 

550 ) 

551 

552 def cache_some( 

553 self, 

554 cache: Optional[dict], 

555 names: Callable[[str], bool], 

556 incl_bwd: bool = False, 

557 device: DeviceType = None, 

558 remove_batch_dim: bool = False, 

559 ): 

560 """Cache a list of hook provided by names, Boolean function on names""" 

561 logging.warning( 

562 "cache_some is deprecated and will eventually be removed, use add_caching_hooks or run_with_cache" 

563 ) 

564 self.add_caching_hooks( 

565 names_filter=names, 

566 cache=cache, 

567 incl_bwd=incl_bwd, 

568 device=device, 

569 remove_batch_dim=remove_batch_dim, 

570 )