amachine.am_transformers.am_model_inspector
model_inspector.py
Utilities for inspecting HuggingFace transformer models: enumerating weights, capturing raw CUDA pointers, registering activation hooks, and building a full memory map — ready for use with custom CUDA kernels or OpenGL interop.
Usage:
from model_inspector import ModelInspector
inspector = ModelInspector(model) inspector.print_architecture() inspector.print_parameters() inspector.print_cuda_pointers() inspector.inspect_layer(0) inspector.register_hooks() inspector.run_forward(tokenizer, "Hello world") inspector.print_activations() inspector.print_memory_map()
Get a raw pointer for a CUDA kernel or GL copy:
ptr = inspector.get_param_ptr("model.layers.0.self_attn.q_proj.weight") tensor = inspector.get_activation("layer_0")
1""" 2model_inspector.py 3------------------ 4Utilities for inspecting HuggingFace transformer models: enumerating weights, 5capturing raw CUDA pointers, registering activation hooks, and building a 6full memory map — ready for use with custom CUDA kernels or OpenGL interop. 7 8Usage: 9 from model_inspector import ModelInspector 10 11 inspector = ModelInspector(model) 12 inspector.print_architecture() 13 inspector.print_parameters() 14 inspector.print_cuda_pointers() 15 inspector.inspect_layer(0) 16 inspector.register_hooks() 17 inspector.run_forward(tokenizer, "Hello world") 18 inspector.print_activations() 19 inspector.print_memory_map() 20 21 # Get a raw pointer for a CUDA kernel or GL copy: 22 ptr = inspector.get_param_ptr("model.layers.0.self_attn.q_proj.weight") 23 tensor = inspector.get_activation("layer_0") 24""" 25 26from __future__ import annotations 27 28import warnings 29import matplotlib.pyplot as plt 30import torch 31from typing import Any 32 33try: 34 from .am_control_model_exp import * 35except Exception: 36 import traceback 37 traceback.print_exc() 38 warnings.warn("Failed to import control model") 39 40# ───────────────────────────────────────────────────────────────────────────── 41# ModelInspector 42# ───────────────────────────────────────────────────────────────────────────── 43 44class ModelInspector: 45 """ 46 Wraps a HuggingFace CausalLM (or any nn.Module) and exposes utilities for 47 inspecting weights, buffers, activations, raw CUDA pointers, and memory. 48 49 Parameters 50 ---------- 51 model : torch.nn.Module 52 A loaded HuggingFace model (or any PyTorch model on CUDA). 53 """ 54 55 def __init__(self, model: torch.nn.Module) -> None: 56 self.model = model 57 self._hooks: list[torch.utils.hooks.RemovableHook] = [] 58 self._activations: dict[str, dict[str, Any]] = {} 59 60 # ───────────────────────────────────────────────────────────────────────── 61 # Architecture 62 # ───────────────────────────────────────────────────────────────────────── 63 64 def print_architecture(self) -> None: 65 """Print the top-level module tree.""" 66 print("\n" + "═" * 80) 67 print(" MODEL ARCHITECTURE") 68 print("═" * 80) 69 print(self.model) 70 print() 71 72 # ───────────────────────────────────────────────────────────────────────── 73 # Parameters & buffers 74 # ───────────────────────────────────────────────────────────────────────── 75 76 def print_parameters(self, cuda_only: bool = False) -> None: 77 """ 78 Print every named parameter (trainable weight). 79 80 Parameters 81 ---------- 82 cuda_only : bool 83 When True, only show tensors that live on a CUDA device. 84 """ 85 print( "Parameters" ) 86 print("\n" + "─" * 110) 87 print(f" {'NAME':55s} {'SHAPE':28s} {'DTYPE':12s} DEVICE") 88 print("─" * 110) 89 for name, param in self.model.named_parameters(): 90 if cuda_only and param.device.type != "cuda": 91 continue 92 print(f" {name:55s} {str(tuple(param.shape)):28s} " 93 f"{str(param.dtype):12s} {param.device}") 94 print() 95 96 def print_buffers(self, cuda_only: bool = False) -> None: 97 """ 98 Print every named buffer (non-trainable tensors, e.g. RoPE cos/sin). 99 100 Parameters 101 ---------- 102 cuda_only : bool 103 When True, only show tensors that live on a CUDA device. 104 """ 105 print( "Buffers" ) 106 print("\n" + "─" * 110) 107 print(f" {'NAME (BUFFER)':55s} {'SHAPE':28s} {'DTYPE':12s} DEVICE") 108 print("─" * 110) 109 for name, buf in self.model.named_buffers(): 110 if cuda_only and buf.device.type != "cuda": 111 continue 112 print(f" {name:55s} {str(tuple(buf.shape)):28s} " 113 f"{str(buf.dtype):12s} {buf.device}") 114 print() 115 116 # ───────────────────────────────────────────────────────────────────────── 117 # Raw CUDA pointers 118 # ───────────────────────────────────────────────────────────────────────── 119 120 def print_cuda_pointers(self) -> None: 121 """ 122 Print the raw device pointer for every CUDA parameter and buffer. 123 These pointers can be passed directly to custom CUDA kernels or used 124 with cudaMemcpy for OpenGL / Vulkan interop. 125 """ 126 print( "Pointers" ) 127 print("\n" + "─" * 120) 128 print(f" {'NAME':60s} {'SHAPE':25s} {'DTYPE':12s} DATA_PTR (hex)") 129 print("─" * 120) 130 for name, tensor in list(self.model.named_parameters()) + list(self.model.named_buffers()): 131 if tensor.device.type == "cuda": 132 print(f" {name:60s} {str(tuple(tensor.shape)):25s} " 133 f"{str(tensor.dtype):12s} {hex(tensor.data_ptr())}") 134 print() 135 136 def get_param_ptr(self, param_name: str) -> int: 137 """ 138 Return the raw CUDA device pointer (as a Python int / void*) for a 139 named parameter or buffer. 140 141 Example 142 ------- 143 >>> ptr = inspector.get_param_ptr("model.layers.0.self_attn.q_proj.weight") 144 >>> my_cuda_ext.my_kernel(ptr, rows, cols) 145 146 Parameters 147 ---------- 148 param_name : str 149 Dot-separated parameter path, e.g. "model.layers.0.self_attn.q_proj.weight". 150 151 Returns 152 ------- 153 int 154 The device pointer as a Python integer. 155 """ 156 # Search parameters first, then buffers 157 lookup = dict(self.model.named_parameters()) 158 lookup.update(dict(self.model.named_buffers())) 159 if param_name not in lookup: 160 raise KeyError(f"No parameter or buffer named '{param_name}'") 161 tensor = lookup[param_name] 162 if tensor.device.type != "cuda": 163 raise RuntimeError(f"'{param_name}' is on {tensor.device}, not CUDA") 164 return tensor.data_ptr() 165 166 # ───────────────────────────────────────────────────────────────────────── 167 # Layer-level deep-dive (LLaMA / Mistral / GPT-NeoX style) 168 # ───────────────────────────────────────────────────────────────────────── 169 170 def inspect_layer(self, layer_idx: int) -> None: 171 try: 172 layer = self.model.model.layers[layer_idx] 173 except (AttributeError, IndexError) as exc: 174 print(f"[inspect_layer] Could not access layer {layer_idx}: {exc}") 175 return 176 177 print(f"\n{'═' * 80}") 178 print(f" LAYER {layer_idx} DETAIL") 179 print("═" * 80) 180 181 # Dynamically discover and print every submodule (e.g., self_attn, shared_mlp, norm) 182 for name, module in layer.named_children(): 183 self._print_submodule(name, module) 184 print() 185 186 def _print_submodule(self, label: str, module: torch.nn.Module) -> None: 187 """Helper: print all parameters in an arbitrary sub-module.""" 188 print(f"\n [{label}]") 189 print(f" {'param':30s} {'shape':25s} {'dtype':12s} data_ptr") 190 print(" " + "─" * 82) 191 for name, param in module.named_parameters(): 192 ptr = hex(param.data_ptr()) if param.is_cuda else "cpu" 193 print(f" {name:30s} {str(tuple(param.shape)):25s} " 194 f"{str(param.dtype):12s} {ptr}") 195 196 def visualize_weights(self, layer_idx: int = 0, cmap: str = "viridis") -> None: 197 """ 198 Visualizes the weights and biases of a specific layer as 2D slices. 199 Higher-dimensional tensors (e.g., 3D or 4D) are sliced into 2D grids. 200 """ 201 try: 202 # Targeting the specific layer in the Granite model 203 layer = self.model.model.layers[layer_idx] 204 except (AttributeError, IndexError): 205 print(f"Layer {layer_idx} not found.") 206 return 207 208 # Collect all parameters within this specific layer 209 params = {n: p for n, p in layer.named_parameters()} 210 211 if not params: 212 print(f"No parameters found in layer {layer_idx}.") 213 return 214 215 num_params = len(params) 216 fig, axes = plt.subplots(num_params, 1, figsize=(10, 10 * num_params)) 217 218 if num_params == 1: axes = [axes] 219 220 for ax, (name, param) in zip(axes, params.items()): 221 222 data = param.detach().cpu().float() 223 224 # Handle different dimensionalities 225 if data.dim() == 1: 226 # 1D (Biases/Norms) -> Reshape to a 2D row for visualization 227 data = data.unsqueeze(0) 228 elif data.dim() > 2: 229 # Multi-dimensional -> Take the first 2D slice [0, 0, :, :] 230 # This is common in MoE routers or complex head projections 231 while data.dim() > 2: 232 data = data[0] 233 234 im = ax.imshow(data.numpy(), aspect='auto', cmap=cmap) 235 ax.set_title(f"{name} | Shape: {tuple(param.shape)}") 236 fig.colorbar(im, ax=ax) 237 238 plt.tight_layout() 239 plt.show() 240 241 def visualize_activations(self, tag: str, tokenizer: Any = None, prompt: str = None) -> None: 242 """ 243 Visualizes the captured activation for a given module tag. 244 If tokenizer and prompt are provided, labels the Y-axis with tokens. 245 """ 246 if tag not in self._activations: 247 print(f"No activation found for tag: '{tag}'. Did you run_forward()?") 248 return 249 250 # Extract the tensor from the stored activation data 251 data = self._activations[tag]["tensor"].detach().cpu().float() 252 253 # Squeeze batch dimension if it's 1: (1, Seq, Hidden) -> (Seq, Hidden) 254 if data.dim() == 3 and data.size(0) == 1: 255 data = data.squeeze(0) 256 257 # If it's still > 2D (e.g. multi-head attention states), take the last 2 dims 258 while data.dim() > 2: 259 data = data[0] 260 261 fig, ax = plt.subplots(figsize=(12, 0.5 * data.size(0) + 2)) 262 im = ax.imshow(data.numpy(), aspect='auto', cmap='magma') 263 264 # Labeling tokens if possible 265 if tokenizer and prompt: 266 tokens = tokenizer.tokenize(prompt) 267 # Handle special tokens if the tokenizer adds them (like <s>) 268 if len(tokens) < data.size(0): 269 tokens = ["<bos>"] + tokens 270 271 ax.set_yticks(range(len(tokens))) 272 ax.set_yticklabels(tokens) 273 ax.set_ylabel("Tokens") 274 else: 275 ax.set_ylabel("Sequence Position") 276 277 ax.set_xlabel("Hidden Dimension (Features)") 278 ax.set_title(f"Activations: {tag}\nShape: {tuple(data.shape)}") 279 280 fig.colorbar(im, ax=ax, label="Activation Value") 281 plt.tight_layout() 282 plt.show() 283 284 # ───────────────────────────────────────────────────────────────────────── 285 # Forward hooks — capture live activations 286 # ───────────────────────────────────────────────────────────────────────── 287 288 def register_hooks(self) -> None: 289 290 self.remove_hooks() 291 292 def _make_hook(tag: str): 293 def _hook(module, inp, output): 294 tensor = output[0] if isinstance(output, tuple) else output 295 if not isinstance(tensor, torch.Tensor): 296 return 297 self._activations[tag] = { 298 "shape" : tuple(tensor.shape), 299 "dtype" : str(tensor.dtype), 300 "device" : str(tensor.device), 301 "data_ptr" : hex(tensor.data_ptr()) if tensor.is_cuda else "cpu", 302 "tensor" : tensor, 303 } 304 return _hook 305 306 # Recursively hook every module 307 for name, module in self.model.named_modules(): 308 # Skip the top-level model to avoid redundant giant hooks 309 if module is self.model or name == "": 310 continue 311 312 # Only hook leaf modules (the actual operations like Linear, RMSNorm) 313 # to avoid duplicating tensors from parent container modules. 314 if not list(module.children()): 315 h = module.register_forward_hook(_make_hook(name)) 316 self._hooks.append(h) 317 318 print(f"[register_hooks] Registered {len(self._hooks)} granular hooks.") 319 320 def remove_hooks(self) -> None: 321 """Remove all registered forward hooks.""" 322 for h in self._hooks: 323 h.remove() 324 self._hooks.clear() 325 326 def run_forward(self, tokenizer, text: str) -> torch.Tensor: 327 """ 328 Tokenize ``text``, run a single forward pass, and return the raw 329 logits tensor. Hooks (if registered) will populate ``_activations``. 330 331 Parameters 332 ---------- 333 tokenizer : transformers tokenizer 334 text : str 335 336 Returns 337 ------- 338 torch.Tensor — logits of shape [1, seq_len, vocab_size] 339 """ 340 device = next(self.model.parameters()).device 341 inputs = tokenizer(text, return_tensors="pt").to(device) 342 with torch.no_grad(): 343 outputs = self.model(**inputs, use_cache=False) 344 return outputs.logits 345 346 def inspect_activations(self) -> None: 347 for name, info in self._activations.items(): 348 self.visualize_activations( name ) 349 break 350 351 def print_activations(self) -> None: 352 """ 353 Print a summary of every activation captured by the forward hooks. 354 Must call ``register_hooks()`` + ``run_forward()`` first. 355 """ 356 print( "Activations" ) 357 if not self._activations: 358 print("[print_activations] No activations captured yet. " 359 "Call register_hooks() then run_forward().") 360 return 361 362 print("\n" + "─" * 100) 363 print(f" {'HOOK NAME':20s} {'SHAPE':35s} {'DTYPE':12s} DATA_PTR (hex)") 364 print("─" * 100) 365 for name, info in self._activations.items(): 366 print(f" {name:20s} {str(info['shape']):35s} " 367 f"{info['dtype']:12s} {info['data_ptr']}") 368 print() 369 370 def get_activation(self, hook_name: str) -> torch.Tensor: 371 """ 372 Retrieve a captured activation tensor by hook name. 373 374 Example 375 ------- 376 >>> tensor = inspector.get_activation("layer_0") 377 >>> my_cuda_ext.process(tensor.data_ptr(), tensor.numel()) 378 379 Parameters 380 ---------- 381 hook_name : str 382 One of the keys printed by ``print_activations()``. 383 384 Returns 385 ------- 386 torch.Tensor — the live activation (zero-copy) 387 """ 388 if hook_name not in self._activations: 389 raise KeyError( 390 f"No activation for '{hook_name}'. " 391 f"Available: {list(self._activations.keys())}" 392 ) 393 return self._activations[hook_name]["tensor"] 394 395 # ───────────────────────────────────────────────────────────────────────── 396 # Memory map 397 # ───────────────────────────────────────────────────────────────────────── 398 399 def print_memory_map(self, top_n: int = 0) -> None: 400 """ 401 Print a full memory map of all parameters, sorted by size descending. 402 403 Parameters 404 ---------- 405 top_n : int 406 If > 0, show only the top N largest tensors. 407 """ 408 rows = [] 409 total_bytes = 0 410 for name, param in self.model.named_parameters(): 411 nb = param.numel() * param.element_size() 412 total_bytes += nb 413 rows.append({ 414 "name" : name, 415 "shape" : tuple(param.shape), 416 "dtype" : str(param.dtype), 417 "mb" : nb / 1e6, 418 "data_ptr" : hex(param.data_ptr()) if param.is_cuda else "cpu", 419 "contiguous" : param.is_contiguous(), 420 }) 421 422 rows.sort(key=lambda r: r["mb"], reverse=True) 423 if top_n: 424 rows = rows[:top_n] 425 426 print( "Memory Map" ) 427 print("\n" + "─" * 135) 428 print(f" {'NAME':55s} {'SHAPE':22s} {'DTYPE':12s} " 429 f"{'MB':8s} {'PTR':18s} CONTIG") 430 print("─" * 135) 431 for r in rows: 432 print(f" {r['name']:55s} {str(r['shape']):22s} {r['dtype']:12s} " 433 f"{r['mb']:8.2f} {r['data_ptr']:18s} {r['contiguous']}") 434 435 print(f"\n Total parameter memory: {total_bytes / 1e9:.3f} GB\n") 436 437 # ───────────────────────────────────────────────────────────────────────── 438 # Convenience: wrap a raw CUDA pointer as a PyTorch tensor (zero-copy) 439 # ───────────────────────────────────────────────────────────────────────── 440 441 @staticmethod 442 def from_raw_ptr( 443 ptr: int, 444 shape: tuple[int, ...], 445 dtype: torch.dtype = torch.float32, 446 ) -> torch.Tensor: 447 """ 448 Wrap an external CUDA device pointer (e.g. from an OpenGL buffer mapped 449 via cudaGraphicsResourceGetMappedPointer, or from Vulkan external memory) 450 as a PyTorch tensor — zero-copy. 451 452 This is the reverse of ``get_param_ptr()``: use it when you want PyTorch 453 to operate on memory that was allocated outside of PyTorch. 454 455 Example (in a C++ extension after GL/Vulkan interop): 456 void* gl_ptr = ...; // from cudaGraphics* 457 auto t = torch::from_blob(gl_ptr, {n}, opts); // in C++ 458 459 Or from Python with a ctypes / CUDA-Python pointer: 460 t = ModelInspector.from_raw_ptr(raw_ptr, (1024,), torch.float16) 461 462 Parameters 463 ---------- 464 ptr : int Raw CUDA device pointer as a Python int. 465 shape : tuple[int] Desired tensor shape. 466 dtype : torch.dtype Element type (must match what the memory holds). 467 468 Returns 469 ------- 470 torch.Tensor — a non-owning view; the caller is responsible for lifetime. 471 """ 472 import ctypes 473 storage = torch.cuda.UntypedStorage.from_file( # type: ignore[attr-defined] 474 # Workaround: use ctypes + from_blob equivalent via storage pointer 475 # For real use, call torch::from_blob in a C++ extension instead. 476 "", False, 0 477 ) 478 # Preferred path: expose via torch._utils._rebuild_tensor_v2 or 479 # a thin C++ extension. Here we document the pattern: 480 raise NotImplementedError( 481 "from_raw_ptr is best implemented in a C++ extension using " 482 "torch::from_blob(ptr, shape, options). " 483 "See the docstring for the equivalent C++ snippet." 484 ) 485 486 487# ───────────────────────────────────────────────────────────────────────────── 488# Quick demo (run: python model_inspector.py) 489# ───────────────────────────────────────────────────────────────────────────── 490 491if __name__ == "__main__": 492 493 import argparse 494 from transformers import AutoModelForCausalLM, AutoTokenizer 495 496 parser = argparse.ArgumentParser(description="Inspect a HuggingFace model") 497 parser.add_argument("--model_path", default="../separable-alignment/transformer/g4-zepo-192-7-checkpoints/step_0001000", help="Path or HF model id") 498 parser.add_argument("--prompt", default="1211012011010", help="Prompt for forward pass") 499 parser.add_argument("--layer", type=int, default=0, help="Layer index to deep-dive") 500 parser.add_argument("--top_n", type=int, default=1000, help="Top N tensors in memory map") 501 502 args = parser.parse_args() 503 504 device = "cuda" if torch.cuda.is_available() else "cpu" 505 print(f"Loading model from '{args.model_path}' on {device} …") 506 507 tokenizer = AutoTokenizer.from_pretrained(args.model_path) 508 model = AutoModelForCausalLM.from_pretrained(args.model_path, device_map=device) 509 510 model.eval() 511 512 inspector = ModelInspector(model) 513 514 inspector.print_architecture() 515 inspector.print_parameters(cuda_only=True) 516 517 # inspector.print_buffers(cuda_only=True) 518 # inspector.print_cuda_pointers() 519 # inspector.inspect_layer(args.layer) 520 # inspector.visualize_weights() 521 522 inspector.register_hooks() 523 inspector.run_forward(tokenizer, args.prompt) 524 inspector.print_activations() 525 inspector.inspect_activations() 526 inspector.print_memory_map(top_n=args.top_n) 527 528 inspector.remove_hooks()
45class ModelInspector: 46 """ 47 Wraps a HuggingFace CausalLM (or any nn.Module) and exposes utilities for 48 inspecting weights, buffers, activations, raw CUDA pointers, and memory. 49 50 Parameters 51 ---------- 52 model : torch.nn.Module 53 A loaded HuggingFace model (or any PyTorch model on CUDA). 54 """ 55 56 def __init__(self, model: torch.nn.Module) -> None: 57 self.model = model 58 self._hooks: list[torch.utils.hooks.RemovableHook] = [] 59 self._activations: dict[str, dict[str, Any]] = {} 60 61 # ───────────────────────────────────────────────────────────────────────── 62 # Architecture 63 # ───────────────────────────────────────────────────────────────────────── 64 65 def print_architecture(self) -> None: 66 """Print the top-level module tree.""" 67 print("\n" + "═" * 80) 68 print(" MODEL ARCHITECTURE") 69 print("═" * 80) 70 print(self.model) 71 print() 72 73 # ───────────────────────────────────────────────────────────────────────── 74 # Parameters & buffers 75 # ───────────────────────────────────────────────────────────────────────── 76 77 def print_parameters(self, cuda_only: bool = False) -> None: 78 """ 79 Print every named parameter (trainable weight). 80 81 Parameters 82 ---------- 83 cuda_only : bool 84 When True, only show tensors that live on a CUDA device. 85 """ 86 print( "Parameters" ) 87 print("\n" + "─" * 110) 88 print(f" {'NAME':55s} {'SHAPE':28s} {'DTYPE':12s} DEVICE") 89 print("─" * 110) 90 for name, param in self.model.named_parameters(): 91 if cuda_only and param.device.type != "cuda": 92 continue 93 print(f" {name:55s} {str(tuple(param.shape)):28s} " 94 f"{str(param.dtype):12s} {param.device}") 95 print() 96 97 def print_buffers(self, cuda_only: bool = False) -> None: 98 """ 99 Print every named buffer (non-trainable tensors, e.g. RoPE cos/sin). 100 101 Parameters 102 ---------- 103 cuda_only : bool 104 When True, only show tensors that live on a CUDA device. 105 """ 106 print( "Buffers" ) 107 print("\n" + "─" * 110) 108 print(f" {'NAME (BUFFER)':55s} {'SHAPE':28s} {'DTYPE':12s} DEVICE") 109 print("─" * 110) 110 for name, buf in self.model.named_buffers(): 111 if cuda_only and buf.device.type != "cuda": 112 continue 113 print(f" {name:55s} {str(tuple(buf.shape)):28s} " 114 f"{str(buf.dtype):12s} {buf.device}") 115 print() 116 117 # ───────────────────────────────────────────────────────────────────────── 118 # Raw CUDA pointers 119 # ───────────────────────────────────────────────────────────────────────── 120 121 def print_cuda_pointers(self) -> None: 122 """ 123 Print the raw device pointer for every CUDA parameter and buffer. 124 These pointers can be passed directly to custom CUDA kernels or used 125 with cudaMemcpy for OpenGL / Vulkan interop. 126 """ 127 print( "Pointers" ) 128 print("\n" + "─" * 120) 129 print(f" {'NAME':60s} {'SHAPE':25s} {'DTYPE':12s} DATA_PTR (hex)") 130 print("─" * 120) 131 for name, tensor in list(self.model.named_parameters()) + list(self.model.named_buffers()): 132 if tensor.device.type == "cuda": 133 print(f" {name:60s} {str(tuple(tensor.shape)):25s} " 134 f"{str(tensor.dtype):12s} {hex(tensor.data_ptr())}") 135 print() 136 137 def get_param_ptr(self, param_name: str) -> int: 138 """ 139 Return the raw CUDA device pointer (as a Python int / void*) for a 140 named parameter or buffer. 141 142 Example 143 ------- 144 >>> ptr = inspector.get_param_ptr("model.layers.0.self_attn.q_proj.weight") 145 >>> my_cuda_ext.my_kernel(ptr, rows, cols) 146 147 Parameters 148 ---------- 149 param_name : str 150 Dot-separated parameter path, e.g. "model.layers.0.self_attn.q_proj.weight". 151 152 Returns 153 ------- 154 int 155 The device pointer as a Python integer. 156 """ 157 # Search parameters first, then buffers 158 lookup = dict(self.model.named_parameters()) 159 lookup.update(dict(self.model.named_buffers())) 160 if param_name not in lookup: 161 raise KeyError(f"No parameter or buffer named '{param_name}'") 162 tensor = lookup[param_name] 163 if tensor.device.type != "cuda": 164 raise RuntimeError(f"'{param_name}' is on {tensor.device}, not CUDA") 165 return tensor.data_ptr() 166 167 # ───────────────────────────────────────────────────────────────────────── 168 # Layer-level deep-dive (LLaMA / Mistral / GPT-NeoX style) 169 # ───────────────────────────────────────────────────────────────────────── 170 171 def inspect_layer(self, layer_idx: int) -> None: 172 try: 173 layer = self.model.model.layers[layer_idx] 174 except (AttributeError, IndexError) as exc: 175 print(f"[inspect_layer] Could not access layer {layer_idx}: {exc}") 176 return 177 178 print(f"\n{'═' * 80}") 179 print(f" LAYER {layer_idx} DETAIL") 180 print("═" * 80) 181 182 # Dynamically discover and print every submodule (e.g., self_attn, shared_mlp, norm) 183 for name, module in layer.named_children(): 184 self._print_submodule(name, module) 185 print() 186 187 def _print_submodule(self, label: str, module: torch.nn.Module) -> None: 188 """Helper: print all parameters in an arbitrary sub-module.""" 189 print(f"\n [{label}]") 190 print(f" {'param':30s} {'shape':25s} {'dtype':12s} data_ptr") 191 print(" " + "─" * 82) 192 for name, param in module.named_parameters(): 193 ptr = hex(param.data_ptr()) if param.is_cuda else "cpu" 194 print(f" {name:30s} {str(tuple(param.shape)):25s} " 195 f"{str(param.dtype):12s} {ptr}") 196 197 def visualize_weights(self, layer_idx: int = 0, cmap: str = "viridis") -> None: 198 """ 199 Visualizes the weights and biases of a specific layer as 2D slices. 200 Higher-dimensional tensors (e.g., 3D or 4D) are sliced into 2D grids. 201 """ 202 try: 203 # Targeting the specific layer in the Granite model 204 layer = self.model.model.layers[layer_idx] 205 except (AttributeError, IndexError): 206 print(f"Layer {layer_idx} not found.") 207 return 208 209 # Collect all parameters within this specific layer 210 params = {n: p for n, p in layer.named_parameters()} 211 212 if not params: 213 print(f"No parameters found in layer {layer_idx}.") 214 return 215 216 num_params = len(params) 217 fig, axes = plt.subplots(num_params, 1, figsize=(10, 10 * num_params)) 218 219 if num_params == 1: axes = [axes] 220 221 for ax, (name, param) in zip(axes, params.items()): 222 223 data = param.detach().cpu().float() 224 225 # Handle different dimensionalities 226 if data.dim() == 1: 227 # 1D (Biases/Norms) -> Reshape to a 2D row for visualization 228 data = data.unsqueeze(0) 229 elif data.dim() > 2: 230 # Multi-dimensional -> Take the first 2D slice [0, 0, :, :] 231 # This is common in MoE routers or complex head projections 232 while data.dim() > 2: 233 data = data[0] 234 235 im = ax.imshow(data.numpy(), aspect='auto', cmap=cmap) 236 ax.set_title(f"{name} | Shape: {tuple(param.shape)}") 237 fig.colorbar(im, ax=ax) 238 239 plt.tight_layout() 240 plt.show() 241 242 def visualize_activations(self, tag: str, tokenizer: Any = None, prompt: str = None) -> None: 243 """ 244 Visualizes the captured activation for a given module tag. 245 If tokenizer and prompt are provided, labels the Y-axis with tokens. 246 """ 247 if tag not in self._activations: 248 print(f"No activation found for tag: '{tag}'. Did you run_forward()?") 249 return 250 251 # Extract the tensor from the stored activation data 252 data = self._activations[tag]["tensor"].detach().cpu().float() 253 254 # Squeeze batch dimension if it's 1: (1, Seq, Hidden) -> (Seq, Hidden) 255 if data.dim() == 3 and data.size(0) == 1: 256 data = data.squeeze(0) 257 258 # If it's still > 2D (e.g. multi-head attention states), take the last 2 dims 259 while data.dim() > 2: 260 data = data[0] 261 262 fig, ax = plt.subplots(figsize=(12, 0.5 * data.size(0) + 2)) 263 im = ax.imshow(data.numpy(), aspect='auto', cmap='magma') 264 265 # Labeling tokens if possible 266 if tokenizer and prompt: 267 tokens = tokenizer.tokenize(prompt) 268 # Handle special tokens if the tokenizer adds them (like <s>) 269 if len(tokens) < data.size(0): 270 tokens = ["<bos>"] + tokens 271 272 ax.set_yticks(range(len(tokens))) 273 ax.set_yticklabels(tokens) 274 ax.set_ylabel("Tokens") 275 else: 276 ax.set_ylabel("Sequence Position") 277 278 ax.set_xlabel("Hidden Dimension (Features)") 279 ax.set_title(f"Activations: {tag}\nShape: {tuple(data.shape)}") 280 281 fig.colorbar(im, ax=ax, label="Activation Value") 282 plt.tight_layout() 283 plt.show() 284 285 # ───────────────────────────────────────────────────────────────────────── 286 # Forward hooks — capture live activations 287 # ───────────────────────────────────────────────────────────────────────── 288 289 def register_hooks(self) -> None: 290 291 self.remove_hooks() 292 293 def _make_hook(tag: str): 294 def _hook(module, inp, output): 295 tensor = output[0] if isinstance(output, tuple) else output 296 if not isinstance(tensor, torch.Tensor): 297 return 298 self._activations[tag] = { 299 "shape" : tuple(tensor.shape), 300 "dtype" : str(tensor.dtype), 301 "device" : str(tensor.device), 302 "data_ptr" : hex(tensor.data_ptr()) if tensor.is_cuda else "cpu", 303 "tensor" : tensor, 304 } 305 return _hook 306 307 # Recursively hook every module 308 for name, module in self.model.named_modules(): 309 # Skip the top-level model to avoid redundant giant hooks 310 if module is self.model or name == "": 311 continue 312 313 # Only hook leaf modules (the actual operations like Linear, RMSNorm) 314 # to avoid duplicating tensors from parent container modules. 315 if not list(module.children()): 316 h = module.register_forward_hook(_make_hook(name)) 317 self._hooks.append(h) 318 319 print(f"[register_hooks] Registered {len(self._hooks)} granular hooks.") 320 321 def remove_hooks(self) -> None: 322 """Remove all registered forward hooks.""" 323 for h in self._hooks: 324 h.remove() 325 self._hooks.clear() 326 327 def run_forward(self, tokenizer, text: str) -> torch.Tensor: 328 """ 329 Tokenize ``text``, run a single forward pass, and return the raw 330 logits tensor. Hooks (if registered) will populate ``_activations``. 331 332 Parameters 333 ---------- 334 tokenizer : transformers tokenizer 335 text : str 336 337 Returns 338 ------- 339 torch.Tensor — logits of shape [1, seq_len, vocab_size] 340 """ 341 device = next(self.model.parameters()).device 342 inputs = tokenizer(text, return_tensors="pt").to(device) 343 with torch.no_grad(): 344 outputs = self.model(**inputs, use_cache=False) 345 return outputs.logits 346 347 def inspect_activations(self) -> None: 348 for name, info in self._activations.items(): 349 self.visualize_activations( name ) 350 break 351 352 def print_activations(self) -> None: 353 """ 354 Print a summary of every activation captured by the forward hooks. 355 Must call ``register_hooks()`` + ``run_forward()`` first. 356 """ 357 print( "Activations" ) 358 if not self._activations: 359 print("[print_activations] No activations captured yet. " 360 "Call register_hooks() then run_forward().") 361 return 362 363 print("\n" + "─" * 100) 364 print(f" {'HOOK NAME':20s} {'SHAPE':35s} {'DTYPE':12s} DATA_PTR (hex)") 365 print("─" * 100) 366 for name, info in self._activations.items(): 367 print(f" {name:20s} {str(info['shape']):35s} " 368 f"{info['dtype']:12s} {info['data_ptr']}") 369 print() 370 371 def get_activation(self, hook_name: str) -> torch.Tensor: 372 """ 373 Retrieve a captured activation tensor by hook name. 374 375 Example 376 ------- 377 >>> tensor = inspector.get_activation("layer_0") 378 >>> my_cuda_ext.process(tensor.data_ptr(), tensor.numel()) 379 380 Parameters 381 ---------- 382 hook_name : str 383 One of the keys printed by ``print_activations()``. 384 385 Returns 386 ------- 387 torch.Tensor — the live activation (zero-copy) 388 """ 389 if hook_name not in self._activations: 390 raise KeyError( 391 f"No activation for '{hook_name}'. " 392 f"Available: {list(self._activations.keys())}" 393 ) 394 return self._activations[hook_name]["tensor"] 395 396 # ───────────────────────────────────────────────────────────────────────── 397 # Memory map 398 # ───────────────────────────────────────────────────────────────────────── 399 400 def print_memory_map(self, top_n: int = 0) -> None: 401 """ 402 Print a full memory map of all parameters, sorted by size descending. 403 404 Parameters 405 ---------- 406 top_n : int 407 If > 0, show only the top N largest tensors. 408 """ 409 rows = [] 410 total_bytes = 0 411 for name, param in self.model.named_parameters(): 412 nb = param.numel() * param.element_size() 413 total_bytes += nb 414 rows.append({ 415 "name" : name, 416 "shape" : tuple(param.shape), 417 "dtype" : str(param.dtype), 418 "mb" : nb / 1e6, 419 "data_ptr" : hex(param.data_ptr()) if param.is_cuda else "cpu", 420 "contiguous" : param.is_contiguous(), 421 }) 422 423 rows.sort(key=lambda r: r["mb"], reverse=True) 424 if top_n: 425 rows = rows[:top_n] 426 427 print( "Memory Map" ) 428 print("\n" + "─" * 135) 429 print(f" {'NAME':55s} {'SHAPE':22s} {'DTYPE':12s} " 430 f"{'MB':8s} {'PTR':18s} CONTIG") 431 print("─" * 135) 432 for r in rows: 433 print(f" {r['name']:55s} {str(r['shape']):22s} {r['dtype']:12s} " 434 f"{r['mb']:8.2f} {r['data_ptr']:18s} {r['contiguous']}") 435 436 print(f"\n Total parameter memory: {total_bytes / 1e9:.3f} GB\n") 437 438 # ───────────────────────────────────────────────────────────────────────── 439 # Convenience: wrap a raw CUDA pointer as a PyTorch tensor (zero-copy) 440 # ───────────────────────────────────────────────────────────────────────── 441 442 @staticmethod 443 def from_raw_ptr( 444 ptr: int, 445 shape: tuple[int, ...], 446 dtype: torch.dtype = torch.float32, 447 ) -> torch.Tensor: 448 """ 449 Wrap an external CUDA device pointer (e.g. from an OpenGL buffer mapped 450 via cudaGraphicsResourceGetMappedPointer, or from Vulkan external memory) 451 as a PyTorch tensor — zero-copy. 452 453 This is the reverse of ``get_param_ptr()``: use it when you want PyTorch 454 to operate on memory that was allocated outside of PyTorch. 455 456 Example (in a C++ extension after GL/Vulkan interop): 457 void* gl_ptr = ...; // from cudaGraphics* 458 auto t = torch::from_blob(gl_ptr, {n}, opts); // in C++ 459 460 Or from Python with a ctypes / CUDA-Python pointer: 461 t = ModelInspector.from_raw_ptr(raw_ptr, (1024,), torch.float16) 462 463 Parameters 464 ---------- 465 ptr : int Raw CUDA device pointer as a Python int. 466 shape : tuple[int] Desired tensor shape. 467 dtype : torch.dtype Element type (must match what the memory holds). 468 469 Returns 470 ------- 471 torch.Tensor — a non-owning view; the caller is responsible for lifetime. 472 """ 473 import ctypes 474 storage = torch.cuda.UntypedStorage.from_file( # type: ignore[attr-defined] 475 # Workaround: use ctypes + from_blob equivalent via storage pointer 476 # For real use, call torch::from_blob in a C++ extension instead. 477 "", False, 0 478 ) 479 # Preferred path: expose via torch._utils._rebuild_tensor_v2 or 480 # a thin C++ extension. Here we document the pattern: 481 raise NotImplementedError( 482 "from_raw_ptr is best implemented in a C++ extension using " 483 "torch::from_blob(ptr, shape, options). " 484 "See the docstring for the equivalent C++ snippet." 485 )
Wraps a HuggingFace CausalLM (or any nn.Module) and exposes utilities for inspecting weights, buffers, activations, raw CUDA pointers, and memory.
Parameters
model : torch.nn.Module A loaded HuggingFace model (or any PyTorch model on CUDA).
65 def print_architecture(self) -> None: 66 """Print the top-level module tree.""" 67 print("\n" + "═" * 80) 68 print(" MODEL ARCHITECTURE") 69 print("═" * 80) 70 print(self.model) 71 print()
Print the top-level module tree.
77 def print_parameters(self, cuda_only: bool = False) -> None: 78 """ 79 Print every named parameter (trainable weight). 80 81 Parameters 82 ---------- 83 cuda_only : bool 84 When True, only show tensors that live on a CUDA device. 85 """ 86 print( "Parameters" ) 87 print("\n" + "─" * 110) 88 print(f" {'NAME':55s} {'SHAPE':28s} {'DTYPE':12s} DEVICE") 89 print("─" * 110) 90 for name, param in self.model.named_parameters(): 91 if cuda_only and param.device.type != "cuda": 92 continue 93 print(f" {name:55s} {str(tuple(param.shape)):28s} " 94 f"{str(param.dtype):12s} {param.device}") 95 print()
Print every named parameter (trainable weight).
Parameters
cuda_only : bool When True, only show tensors that live on a CUDA device.
97 def print_buffers(self, cuda_only: bool = False) -> None: 98 """ 99 Print every named buffer (non-trainable tensors, e.g. RoPE cos/sin). 100 101 Parameters 102 ---------- 103 cuda_only : bool 104 When True, only show tensors that live on a CUDA device. 105 """ 106 print( "Buffers" ) 107 print("\n" + "─" * 110) 108 print(f" {'NAME (BUFFER)':55s} {'SHAPE':28s} {'DTYPE':12s} DEVICE") 109 print("─" * 110) 110 for name, buf in self.model.named_buffers(): 111 if cuda_only and buf.device.type != "cuda": 112 continue 113 print(f" {name:55s} {str(tuple(buf.shape)):28s} " 114 f"{str(buf.dtype):12s} {buf.device}") 115 print()
Print every named buffer (non-trainable tensors, e.g. RoPE cos/sin).
Parameters
cuda_only : bool When True, only show tensors that live on a CUDA device.
121 def print_cuda_pointers(self) -> None: 122 """ 123 Print the raw device pointer for every CUDA parameter and buffer. 124 These pointers can be passed directly to custom CUDA kernels or used 125 with cudaMemcpy for OpenGL / Vulkan interop. 126 """ 127 print( "Pointers" ) 128 print("\n" + "─" * 120) 129 print(f" {'NAME':60s} {'SHAPE':25s} {'DTYPE':12s} DATA_PTR (hex)") 130 print("─" * 120) 131 for name, tensor in list(self.model.named_parameters()) + list(self.model.named_buffers()): 132 if tensor.device.type == "cuda": 133 print(f" {name:60s} {str(tuple(tensor.shape)):25s} " 134 f"{str(tensor.dtype):12s} {hex(tensor.data_ptr())}") 135 print()
Print the raw device pointer for every CUDA parameter and buffer. These pointers can be passed directly to custom CUDA kernels or used with cudaMemcpy for OpenGL / Vulkan interop.
137 def get_param_ptr(self, param_name: str) -> int: 138 """ 139 Return the raw CUDA device pointer (as a Python int / void*) for a 140 named parameter or buffer. 141 142 Example 143 ------- 144 >>> ptr = inspector.get_param_ptr("model.layers.0.self_attn.q_proj.weight") 145 >>> my_cuda_ext.my_kernel(ptr, rows, cols) 146 147 Parameters 148 ---------- 149 param_name : str 150 Dot-separated parameter path, e.g. "model.layers.0.self_attn.q_proj.weight". 151 152 Returns 153 ------- 154 int 155 The device pointer as a Python integer. 156 """ 157 # Search parameters first, then buffers 158 lookup = dict(self.model.named_parameters()) 159 lookup.update(dict(self.model.named_buffers())) 160 if param_name not in lookup: 161 raise KeyError(f"No parameter or buffer named '{param_name}'") 162 tensor = lookup[param_name] 163 if tensor.device.type != "cuda": 164 raise RuntimeError(f"'{param_name}' is on {tensor.device}, not CUDA") 165 return tensor.data_ptr()
Return the raw CUDA device pointer (as a Python int / void*) for a named parameter or buffer.
Example
>>> ptr = inspector.get_param_ptr("model.layers.0.self_attn.q_proj.weight")
>>> my_cuda_ext.my_kernel(ptr, rows, cols)
Parameters
param_name : str Dot-separated parameter path, e.g. "model.layers.0.self_attn.q_proj.weight".
Returns
int The device pointer as a Python integer.
171 def inspect_layer(self, layer_idx: int) -> None: 172 try: 173 layer = self.model.model.layers[layer_idx] 174 except (AttributeError, IndexError) as exc: 175 print(f"[inspect_layer] Could not access layer {layer_idx}: {exc}") 176 return 177 178 print(f"\n{'═' * 80}") 179 print(f" LAYER {layer_idx} DETAIL") 180 print("═" * 80) 181 182 # Dynamically discover and print every submodule (e.g., self_attn, shared_mlp, norm) 183 for name, module in layer.named_children(): 184 self._print_submodule(name, module) 185 print()
197 def visualize_weights(self, layer_idx: int = 0, cmap: str = "viridis") -> None: 198 """ 199 Visualizes the weights and biases of a specific layer as 2D slices. 200 Higher-dimensional tensors (e.g., 3D or 4D) are sliced into 2D grids. 201 """ 202 try: 203 # Targeting the specific layer in the Granite model 204 layer = self.model.model.layers[layer_idx] 205 except (AttributeError, IndexError): 206 print(f"Layer {layer_idx} not found.") 207 return 208 209 # Collect all parameters within this specific layer 210 params = {n: p for n, p in layer.named_parameters()} 211 212 if not params: 213 print(f"No parameters found in layer {layer_idx}.") 214 return 215 216 num_params = len(params) 217 fig, axes = plt.subplots(num_params, 1, figsize=(10, 10 * num_params)) 218 219 if num_params == 1: axes = [axes] 220 221 for ax, (name, param) in zip(axes, params.items()): 222 223 data = param.detach().cpu().float() 224 225 # Handle different dimensionalities 226 if data.dim() == 1: 227 # 1D (Biases/Norms) -> Reshape to a 2D row for visualization 228 data = data.unsqueeze(0) 229 elif data.dim() > 2: 230 # Multi-dimensional -> Take the first 2D slice [0, 0, :, :] 231 # This is common in MoE routers or complex head projections 232 while data.dim() > 2: 233 data = data[0] 234 235 im = ax.imshow(data.numpy(), aspect='auto', cmap=cmap) 236 ax.set_title(f"{name} | Shape: {tuple(param.shape)}") 237 fig.colorbar(im, ax=ax) 238 239 plt.tight_layout() 240 plt.show()
Visualizes the weights and biases of a specific layer as 2D slices. Higher-dimensional tensors (e.g., 3D or 4D) are sliced into 2D grids.
242 def visualize_activations(self, tag: str, tokenizer: Any = None, prompt: str = None) -> None: 243 """ 244 Visualizes the captured activation for a given module tag. 245 If tokenizer and prompt are provided, labels the Y-axis with tokens. 246 """ 247 if tag not in self._activations: 248 print(f"No activation found for tag: '{tag}'. Did you run_forward()?") 249 return 250 251 # Extract the tensor from the stored activation data 252 data = self._activations[tag]["tensor"].detach().cpu().float() 253 254 # Squeeze batch dimension if it's 1: (1, Seq, Hidden) -> (Seq, Hidden) 255 if data.dim() == 3 and data.size(0) == 1: 256 data = data.squeeze(0) 257 258 # If it's still > 2D (e.g. multi-head attention states), take the last 2 dims 259 while data.dim() > 2: 260 data = data[0] 261 262 fig, ax = plt.subplots(figsize=(12, 0.5 * data.size(0) + 2)) 263 im = ax.imshow(data.numpy(), aspect='auto', cmap='magma') 264 265 # Labeling tokens if possible 266 if tokenizer and prompt: 267 tokens = tokenizer.tokenize(prompt) 268 # Handle special tokens if the tokenizer adds them (like <s>) 269 if len(tokens) < data.size(0): 270 tokens = ["<bos>"] + tokens 271 272 ax.set_yticks(range(len(tokens))) 273 ax.set_yticklabels(tokens) 274 ax.set_ylabel("Tokens") 275 else: 276 ax.set_ylabel("Sequence Position") 277 278 ax.set_xlabel("Hidden Dimension (Features)") 279 ax.set_title(f"Activations: {tag}\nShape: {tuple(data.shape)}") 280 281 fig.colorbar(im, ax=ax, label="Activation Value") 282 plt.tight_layout() 283 plt.show()
Visualizes the captured activation for a given module tag. If tokenizer and prompt are provided, labels the Y-axis with tokens.
289 def register_hooks(self) -> None: 290 291 self.remove_hooks() 292 293 def _make_hook(tag: str): 294 def _hook(module, inp, output): 295 tensor = output[0] if isinstance(output, tuple) else output 296 if not isinstance(tensor, torch.Tensor): 297 return 298 self._activations[tag] = { 299 "shape" : tuple(tensor.shape), 300 "dtype" : str(tensor.dtype), 301 "device" : str(tensor.device), 302 "data_ptr" : hex(tensor.data_ptr()) if tensor.is_cuda else "cpu", 303 "tensor" : tensor, 304 } 305 return _hook 306 307 # Recursively hook every module 308 for name, module in self.model.named_modules(): 309 # Skip the top-level model to avoid redundant giant hooks 310 if module is self.model or name == "": 311 continue 312 313 # Only hook leaf modules (the actual operations like Linear, RMSNorm) 314 # to avoid duplicating tensors from parent container modules. 315 if not list(module.children()): 316 h = module.register_forward_hook(_make_hook(name)) 317 self._hooks.append(h) 318 319 print(f"[register_hooks] Registered {len(self._hooks)} granular hooks.")
321 def remove_hooks(self) -> None: 322 """Remove all registered forward hooks.""" 323 for h in self._hooks: 324 h.remove() 325 self._hooks.clear()
Remove all registered forward hooks.
327 def run_forward(self, tokenizer, text: str) -> torch.Tensor: 328 """ 329 Tokenize ``text``, run a single forward pass, and return the raw 330 logits tensor. Hooks (if registered) will populate ``_activations``. 331 332 Parameters 333 ---------- 334 tokenizer : transformers tokenizer 335 text : str 336 337 Returns 338 ------- 339 torch.Tensor — logits of shape [1, seq_len, vocab_size] 340 """ 341 device = next(self.model.parameters()).device 342 inputs = tokenizer(text, return_tensors="pt").to(device) 343 with torch.no_grad(): 344 outputs = self.model(**inputs, use_cache=False) 345 return outputs.logits
Tokenize text, run a single forward pass, and return the raw
logits tensor. Hooks (if registered) will populate _activations.
Parameters
tokenizer : transformers tokenizer text : str
Returns
torch.Tensor — logits of shape [1, seq_len, vocab_size]
352 def print_activations(self) -> None: 353 """ 354 Print a summary of every activation captured by the forward hooks. 355 Must call ``register_hooks()`` + ``run_forward()`` first. 356 """ 357 print( "Activations" ) 358 if not self._activations: 359 print("[print_activations] No activations captured yet. " 360 "Call register_hooks() then run_forward().") 361 return 362 363 print("\n" + "─" * 100) 364 print(f" {'HOOK NAME':20s} {'SHAPE':35s} {'DTYPE':12s} DATA_PTR (hex)") 365 print("─" * 100) 366 for name, info in self._activations.items(): 367 print(f" {name:20s} {str(info['shape']):35s} " 368 f"{info['dtype']:12s} {info['data_ptr']}") 369 print()
Print a summary of every activation captured by the forward hooks.
Must call register_hooks() + run_forward() first.
371 def get_activation(self, hook_name: str) -> torch.Tensor: 372 """ 373 Retrieve a captured activation tensor by hook name. 374 375 Example 376 ------- 377 >>> tensor = inspector.get_activation("layer_0") 378 >>> my_cuda_ext.process(tensor.data_ptr(), tensor.numel()) 379 380 Parameters 381 ---------- 382 hook_name : str 383 One of the keys printed by ``print_activations()``. 384 385 Returns 386 ------- 387 torch.Tensor — the live activation (zero-copy) 388 """ 389 if hook_name not in self._activations: 390 raise KeyError( 391 f"No activation for '{hook_name}'. " 392 f"Available: {list(self._activations.keys())}" 393 ) 394 return self._activations[hook_name]["tensor"]
Retrieve a captured activation tensor by hook name.
Example
>>> tensor = inspector.get_activation("layer_0")
>>> my_cuda_ext.process(tensor.data_ptr(), tensor.numel())
Parameters
hook_name : str
One of the keys printed by print_activations().
Returns
torch.Tensor — the live activation (zero-copy)
400 def print_memory_map(self, top_n: int = 0) -> None: 401 """ 402 Print a full memory map of all parameters, sorted by size descending. 403 404 Parameters 405 ---------- 406 top_n : int 407 If > 0, show only the top N largest tensors. 408 """ 409 rows = [] 410 total_bytes = 0 411 for name, param in self.model.named_parameters(): 412 nb = param.numel() * param.element_size() 413 total_bytes += nb 414 rows.append({ 415 "name" : name, 416 "shape" : tuple(param.shape), 417 "dtype" : str(param.dtype), 418 "mb" : nb / 1e6, 419 "data_ptr" : hex(param.data_ptr()) if param.is_cuda else "cpu", 420 "contiguous" : param.is_contiguous(), 421 }) 422 423 rows.sort(key=lambda r: r["mb"], reverse=True) 424 if top_n: 425 rows = rows[:top_n] 426 427 print( "Memory Map" ) 428 print("\n" + "─" * 135) 429 print(f" {'NAME':55s} {'SHAPE':22s} {'DTYPE':12s} " 430 f"{'MB':8s} {'PTR':18s} CONTIG") 431 print("─" * 135) 432 for r in rows: 433 print(f" {r['name']:55s} {str(r['shape']):22s} {r['dtype']:12s} " 434 f"{r['mb']:8.2f} {r['data_ptr']:18s} {r['contiguous']}") 435 436 print(f"\n Total parameter memory: {total_bytes / 1e9:.3f} GB\n")
Print a full memory map of all parameters, sorted by size descending.
Parameters
top_n : int If > 0, show only the top N largest tensors.
442 @staticmethod 443 def from_raw_ptr( 444 ptr: int, 445 shape: tuple[int, ...], 446 dtype: torch.dtype = torch.float32, 447 ) -> torch.Tensor: 448 """ 449 Wrap an external CUDA device pointer (e.g. from an OpenGL buffer mapped 450 via cudaGraphicsResourceGetMappedPointer, or from Vulkan external memory) 451 as a PyTorch tensor — zero-copy. 452 453 This is the reverse of ``get_param_ptr()``: use it when you want PyTorch 454 to operate on memory that was allocated outside of PyTorch. 455 456 Example (in a C++ extension after GL/Vulkan interop): 457 void* gl_ptr = ...; // from cudaGraphics* 458 auto t = torch::from_blob(gl_ptr, {n}, opts); // in C++ 459 460 Or from Python with a ctypes / CUDA-Python pointer: 461 t = ModelInspector.from_raw_ptr(raw_ptr, (1024,), torch.float16) 462 463 Parameters 464 ---------- 465 ptr : int Raw CUDA device pointer as a Python int. 466 shape : tuple[int] Desired tensor shape. 467 dtype : torch.dtype Element type (must match what the memory holds). 468 469 Returns 470 ------- 471 torch.Tensor — a non-owning view; the caller is responsible for lifetime. 472 """ 473 import ctypes 474 storage = torch.cuda.UntypedStorage.from_file( # type: ignore[attr-defined] 475 # Workaround: use ctypes + from_blob equivalent via storage pointer 476 # For real use, call torch::from_blob in a C++ extension instead. 477 "", False, 0 478 ) 479 # Preferred path: expose via torch._utils._rebuild_tensor_v2 or 480 # a thin C++ extension. Here we document the pattern: 481 raise NotImplementedError( 482 "from_raw_ptr is best implemented in a C++ extension using " 483 "torch::from_blob(ptr, shape, options). " 484 "See the docstring for the equivalent C++ snippet." 485 )
Wrap an external CUDA device pointer (e.g. from an OpenGL buffer mapped via cudaGraphicsResourceGetMappedPointer, or from Vulkan external memory) as a PyTorch tensor — zero-copy.
This is the reverse of get_param_ptr(): use it when you want PyTorch
to operate on memory that was allocated outside of PyTorch.
Example (in a C++ extension after GL/Vulkan interop): void* gl_ptr = ...; // from cudaGraphics* auto t = torch::from_blob(gl_ptr, {n}, opts); // in C++
Or from Python with a ctypes / CUDA-Python pointer: t = ModelInspector.from_raw_ptr(raw_ptr, (1024,), torch.float16)
Parameters
ptr : int Raw CUDA device pointer as a Python int. shape : tuple[int] Desired tensor shape. dtype : torch.dtype Element type (must match what the memory holds).
Returns
torch.Tensor — a non-owning view; the caller is responsible for lifetime.