Jongsu Liam Kim commited on
Commit
32fa850
·
1 Parent(s): 2b546bf

feat: convert .pt checkpoints to HuggingFace-style state_dict + config

Browse files

Convert pickle-based full model .pt files to safetensors + config.json
structure for PyTorch version compatibility and safer loading.

Datasets: MSL, SMAP, SWaT, WADI

MSL/config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "AnomalyBERT",
3
+ "input_d_data": 55,
4
+ "output_d_data": 1,
5
+ "patch_size": 2,
6
+ "d_embed": 512,
7
+ "hidden_dim_rate": 4.0,
8
+ "max_seq_len": 512,
9
+ "positional_encoding": null,
10
+ "relative_position_embedding": true,
11
+ "transformer_n_layer": 6,
12
+ "transformer_n_head": 8,
13
+ "dropout": 0.1
14
+ }
MSL/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:732a51712cd2a1402a0c18cba2b895fa5daf437229c561801bc875b50dc2ade4
3
+ size 80314744
SMAP/config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "AnomalyBERT",
3
+ "input_d_data": 25,
4
+ "output_d_data": 1,
5
+ "patch_size": 4,
6
+ "d_embed": 512,
7
+ "hidden_dim_rate": 4.0,
8
+ "max_seq_len": 512,
9
+ "positional_encoding": null,
10
+ "relative_position_embedding": true,
11
+ "transformer_n_layer": 6,
12
+ "transformer_n_head": 8,
13
+ "dropout": 0.1
14
+ }
SMAP/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:139feda8feff168b1eeb2e4bb977e93bbe25bb46b7ebff31b9073dbd3e1b3a08
3
+ size 80310656
SWaT/config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "AnomalyBERT",
3
+ "input_d_data": 50,
4
+ "output_d_data": 1,
5
+ "patch_size": 14,
6
+ "d_embed": 512,
7
+ "hidden_dim_rate": 4.0,
8
+ "max_seq_len": 512,
9
+ "positional_encoding": null,
10
+ "relative_position_embedding": true,
11
+ "transformer_n_layer": 6,
12
+ "transformer_n_head": 8,
13
+ "dropout": 0.1
14
+ }
SWaT/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b80bbf2b36004c9061957b408765645262ddbf129edab0b4baab148efcb78b09
3
+ size 81621424
WADI/config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "AnomalyBERT",
3
+ "input_d_data": 122,
4
+ "output_d_data": 1,
5
+ "patch_size": 8,
6
+ "d_embed": 512,
7
+ "hidden_dim_rate": 4.0,
8
+ "max_seq_len": 512,
9
+ "positional_encoding": null,
10
+ "relative_position_embedding": true,
11
+ "transformer_n_layer": 6,
12
+ "transformer_n_head": 8,
13
+ "dropout": 0.1
14
+ }
WADI/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:83eca0fd163b5bf7c9bc1a564a98dd0fb21e4b838687bb4fa724f488dd900201
3
+ size 82137496
convert_to_hf.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.13"
3
+ # dependencies = ["torch", "timm", "safetensors"]
4
+ # ///
5
+ """Convert AnomalyBERT .pt checkpoint files to HuggingFace-style state_dict + config structure.
6
+
7
+ Each .pt file is converted to a directory containing:
8
+ - config.json: model hyperparameters
9
+ - model.safetensors: state_dict in safetensors format
10
+ """
11
+
12
+ import json
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ sys.path.insert(0, 'original')
17
+
18
+ import torch
19
+ from safetensors.torch import save_file
20
+
21
+
22
+ def extract_config(model: torch.nn.Module) -> dict:
23
+ """Extract model hyperparameters from a loaded AnomalyTransformer."""
24
+ patch_size = model.patch_size
25
+ max_seq_len = model.max_seq_len
26
+ d_embed = model.linear_embedding.weight.shape[0]
27
+ input_d_data = model.linear_embedding.weight.shape[1] // patch_size
28
+
29
+ # output_d_data from mlp_layers[-1].out_features / patch_size
30
+ mlp_last = model.mlp_layers[-1]
31
+ output_d_data = mlp_last.out_features // patch_size
32
+
33
+ # Count transformer layers
34
+ n_layer = len(model.transformer_encoder.encoder_layers)
35
+
36
+ # Get n_head from first attention layer
37
+ first_attn = model.transformer_encoder.encoder_layers[0].attention_layer
38
+ n_head = first_attn.n_head
39
+
40
+ # Get hidden_dim from first feed forward layer
41
+ first_ff = model.transformer_encoder.encoder_layers[0].feed_forward_layer
42
+ hidden_dim = first_ff.first_fc_layer.out_features
43
+ hidden_dim_rate = hidden_dim / d_embed
44
+
45
+ # Detect positional encoding type
46
+ has_pe = model.transformer_encoder.positional_encoding
47
+ if has_pe:
48
+ pe_layer = model.transformer_encoder.positional_encoding_layer
49
+ pe_type = type(pe_layer).__name__
50
+ if 'Sinusoidal' in pe_type:
51
+ positional_encoding = 'Sinusoidal'
52
+ elif 'Absolute' in pe_type:
53
+ positional_encoding = 'Absolute'
54
+ else:
55
+ positional_encoding = pe_type
56
+ else:
57
+ positional_encoding = None
58
+
59
+ # Detect relative position embedding
60
+ relative_position_embedding = first_attn.relative_position_embedding
61
+
62
+ # Dropout rate from encoder layer
63
+ dropout = model.transformer_encoder.encoder_layers[0].dropout_layer.p
64
+
65
+ return {
66
+ 'model_type': 'AnomalyBERT',
67
+ 'input_d_data': input_d_data,
68
+ 'output_d_data': output_d_data,
69
+ 'patch_size': patch_size,
70
+ 'd_embed': d_embed,
71
+ 'hidden_dim_rate': hidden_dim_rate,
72
+ 'max_seq_len': max_seq_len,
73
+ 'positional_encoding': positional_encoding,
74
+ 'relative_position_embedding': relative_position_embedding,
75
+ 'transformer_n_layer': n_layer,
76
+ 'transformer_n_head': n_head,
77
+ 'dropout': dropout,
78
+ }
79
+
80
+
81
+ def convert_checkpoint(pt_path: Path, output_dir: Path) -> None:
82
+ """Convert a single .pt checkpoint to state_dict + config."""
83
+ print(f'Converting {pt_path.name}...')
84
+
85
+ model = torch.load(pt_path, map_location='cpu', weights_only=False)
86
+ config = extract_config(model)
87
+ state_dict = model.state_dict()
88
+
89
+ output_dir.mkdir(parents=True, exist_ok=True)
90
+
91
+ # Save config
92
+ config_path = output_dir / 'config.json'
93
+ with open(config_path, 'w') as f:
94
+ json.dump(config, f, indent=2)
95
+ print(f' config.json: {json.dumps(config, indent=2)}')
96
+
97
+ # Save state_dict as safetensors
98
+ safetensors_path = output_dir / 'model.safetensors'
99
+ save_file(state_dict, str(safetensors_path))
100
+ size_mb = safetensors_path.stat().st_size / 1024 / 1024
101
+ print(f' model.safetensors: {size_mb:.1f} MB ({len(state_dict)} tensors)')
102
+
103
+ print(f' -> {output_dir}/')
104
+
105
+
106
+ def main() -> None:
107
+ """Convert all *_parameters.pt files."""
108
+ pt_files = sorted(Path('.').glob('*_parameters.pt'))
109
+ if not pt_files:
110
+ print('No *_parameters.pt files found.')
111
+ return
112
+
113
+ for pt_path in pt_files:
114
+ dataset_name = pt_path.stem.replace('_parameters', '')
115
+ output_dir = Path(dataset_name)
116
+ convert_checkpoint(pt_path, output_dir)
117
+
118
+ print(f'\nDone. Converted {len(pt_files)} checkpoints.')
119
+
120
+
121
+ if __name__ == '__main__':
122
+ main()
inspect_pt.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.13"
3
+ # dependencies = ["torch", "timm"]
4
+ # ///
5
+ """Inspect the structure of .pt checkpoint files."""
6
+
7
+ import sys
8
+
9
+ sys.path.insert(0, 'original')
10
+
11
+ import torch
12
+ from pathlib import Path
13
+
14
+
15
+ def inspect_checkpoint(path: Path) -> None:
16
+ """Print the structure of a checkpoint file."""
17
+ print(f'\n{"=" * 60}')
18
+ print(f'File: {path.name} ({path.stat().st_size / 1024 / 1024:.1f} MB)')
19
+ print('=' * 60)
20
+
21
+ data = torch.load(path, map_location='cpu', weights_only=False)
22
+ print(f'Top-level type: {type(data).__name__}')
23
+
24
+ if isinstance(data, dict):
25
+ print(f'Keys: {list(data.keys())[:10]}')
26
+ for key, val in list(data.items())[:5]:
27
+ if isinstance(val, torch.Tensor):
28
+ print(f' {key}: Tensor {val.shape} {val.dtype}')
29
+ else:
30
+ print(f' {key}: {type(val).__name__}')
31
+ elif hasattr(data, 'state_dict'):
32
+ print('This is a full model object.')
33
+ sd = data.state_dict()
34
+ print(f'state_dict keys ({len(sd)}):')
35
+ for k, v in sd.items():
36
+ print(f' {k}: {v.shape} {v.dtype}')
37
+ # Print model attributes
38
+ for attr in ['max_seq_len', 'patch_size', 'data_seq_len']:
39
+ if hasattr(data, attr):
40
+ print(f' model.{attr} = {getattr(data, attr)}')
41
+ else:
42
+ print(f'Unexpected type: {type(data)}')
43
+
44
+
45
+ if __name__ == '__main__':
46
+ for pt_file in sorted(Path('.').glob('*_parameters.pt')):
47
+ inspect_checkpoint(pt_file)
verify_conversion.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.13"
3
+ # dependencies = ["torch", "timm", "safetensors"]
4
+ # ///
5
+ """Verify converted safetensors match original .pt checkpoints."""
6
+
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ sys.path.insert(0, 'original')
12
+
13
+ import torch
14
+ from safetensors.torch import load_file
15
+
16
+ from models.anomaly_transformer import get_anomaly_transformer
17
+
18
+
19
+ def verify(dataset: str) -> bool:
20
+ """Verify a single converted checkpoint."""
21
+ pt_path = Path(f'{dataset}_parameters.pt')
22
+ config_path = Path(dataset) / 'config.json'
23
+ safetensors_path = Path(dataset) / 'model.safetensors'
24
+
25
+ # Load original
26
+ original = torch.load(pt_path, map_location='cpu', weights_only=False)
27
+ original_sd = original.state_dict()
28
+
29
+ # Load config and rebuild model
30
+ with open(config_path) as f:
31
+ config = json.load(f)
32
+
33
+ model = get_anomaly_transformer(
34
+ input_d_data=config['input_d_data'],
35
+ output_d_data=config['output_d_data'],
36
+ patch_size=config['patch_size'],
37
+ d_embed=config['d_embed'],
38
+ hidden_dim_rate=config['hidden_dim_rate'],
39
+ max_seq_len=config['max_seq_len'],
40
+ positional_encoding=config['positional_encoding'],
41
+ relative_position_embedding=config['relative_position_embedding'],
42
+ transformer_n_layer=config['transformer_n_layer'],
43
+ transformer_n_head=config['transformer_n_head'],
44
+ dropout=config['dropout'],
45
+ )
46
+
47
+ # Load safetensors weights
48
+ saved_sd = load_file(str(safetensors_path))
49
+ model.load_state_dict(saved_sd)
50
+ loaded_sd = model.state_dict()
51
+
52
+ # Compare
53
+ ok = True
54
+ for key in original_sd:
55
+ if key not in loaded_sd:
56
+ print(f' MISSING: {key}')
57
+ ok = False
58
+ continue
59
+ if not torch.equal(original_sd[key], loaded_sd[key]):
60
+ diff = (original_sd[key] - loaded_sd[key]).abs().max().item()
61
+ print(f' MISMATCH: {key} (max diff={diff})')
62
+ ok = False
63
+
64
+ extra = set(loaded_sd.keys()) - set(original_sd.keys())
65
+ if extra:
66
+ print(f' EXTRA keys: {extra}')
67
+ ok = False
68
+
69
+ status = 'OK' if ok else 'FAIL'
70
+ print(f'{dataset}: {status}')
71
+ return ok
72
+
73
+
74
+ def main() -> None:
75
+ """Verify all converted checkpoints."""
76
+ datasets = ['MSL', 'SMAP', 'SWaT', 'WADI']
77
+ results = {d: verify(d) for d in datasets}
78
+ all_ok = all(results.values())
79
+ print(f'\nAll passed: {all_ok}')
80
+ if not all_ok:
81
+ sys.exit(1)
82
+
83
+
84
+ if __name__ == '__main__':
85
+ main()