Jongsu Liam Kim commited on
Commit ·
32fa850
1
Parent(s): 2b546bf
feat: convert .pt checkpoints to HuggingFace-style state_dict + config
Browse filesConvert 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 +14 -0
- MSL/model.safetensors +3 -0
- SMAP/config.json +14 -0
- SMAP/model.safetensors +3 -0
- SWaT/config.json +14 -0
- SWaT/model.safetensors +3 -0
- WADI/config.json +14 -0
- WADI/model.safetensors +3 -0
- convert_to_hf.py +122 -0
- inspect_pt.py +47 -0
- verify_conversion.py +85 -0
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()
|