| |
| """ |
| Đếm số tham số (Parameters) trực tiếp từ file model.safetensors của ACT |
| (Action Chunking Transformer). |
| |
| Giống count_params_from_file.py (dùng cho Diffusion Policy): chỉ đọc header |
| JSON (vài KB đầu file) — KHÔNG cần load toàn bộ model vào RAM/VRAM, KHÔNG cần |
| cài PyTorch. |
| |
| Thêm so với bản gốc: phân nhóm tham số theo module (backbone thị giác / |
| attention / feed-forward / embeddings / ...) — đặc thù cho kiến trúc ACT, |
| để biết phần nào đang chiếm dung lượng khi so sánh các checkpoint (vd. v2 vs v7). |
| |
| Cách dùng: |
| python3 count_params_act.py /duong/dan/toi/model.safetensors |
| python3 count_params_act.py model_v2.safetensors model_v7.safetensors # so sánh nhiều file |
| """ |
| import json |
| import struct |
| import sys |
| from math import prod |
|
|
|
|
| |
| |
| |
| GROUP_RULES = [ |
| ("Backbone thị giác (ResNet18)", lambda k: "backbone" in k), |
| ("VAE encoder — các tầng Transformer", lambda k: k.startswith("model.vae_encoder.layers")), |
| ("VAE encoder — projections/embeddings", lambda k: k.startswith("model.vae_encoder") and "layers" not in k), |
| ("Transformer encoder — các tầng", lambda k: k.startswith("model.encoder.layers")), |
| ("Transformer encoder — projections/embeddings", lambda k: k.startswith("model.encoder") and "layers" not in k), |
| ("Transformer decoder — các tầng", lambda k: k.startswith("model.decoder.layers")), |
| ("Transformer decoder — projections/embeddings", lambda k: k.startswith("model.decoder") and "layers" not in k), |
| ("Action head (đầu ra hành động)", lambda k: "action_head" in k), |
| ] |
| GROUP_OTHER = "Khác (chưa phân loại)" |
|
|
|
|
| def group_of(tensor_name: str) -> str: |
| for group_name, match in GROUP_RULES: |
| if match(tensor_name): |
| return group_name |
| return GROUP_OTHER |
|
|
|
|
| def load_header(path: str) -> dict: |
| with open(path, "rb") as f: |
| |
| header_len = struct.unpack("<Q", f.read(8))[0] |
| header = json.loads(f.read(header_len)) |
| |
| header.pop("__metadata__", None) |
| return header |
|
|
|
|
| def count_params(path: str) -> dict: |
| header = load_header(path) |
|
|
| total_params = 0 |
| total_bytes = 0 |
| dtype_count: dict = {} |
| group_params: dict = {} |
| group_tensor_count: dict = {} |
|
|
| for name, info in header.items(): |
| shape = info["shape"] |
| n_elem = prod(shape) if shape else 1 |
| total_params += n_elem |
|
|
| start, end = info["data_offsets"] |
| total_bytes += end - start |
|
|
| dt = info["dtype"] |
| dtype_count[dt] = dtype_count.get(dt, 0) + 1 |
|
|
| g = group_of(name) |
| group_params[g] = group_params.get(g, 0) + n_elem |
| group_tensor_count[g] = group_tensor_count.get(g, 0) + 1 |
|
|
| return { |
| "path": path, |
| "n_tensors": len(header), |
| "dtype_count": dtype_count, |
| "total_params": total_params, |
| "total_bytes": total_bytes, |
| "group_params": group_params, |
| "group_tensor_count": group_tensor_count, |
| } |
|
|
|
|
| def print_report(r: dict) -> None: |
| print(f"File: {r['path']}") |
| print(f"Số tensor: {r['n_tensors']}") |
| print(f"Kiểu dữ liệu: {r['dtype_count']}") |
| print(f"Tổng tham số: {r['total_params']:,}") |
| print(f"Kích thước data: {r['total_bytes']:,} byte ({r['total_bytes']/1024**2:.1f} MiB)") |
| print() |
| print("--- Phân theo module ---") |
| header = f"{'Module':46s}{'#tensor':>9s}{'Tham số':>14s}{'%':>7s}" |
| print(header) |
| print("-" * len(header)) |
| ordered = [g for g, _ in GROUP_RULES] + [GROUP_OTHER] |
| for g in ordered: |
| p = r["group_params"].get(g, 0) |
| if p == 0: |
| continue |
| n = r["group_tensor_count"].get(g, 0) |
| pct = 100 * p / r["total_params"] if r["total_params"] else 0 |
| print(f"{g:46s}{n:9d}{p:14,d}{pct:6.1f}%") |
| print() |
|
|
| |
| |
| |
| w_mib = r["total_params"] * 4 / 1024**2 |
| print("--- Ước tính VRAM do THAM SỐ đóng góp (FP32, chưa tính activations) ---") |
| print(f"Trọng số: {w_mib:8.1f} MiB") |
| print(f"Inference (chỉ trọng số): {w_mib:8.1f} MiB") |
| print(f"Training (+grad +2x Adam): {4*w_mib:8.1f} MiB (~{4*w_mib/1024:.2f} GiB)") |
| print("Lưu ý: activations (ảnh camera) thường lớn hơn khoản này nhiều lần khi train.") |
|
|
|
|
| def print_comparison(reports: list) -> None: |
| print("=== So sánh nhiều file ===") |
| header = f"{'File':30s}{'Tổng tham số':>16s}{'MiB':>10s}" |
| print(header) |
| print("-" * len(header)) |
| for r in reports: |
| name = r["path"].split("/")[-1] |
| print(f"{name:30s}{r['total_params']:16,d}{r['total_bytes']/1024**2:10.1f}") |
| if len(reports) == 2: |
| a, b = reports |
| if b["total_params"]: |
| print(f"\nTỉ lệ {a['path'].split('/')[-1]} / {b['path'].split('/')[-1]}: " |
| f"{a['total_params']/b['total_params']:.3f}x") |
| print() |
|
|
|
|
| if __name__ == "__main__": |
| if len(sys.argv) < 2: |
| print("Dùng: python3 count_params_act.py <model1.safetensors> [model2.safetensors ...]") |
| sys.exit(1) |
|
|
| all_reports = [] |
| for p in sys.argv[1:]: |
| rpt = count_params(p) |
| all_reports.append(rpt) |
| print_report(rpt) |
| print("=" * 70) |
| print() |
|
|
| if len(all_reports) > 1: |
| print_comparison(all_reports) |
|
|