main
py 136 lines 4.63 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Model inspection utilities for vLLM."""
4
5 import torch.nn as nn
6
7
8 def _get_module_info(module: nn.Module) -> str:
9 """Get info string for a module."""
10 class_name = type(module).__name__
11 parts = []
12
13 # Add quant_method if present
14 quant_method = getattr(module, "quant_method", None)
15 if quant_method is not None:
16 quant_name = type(quant_method).__name__
17 # For CompressedTensors, show the underlying scheme instead
18 scheme = getattr(module, "scheme", None)
19 if scheme is not None:
20 quant_name = type(scheme).__name__
21 # Skip unquantized methods
22 if "Unquantized" not in quant_name:
23 parts.append(f"quant={quant_name}")
24
25 # If module has extra_repr, use it
26 if hasattr(module, "extra_repr"):
27 parts.append(module.extra_repr().replace("\n", ""))
28
29 if parts:
30 return f"{class_name}({', '.join(parts)})"
31
32 # For unknown modules, use the default PyTorch repr
33 return str(module)
34
35
36 def _get_child_signature(child: nn.Module) -> str:
37 """Get a signature for a child module to detect duplicates."""
38 lines = []
39 for name, submodule in child.named_modules():
40 lines.append(f"{name}:{_get_module_info(submodule)}")
41 return "\n".join(lines)
42
43
44 def _format_index_ranges(indices: list[int]) -> str:
45 """Format indices into range notation (e.g., [0,1,2,4,5,6] -> '0-2, 4-6')."""
46 indices = sorted(indices)
47 ranges = []
48 start = end = indices[0]
49
50 for idx in indices[1:]:
51 if idx == end + 1:
52 end = idx
53 else:
54 ranges.append(str(start) if start == end else f"{start}-{end}")
55 start = end = idx
56
57 ranges.append(str(start) if start == end else f"{start}-{end}")
58 return ", ".join(ranges)
59
60
61 def _format_module_tree(
62 module: nn.Module,
63 name: str = "",
64 indent: int = 0,
65 ) -> list[str]:
66 """Format a module tree with indentation, grouping identical layers.
67
68 Produces output like:
69 (layers): ModuleList(
70 (0-27, 29-47): 47 x LlamaDecoderLayer(
71 ...
72 )
73 (28, 48): 2 x DifferentDecoderLayer(
74 ...
75 )
76 )
77 """
78 lines = []
79 prefix = " " * indent
80 children = list(module.named_children())
81
82 # Leaf node - just output the module info
83 if not children:
84 info = _get_module_info(module)
85 lines.append(f"{prefix}({name}): {info}" if name else f"{prefix}{info}")
86 return lines
87
88 # Non-leaf node - output opening line and recurse into children
89 info = _get_module_info(module)
90 lines.append(f"{prefix}({name}): {info}(" if name else f"{prefix}{info}(")
91
92 # Separate numbered children (e.g., "0", "1") from named ones (e.g., "norm")
93 numbered: list[tuple[int, nn.Module]] = []
94 non_numbered: list[tuple[str, nn.Module]] = []
95 for child_name, child_module in children:
96 try:
97 numbered.append((int(child_name), child_module))
98 except ValueError:
99 non_numbered.append((child_name, child_module))
100
101 # Group numbered children by structure signature to collapse identical layers
102 # e.g., layers 0-27 and 29-47 with same structure become "(0-27, 29-47): 47 x"
103 if numbered:
104 sig_to_group: dict[str, list[tuple[int, nn.Module]]] = {}
105 for idx, child_module in numbered:
106 sig = _get_child_signature(child_module)
107 sig_to_group.setdefault(sig, []).append((idx, child_module))
108
109 # Output groups sorted by first index
110 for group in sorted(sig_to_group.values(), key=lambda g: g[0][0]):
111 indices = [idx for idx, _ in group]
112 representative = group[0][1]
113 child_lines = _format_module_tree(representative, "", indent + 1)
114 first_line = child_lines[0].lstrip()
115 child_prefix = " " * (indent + 1)
116
117 if len(indices) > 1:
118 range_str = _format_index_ranges(indices)
119 child_lines[0] = (
120 f"{child_prefix}({range_str}): {len(indices)} x {first_line}"
121 )
122 else:
123 child_lines[0] = f"{child_prefix}({indices[0]}): {first_line}"
124 lines.extend(child_lines)
125
126 # Output non-numbered children (e.g., "embed_tokens", "norm")
127 for child_name, child_module in non_numbered:
128 lines.extend(_format_module_tree(child_module, child_name, indent + 1))
129
130 lines.append(f"{prefix})")
131 return lines
132
133
134 def format_model_inspection(model: nn.Module) -> str:
135 """Format a model into a transformers-style hierarchical string."""
136 return "\n".join(_format_module_tree(model))