NithanNguyen commited on
Commit
9c5d0ee
·
1 Parent(s): ce9045e

ân - script tính số Parameters

Browse files
Files changed (2) hide show
  1. count_params_act.py +154 -0
  2. count_params_dp.py +215 -0
count_params_act.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Đếm số tham số (Parameters) trực tiếp từ file model.safetensors của ACT
4
+ (Action Chunking Transformer).
5
+
6
+ Giống count_params_from_file.py (dùng cho Diffusion Policy): chỉ đọc header
7
+ JSON (vài KB đầu file) — KHÔNG cần load toàn bộ model vào RAM/VRAM, KHÔNG cần
8
+ cài PyTorch.
9
+
10
+ Thêm so với bản gốc: phân nhóm tham số theo module (backbone thị giác /
11
+ attention / feed-forward / embeddings / ...) — đặc thù cho kiến trúc ACT,
12
+ để biết phần nào đang chiếm dung lượng khi so sánh các checkpoint (vd. v2 vs v7).
13
+
14
+ Cách dùng:
15
+ python3 count_params_act.py /duong/dan/toi/model.safetensors
16
+ python3 count_params_act.py model_v2.safetensors model_v7.safetensors # so sánh nhiều file
17
+ """
18
+ import json
19
+ import struct
20
+ import sys
21
+ from math import prod
22
+
23
+
24
+ # Thứ tự nhóm cũng là thứ tự hiển thị trong bảng kết quả.
25
+ # Mỗi rule là (tên_nhóm, hàm_kiểm_tra(tên_tensor) -> bool). Rule ở trên được
26
+ # ưu tiên trước — vd. "vae_encoder.layers" phải khớp trước "vae_encoder" chung chung.
27
+ GROUP_RULES = [
28
+ ("Backbone thị giác (ResNet18)", lambda k: "backbone" in k),
29
+ ("VAE encoder — các tầng Transformer", lambda k: k.startswith("model.vae_encoder.layers")),
30
+ ("VAE encoder — projections/embeddings", lambda k: k.startswith("model.vae_encoder") and "layers" not in k),
31
+ ("Transformer encoder — các tầng", lambda k: k.startswith("model.encoder.layers")),
32
+ ("Transformer encoder — projections/embeddings", lambda k: k.startswith("model.encoder") and "layers" not in k),
33
+ ("Transformer decoder — các tầng", lambda k: k.startswith("model.decoder.layers")),
34
+ ("Transformer decoder — projections/embeddings", lambda k: k.startswith("model.decoder") and "layers" not in k),
35
+ ("Action head (đầu ra hành động)", lambda k: "action_head" in k),
36
+ ]
37
+ GROUP_OTHER = "Khác (chưa phân loại)"
38
+
39
+
40
+ def group_of(tensor_name: str) -> str:
41
+ for group_name, match in GROUP_RULES:
42
+ if match(tensor_name):
43
+ return group_name
44
+ return GROUP_OTHER
45
+
46
+
47
+ def load_header(path: str) -> dict:
48
+ with open(path, "rb") as f:
49
+ # 8 byte đầu = độ dài (uint64, little-endian) của phần header JSON
50
+ header_len = struct.unpack("<Q", f.read(8))[0]
51
+ header = json.loads(f.read(header_len))
52
+ # "__metadata__" không phải tensor, phải loại ra trước khi đếm
53
+ header.pop("__metadata__", None)
54
+ return header
55
+
56
+
57
+ def count_params(path: str) -> dict:
58
+ header = load_header(path)
59
+
60
+ total_params = 0
61
+ total_bytes = 0
62
+ dtype_count: dict = {}
63
+ group_params: dict = {}
64
+ group_tensor_count: dict = {}
65
+
66
+ for name, info in header.items():
67
+ shape = info["shape"]
68
+ n_elem = prod(shape) if shape else 1 # tensor 0-chiều (scalar) vẫn tính là 1
69
+ total_params += n_elem
70
+
71
+ start, end = info["data_offsets"]
72
+ total_bytes += end - start
73
+
74
+ dt = info["dtype"]
75
+ dtype_count[dt] = dtype_count.get(dt, 0) + 1
76
+
77
+ g = group_of(name)
78
+ group_params[g] = group_params.get(g, 0) + n_elem
79
+ group_tensor_count[g] = group_tensor_count.get(g, 0) + 1
80
+
81
+ return {
82
+ "path": path,
83
+ "n_tensors": len(header),
84
+ "dtype_count": dtype_count,
85
+ "total_params": total_params,
86
+ "total_bytes": total_bytes,
87
+ "group_params": group_params,
88
+ "group_tensor_count": group_tensor_count,
89
+ }
90
+
91
+
92
+ def print_report(r: dict) -> None:
93
+ print(f"File: {r['path']}")
94
+ print(f"Số tensor: {r['n_tensors']}")
95
+ print(f"Kiểu dữ liệu: {r['dtype_count']}")
96
+ print(f"Tổng tham số: {r['total_params']:,}")
97
+ print(f"Kích thước data: {r['total_bytes']:,} byte ({r['total_bytes']/1024**2:.1f} MiB)")
98
+ print()
99
+ print("--- Phân theo module ---")
100
+ header = f"{'Module':46s}{'#tensor':>9s}{'Tham số':>14s}{'%':>7s}"
101
+ print(header)
102
+ print("-" * len(header))
103
+ ordered = [g for g, _ in GROUP_RULES] + [GROUP_OTHER]
104
+ for g in ordered:
105
+ p = r["group_params"].get(g, 0)
106
+ if p == 0:
107
+ continue
108
+ n = r["group_tensor_count"].get(g, 0)
109
+ pct = 100 * p / r["total_params"] if r["total_params"] else 0
110
+ print(f"{g:46s}{n:9d}{p:14,d}{pct:6.1f}%")
111
+ print()
112
+
113
+ # Ước tính VRAM lúc train (FP32): trọng số + gradient + 2 hệ số Adam = 4x.
114
+ # Đây chỉ là phần do THAM SỐ đóng góp — activations (phụ thuộc batch_size
115
+ # và số camera) thường lớn hơn nhiều và KHÔNG được tính ở đây.
116
+ w_mib = r["total_params"] * 4 / 1024**2
117
+ print("--- Ước tính VRAM do THAM SỐ đóng góp (FP32, chưa tính activations) ---")
118
+ print(f"Trọng số: {w_mib:8.1f} MiB")
119
+ print(f"Inference (chỉ trọng số): {w_mib:8.1f} MiB")
120
+ print(f"Training (+grad +2x Adam): {4*w_mib:8.1f} MiB (~{4*w_mib/1024:.2f} GiB)")
121
+ print("Lưu ý: activations (ảnh camera) thường lớn hơn khoản này nhiều lần khi train.")
122
+
123
+
124
+ def print_comparison(reports: list) -> None:
125
+ print("=== So sánh nhiều file ===")
126
+ header = f"{'File':30s}{'Tổng tham số':>16s}{'MiB':>10s}"
127
+ print(header)
128
+ print("-" * len(header))
129
+ for r in reports:
130
+ name = r["path"].split("/")[-1]
131
+ print(f"{name:30s}{r['total_params']:16,d}{r['total_bytes']/1024**2:10.1f}")
132
+ if len(reports) == 2:
133
+ a, b = reports
134
+ if b["total_params"]:
135
+ print(f"\nTỉ lệ {a['path'].split('/')[-1]} / {b['path'].split('/')[-1]}: "
136
+ f"{a['total_params']/b['total_params']:.3f}x")
137
+ print()
138
+
139
+
140
+ if __name__ == "__main__":
141
+ if len(sys.argv) < 2:
142
+ print("Dùng: python3 count_params_act.py <model1.safetensors> [model2.safetensors ...]")
143
+ sys.exit(1)
144
+
145
+ all_reports = []
146
+ for p in sys.argv[1:]:
147
+ rpt = count_params(p)
148
+ all_reports.append(rpt)
149
+ print_report(rpt)
150
+ print("=" * 70)
151
+ print()
152
+
153
+ if len(all_reports) > 1:
154
+ print_comparison(all_reports)
count_params_dp.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Đếm số tham số (Parameters) trực tiếp từ file model.safetensors của
4
+ Diffusion Policy (LeRobot).
5
+
6
+ Chỉ đọc header JSON (vài KB đầu file) — KHÔNG cần load toàn bộ model vào
7
+ RAM/VRAM, KHÔNG cần cài PyTorch. An toàn với file nhiều GB.
8
+
9
+ Thêm so với bản gốc (đồng bộ với count_params_act.py):
10
+ - Phân nhóm tham số theo module (vision encoder / U-Net down-mid-up / FiLM ...)
11
+ - Ước tính VRAM do THAM SỐ đóng góp (inference và training)
12
+ - Tách riêng tham số học được và buffer (BatchNorm running stats) — vì
13
+ safetensors lưu cả hai, nên "tổng phần tử" > "số tham số thực"
14
+
15
+ Cách dùng:
16
+ python3 count_params_from_file.py /duong/dan/toi/model.safetensors
17
+ python3 count_params_from_file.py dp_v1.safetensors dp_v2.safetensors # so sánh
18
+ """
19
+ import json
20
+ import struct
21
+ import sys
22
+ from math import prod
23
+
24
+
25
+ # Số byte mỗi phần tử theo dtype của safetensors — dùng để ước tính VRAM
26
+ # cho đúng độ chính xác mà model đang được lưu (FP32 / FP16 / BF16).
27
+ DTYPE_BYTES = {
28
+ "F64": 8, "F32": 4, "F16": 2, "BF16": 2,
29
+ "I64": 8, "I32": 4, "I16": 2, "I8": 1, "U8": 1, "BOOL": 1,
30
+ }
31
+
32
+ # Thứ tự nhóm cũng là thứ tự hiển thị trong bảng kết quả.
33
+ # Mỗi rule là (tên_nhóm, hàm_kiểm_tra(tên_tensor) -> bool). Rule ở trên được
34
+ # ưu tiên trước — vd. "rgb_encoder ... backbone" phải khớp trước "rgb_encoder" chung.
35
+ GROUP_RULES = [
36
+ ("Vision encoder — backbone ResNet-18", lambda k: "rgb_encoder" in k and "backbone" in k),
37
+ ("Vision encoder — SpatialSoftmax + projection", lambda k: "rgb_encoder" in k),
38
+ ("U-Net — diffusion_step_encoder", lambda k: "diffusion_step_encoder" in k),
39
+ ("U-Net — down_modules (nhánh nén)", lambda k: "down_modules" in k),
40
+ ("U-Net — mid_modules (bottleneck)", lambda k: "mid_modules" in k),
41
+ ("U-Net — up_modules (nhánh giãn)", lambda k: "up_modules" in k),
42
+ ("U-Net — final_conv (đầu ra action)", lambda k: "final_conv" in k),
43
+ ("Normalization buffers (mean/std/min/max)", lambda k: "normalize" in k.lower()),
44
+ ]
45
+ GROUP_OTHER = "Khác (chưa phân loại)"
46
+
47
+
48
+ # Nhóm cắt ngang: FiLM conditioning nằm rải trong mọi residual block của U-Net,
49
+ # nên không thể tách bằng GROUP_RULES (sẽ trùng với down/mid/up). Thống kê riêng.
50
+ def is_film(name: str) -> bool:
51
+ return "cond_encoder" in name
52
+
53
+
54
+ def is_buffer(name: str, dtype: str) -> bool:
55
+ """Buffer = tensor được lưu trong file nhưng KHÔNG phải tham số học được."""
56
+ return (
57
+ "running_mean" in name
58
+ or "running_var" in name
59
+ or "num_batches_tracked" in name
60
+ or dtype in ("I64", "I32", "BOOL")
61
+ )
62
+
63
+
64
+ def group_of(tensor_name: str) -> str:
65
+ for group_name, match in GROUP_RULES:
66
+ if match(tensor_name):
67
+ return group_name
68
+ return GROUP_OTHER
69
+
70
+
71
+ def load_header(path: str) -> dict:
72
+ with open(path, "rb") as f:
73
+ # 8 byte đầu = độ dài (uint64, little-endian) của phần header JSON
74
+ header_len = struct.unpack("<Q", f.read(8))[0]
75
+ header = json.loads(f.read(header_len))
76
+ # "__metadata__" không phải tensor, phải loại ra trước khi đếm
77
+ header.pop("__metadata__", None)
78
+ return header
79
+
80
+
81
+ def count_params(path: str) -> dict:
82
+ header = load_header(path)
83
+
84
+ total_elems = 0 # mọi phần tử được lưu trong file
85
+ learnable = 0 # tham số học được (đã loại buffer)
86
+ buffers = 0 # BN running stats, num_batches_tracked, ...
87
+ total_bytes = 0
88
+ film_params = 0
89
+ dtype_count: dict = {}
90
+ group_params: dict = {}
91
+ group_tensor_count: dict = {}
92
+
93
+ for name, info in header.items():
94
+ shape = info["shape"]
95
+ n_elem = prod(shape) if shape else 1 # tensor 0-chiều (scalar) vẫn tính là 1
96
+ dt = info["dtype"]
97
+
98
+ total_elems += n_elem
99
+ if is_buffer(name, dt):
100
+ buffers += n_elem
101
+ else:
102
+ learnable += n_elem
103
+ if is_film(name):
104
+ film_params += n_elem
105
+
106
+ start, end = info["data_offsets"]
107
+ total_bytes += end - start
108
+
109
+ dtype_count[dt] = dtype_count.get(dt, 0) + 1
110
+
111
+ g = group_of(name)
112
+ group_params[g] = group_params.get(g, 0) + n_elem
113
+ group_tensor_count[g] = group_tensor_count.get(g, 0) + 1
114
+
115
+ # dtype chiếm đa số — dùng để ước tính VRAM đúng với độ chính xác đang lưu
116
+ dominant_dtype = max(dtype_count, key=dtype_count.get) if dtype_count else "F32"
117
+ bytes_per_elem = DTYPE_BYTES.get(dominant_dtype, 4)
118
+
119
+ return {
120
+ "path": path,
121
+ "n_tensors": len(header),
122
+ "dtype_count": dtype_count,
123
+ "dominant_dtype": dominant_dtype,
124
+ "bytes_per_elem": bytes_per_elem,
125
+ "total_elems": total_elems,
126
+ "learnable": learnable,
127
+ "buffers": buffers,
128
+ "film_params": film_params,
129
+ "total_bytes": total_bytes,
130
+ "group_params": group_params,
131
+ "group_tensor_count": group_tensor_count,
132
+ }
133
+
134
+
135
+ def print_report(r: dict) -> None:
136
+ print(f"File: {r['path']}")
137
+ print(f"Số tensor: {r['n_tensors']}")
138
+ print(f"Kiểu dữ liệu: {r['dtype_count']}")
139
+ print(f"Tổng phần tử: {r['total_elems']:,}")
140
+ print(f" ├─ tham số học được: {r['learnable']:,}")
141
+ print(f" └─ buffer (BN stats): {r['buffers']:,}")
142
+ print(f"Kích thước data: {r['total_bytes']:,} byte "
143
+ f"({r['total_bytes']/1024**2:.1f} MiB / {r['total_bytes']/1e9:.3f} GB)")
144
+ print()
145
+
146
+ print("--- Phân theo module ---")
147
+ head = f"{'Module':46s}{'#tensor':>9s}{'Tham số':>14s}{'%':>7s}"
148
+ print(head)
149
+ print("-" * len(head))
150
+ ordered = [g for g, _ in GROUP_RULES] + [GROUP_OTHER]
151
+ for g in ordered:
152
+ p = r["group_params"].get(g, 0)
153
+ if p == 0:
154
+ continue
155
+ n = r["group_tensor_count"].get(g, 0)
156
+ pct = 100 * p / r["total_elems"] if r["total_elems"] else 0
157
+ print(f"{g:46s}{n:9d}{p:14,d}{pct:6.1f}%")
158
+
159
+ if r["film_params"]:
160
+ pct = 100 * r["film_params"] / r["total_elems"]
161
+ print("-" * len(head))
162
+ print(f"{'(cắt ngang) FiLM cond_encoder':46s}"
163
+ f"{'':>9s}{r['film_params']:14,d}{pct:6.1f}%")
164
+ print(" ^ nằm rải trong down/mid/up ở trên — KHÔNG cộng thêm vào tổng")
165
+ print()
166
+
167
+ # Ước tính VRAM lúc train: trọng số + gradient + 2 hệ số Adam (m, v) = 4x.
168
+ # Đây chỉ là phần do THAM SỐ đóng góp — activations (phụ thuộc batch_size,
169
+ # số camera, độ phân giải ảnh) thường lớn hơn nhiều và KHÔNG được tính ở đây.
170
+ b = r["bytes_per_elem"]
171
+ w_mib = r["total_elems"] * b / 1024**2
172
+ print(f"--- Ước tính VRAM do THAM SỐ đóng góp ({r['dominant_dtype']}, chưa tính activations) ---")
173
+ print(f"Trọng số: {w_mib:8.1f} MiB (~{w_mib/1024:.2f} GiB)")
174
+ print(f"Inference (chỉ trọng số): {w_mib:8.1f} MiB (~{w_mib/1024:.2f} GiB)")
175
+ print(f"Training (+grad +2x Adam): {4*w_mib:8.1f} MiB (~{4*w_mib/1024:.2f} GiB)")
176
+ print()
177
+ print("Lưu ý khi đọc con số trên:")
178
+ print(" - Activations (ảnh camera qua ResNet-18) thường lớn hơn khoản này")
179
+ print(" nhiều lần khi train; đây là sàn dưới, KHÔNG phải tổng VRAM.")
180
+ print(" - Isaac Sim chạy song song cũng chiếm VRAM đáng kể (render 4 camera).")
181
+ print(" - Diffusion Policy lặp U-Net num_inference_steps lần mỗi bước inference:")
182
+ print(" không tăng đỉnh VRAM nhưng gây phân mảnh bộ nhớ -> OOM sớm hơn lý thuyết.")
183
+
184
+
185
+ def print_comparison(reports: list) -> None:
186
+ print("=== So sánh nhiều file ===")
187
+ head = f"{'File':30s}{'Tổng phần tử':>16s}{'MiB':>10s}"
188
+ print(head)
189
+ print("-" * len(head))
190
+ for r in reports:
191
+ name = r["path"].split("/")[-1]
192
+ print(f"{name:30s}{r['total_elems']:16,d}{r['total_bytes']/1024**2:10.1f}")
193
+ if len(reports) == 2:
194
+ a, b = reports
195
+ if b["total_elems"]:
196
+ print(f"\nTỉ lệ {a['path'].split('/')[-1]} / {b['path'].split('/')[-1]}: "
197
+ f"{a['total_elems']/b['total_elems']:.3f}x")
198
+ print()
199
+
200
+
201
+ if __name__ == "__main__":
202
+ if len(sys.argv) < 2:
203
+ print("Dùng: python3 count_params_from_file.py <model1.safetensors> [model2.safetensors ...]")
204
+ sys.exit(1)
205
+
206
+ all_reports = []
207
+ for p in sys.argv[1:]:
208
+ rpt = count_params(p)
209
+ all_reports.append(rpt)
210
+ print_report(rpt)
211
+ print("=" * 70)
212
+ print()
213
+
214
+ if len(all_reports) > 1:
215
+ print_comparison(all_reports)