torch-molecule 0.1.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_molecule/__init__.py +70 -0
- torch_molecule/base/__init__.py +8 -0
- torch_molecule/base/base.py +396 -0
- torch_molecule/base/encoder.py +44 -0
- torch_molecule/base/generator.py +24 -0
- torch_molecule/base/old_encoder.py +322 -0
- torch_molecule/base/old_predictor.py +904 -0
- torch_molecule/base/old_predictor2.py +436 -0
- torch_molecule/base/predictor.py +112 -0
- torch_molecule/encoder/attrmask/__init__.py +3 -0
- torch_molecule/encoder/attrmask/model.py +113 -0
- torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
- torch_molecule/encoder/constant.py +13 -0
- torch_molecule/encoder/contextpred/__init__.py +3 -0
- torch_molecule/encoder/contextpred/model.py +207 -0
- torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
- torch_molecule/encoder/contextpred/utils.py +206 -0
- torch_molecule/encoder/edgepred/__init__.py +3 -0
- torch_molecule/encoder/edgepred/model.py +104 -0
- torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
- torch_molecule/encoder/graphmae/__init__.py +3 -0
- torch_molecule/encoder/graphmae/dataloader.py +206 -0
- torch_molecule/encoder/graphmae/model.py +137 -0
- torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
- torch_molecule/encoder/infograph/__init__.py +3 -0
- torch_molecule/encoder/infograph/loss_function.py +128 -0
- torch_molecule/encoder/infograph/model.py +92 -0
- torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
- torch_molecule/encoder/infograph/module.py +31 -0
- torch_molecule/encoder/moama/__init__.py +3 -0
- torch_molecule/encoder/moama/model.py +131 -0
- torch_molecule/encoder/moama/modeling_moama.py +307 -0
- torch_molecule/encoder/moama/utils.py +143 -0
- torch_molecule/encoder/pretrained/__init__.py +3 -0
- torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
- torch_molecule/encoder/supervised/__init__.py +3 -0
- torch_molecule/encoder/supervised/model.py +97 -0
- torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
- torch_molecule/generator/digress/__init__.py +3 -0
- torch_molecule/generator/digress/diffusion.py +194 -0
- torch_molecule/generator/digress/modeling_digress.py +503 -0
- torch_molecule/generator/digress/transformer.py +360 -0
- torch_molecule/generator/digress/utils.py +196 -0
- torch_molecule/generator/gdss/__init__.py +3 -0
- torch_molecule/generator/gdss/layers.py +257 -0
- torch_molecule/generator/gdss/model.py +204 -0
- torch_molecule/generator/gdss/modeling_gdss.py +474 -0
- torch_molecule/generator/gdss/sde.py +334 -0
- torch_molecule/generator/gdss/solver.py +227 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
- torch_molecule/generator/gdss/utils.py +240 -0
- torch_molecule/generator/graph_dit/__init__.py +3 -0
- torch_molecule/generator/graph_dit/diffusion.py +188 -0
- torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
- torch_molecule/generator/graph_dit/transformer.py +207 -0
- torch_molecule/generator/graph_dit/utils.py +220 -0
- torch_molecule/generator/graph_ga/__init__.py +3 -0
- torch_molecule/generator/graph_ga/crossover.py +182 -0
- torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
- torch_molecule/generator/graph_ga/mutate.py +136 -0
- torch_molecule/generator/graph_ga/oracle.py +95 -0
- torch_molecule/generator/jtvae/__init__.py +3 -0
- torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
- torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
- torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
- torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
- torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
- torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
- torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
- torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
- torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
- torch_molecule/generator/lstm/__init__.py +3 -0
- torch_molecule/generator/lstm/action_sampler.py +91 -0
- torch_molecule/generator/lstm/lstm.py +59 -0
- torch_molecule/generator/lstm/modeling_lstm.py +263 -0
- torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
- torch_molecule/generator/lstm/utils.py +62 -0
- torch_molecule/generator/molgpt/__init__.py +3 -0
- torch_molecule/generator/molgpt/dataset.py +87 -0
- torch_molecule/generator/molgpt/gpt.py +251 -0
- torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
- torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
- torch_molecule/nn/__init__.py +16 -0
- torch_molecule/nn/attention.py +63 -0
- torch_molecule/nn/embedder.py +183 -0
- torch_molecule/nn/gnn.py +631 -0
- torch_molecule/nn/mlp.py +34 -0
- torch_molecule/predictor/bfgnn/__init__.py +3 -0
- torch_molecule/predictor/bfgnn/model.py +116 -0
- torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
- torch_molecule/predictor/dir/__init__.py +3 -0
- torch_molecule/predictor/dir/model.py +235 -0
- torch_molecule/predictor/dir/modeling_dir.py +161 -0
- torch_molecule/predictor/dir/utils.py +40 -0
- torch_molecule/predictor/gnn/__init__.py +3 -0
- torch_molecule/predictor/gnn/model.py +114 -0
- torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
- torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
- torch_molecule/predictor/grea/__init__.py +3 -0
- torch_molecule/predictor/grea/model.py +190 -0
- torch_molecule/predictor/grea/modeling_grea.py +107 -0
- torch_molecule/predictor/grea/old1_features.py +229 -0
- torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
- torch_molecule/predictor/grea/utils.py +97 -0
- torch_molecule/predictor/grin/__init__.py +3 -0
- torch_molecule/predictor/grin/model.py +116 -0
- torch_molecule/predictor/grin/modeling_grin.py +91 -0
- torch_molecule/predictor/irm/__init__.py +3 -0
- torch_molecule/predictor/irm/model.py +127 -0
- torch_molecule/predictor/irm/modeling_irm.py +179 -0
- torch_molecule/predictor/lstm/__init__.py +3 -0
- torch_molecule/predictor/lstm/model.py +145 -0
- torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
- torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
- torch_molecule/predictor/old_modeling_graph.py +650 -0
- torch_molecule/predictor/rpgnn/__init__.py +3 -0
- torch_molecule/predictor/rpgnn/model.py +185 -0
- torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
- torch_molecule/predictor/sgir/__init__.py +3 -0
- torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
- torch_molecule/predictor/sgir/strategy.py +266 -0
- torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
- torch_molecule/predictor/smiles_transformer/model.py +424 -0
- torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
- torch_molecule/predictor/ssr/__init__.py +3 -0
- torch_molecule/predictor/ssr/model.py +283 -0
- torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
- torch_molecule/predictor/ssr/utils.py +185 -0
- torch_molecule/utils/__init__.py +31 -0
- torch_molecule/utils/checker.py +158 -0
- torch_molecule/utils/checkpoint.py +294 -0
- torch_molecule/utils/format.py +140 -0
- torch_molecule/utils/generic/metrics.py +530 -0
- torch_molecule/utils/generic/pseudo_tasks.py +11 -0
- torch_molecule/utils/generic/weights.py +30 -0
- torch_molecule/utils/graph/features.py +178 -0
- torch_molecule/utils/graph/graph_from_smiles.py +134 -0
- torch_molecule/utils/graph/graph_to_smiles.py +312 -0
- torch_molecule/utils/hf.py +232 -0
- torch_molecule/utils/search.py +64 -0
- torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
- torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
- torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
- torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
- torch_molecule-0.1.0.dev2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import datetime
|
|
4
|
+
import tempfile
|
|
5
|
+
import torch
|
|
6
|
+
import warnings
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
# Import typing dependencies if needed
|
|
10
|
+
from typing import Optional, Dict, Any
|
|
11
|
+
|
|
12
|
+
HF_METADATA = {
|
|
13
|
+
"tags": [
|
|
14
|
+
"torch_molecule",
|
|
15
|
+
"molecular-property-prediction",
|
|
16
|
+
],
|
|
17
|
+
"library_name": "torch_molecule",
|
|
18
|
+
}
|
|
19
|
+
from ..utils.format import sanitize_config
|
|
20
|
+
|
|
21
|
+
class LocalCheckpointManager:
|
|
22
|
+
"""Handles saving and loading of models to and from local paths."""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def save_model_to_local(model_instance, path: str) -> None:
|
|
26
|
+
"""Save model weights and configuration to a local file."""
|
|
27
|
+
if not model_instance.is_fitted_:
|
|
28
|
+
raise ValueError("Model must be fitted before saving.")
|
|
29
|
+
|
|
30
|
+
if not path.endswith((".pt", ".pth")):
|
|
31
|
+
raise ValueError("Save path should end with '.pt' or '.pth'.")
|
|
32
|
+
|
|
33
|
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
|
34
|
+
model_name = os.path.splitext(os.path.basename(path))[0]
|
|
35
|
+
|
|
36
|
+
save_dict = {
|
|
37
|
+
"model_state_dict": model_instance.model.state_dict(),
|
|
38
|
+
"hyperparameters": model_instance.get_params(),
|
|
39
|
+
"model_name": model_name,
|
|
40
|
+
"date_saved": datetime.datetime.now().isoformat(),
|
|
41
|
+
"version": getattr(model_instance, "__version__", "1.0.0"),
|
|
42
|
+
}
|
|
43
|
+
torch.save(save_dict, path)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def load_model_from_local(model_instance, path: str) -> None:
|
|
47
|
+
"""Load model weights and configuration from a local file."""
|
|
48
|
+
if not os.path.exists(path):
|
|
49
|
+
raise FileNotFoundError(f"No model file found at '{path}'.")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
checkpoint = torch.load(path, map_location=model_instance.device, weights_only=False)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise ValueError(f"Error loading model from {path}: {str(e)}")
|
|
55
|
+
|
|
56
|
+
verbose = model_instance.get_params().get("verbose", False)
|
|
57
|
+
|
|
58
|
+
required_keys = {"model_state_dict", "hyperparameters", "model_name"}
|
|
59
|
+
if not all(key in checkpoint for key in required_keys):
|
|
60
|
+
missing_keys = required_keys - set(checkpoint.keys())
|
|
61
|
+
raise ValueError(f"Checkpoint missing required keys: {missing_keys}")
|
|
62
|
+
|
|
63
|
+
parameter_status = []
|
|
64
|
+
for key, new_value in checkpoint["hyperparameters"].items():
|
|
65
|
+
if hasattr(model_instance, key):
|
|
66
|
+
old_value = getattr(model_instance, key)
|
|
67
|
+
is_changed = (old_value != new_value)
|
|
68
|
+
parameter_status.append({
|
|
69
|
+
"Parameter": key,
|
|
70
|
+
"Old Value": old_value,
|
|
71
|
+
"New Value": new_value,
|
|
72
|
+
"Status": "Changed" if is_changed else "Unchanged",
|
|
73
|
+
})
|
|
74
|
+
if is_changed:
|
|
75
|
+
setattr(model_instance, key, new_value)
|
|
76
|
+
|
|
77
|
+
if parameter_status and verbose:
|
|
78
|
+
print("\nHyperparameter Status:")
|
|
79
|
+
print("-" * 80)
|
|
80
|
+
print(f"{'Parameter':<20} {'Old Value':<20} {'New Value':<20} {'Status':<10}")
|
|
81
|
+
print("-" * 80)
|
|
82
|
+
# Sort so that changed parameters appear first
|
|
83
|
+
parameter_status.sort(key=lambda x: (x["Status"] != "Changed", x["Parameter"]))
|
|
84
|
+
for param in parameter_status:
|
|
85
|
+
color = "\033[91m" if param["Status"] == "Changed" else "\033[92m"
|
|
86
|
+
print(
|
|
87
|
+
f"{param['Parameter']:<20} "
|
|
88
|
+
f"{str(param['Old Value']):<20} "
|
|
89
|
+
f"{str(param['New Value']):<20} "
|
|
90
|
+
f"{color}{param['Status']}\033[0m"
|
|
91
|
+
)
|
|
92
|
+
print("-" * 80)
|
|
93
|
+
changes_count = sum(1 for p in parameter_status if p["Status"] == "Changed")
|
|
94
|
+
print(
|
|
95
|
+
f"\nSummary: {changes_count} parameters changed, "
|
|
96
|
+
f"{len(parameter_status) - changes_count} unchanged"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Reinitialize
|
|
100
|
+
model_instance._initialize_model(model_instance.model_class, checkpoint)
|
|
101
|
+
model_instance.model_name = checkpoint["model_name"]
|
|
102
|
+
model_instance.is_fitted_ = True
|
|
103
|
+
model_instance.model = model_instance.model.to(model_instance.device)
|
|
104
|
+
print(f"Model successfully loaded from local path: {path}")
|
|
105
|
+
|
|
106
|
+
class HuggingFaceCheckpointManager:
|
|
107
|
+
"""Handles saving and loading of models to and from the Hugging Face Hub."""
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def load_model_from_hf(model_instance, repo_id: str, path: str, config_filename: str = "config.json") -> None:
|
|
111
|
+
"""Load model from Hugging Face Hub, saving locally to `path` first."""
|
|
112
|
+
try:
|
|
113
|
+
from huggingface_hub import hf_hub_download
|
|
114
|
+
except ImportError:
|
|
115
|
+
raise ImportError(
|
|
116
|
+
"huggingface_hub package is required to load from Hugging Face Hub. "
|
|
117
|
+
"Install it with: pip install huggingface_hub"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
|
122
|
+
model_name = os.path.splitext(os.path.basename(path))[0]
|
|
123
|
+
|
|
124
|
+
downloaded_path = hf_hub_download(
|
|
125
|
+
repo_id=repo_id,
|
|
126
|
+
filename=f"{model_name}.pt",
|
|
127
|
+
local_dir=os.path.dirname(path),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
hf_hub_download(
|
|
131
|
+
repo_id=repo_id,
|
|
132
|
+
filename=config_filename,
|
|
133
|
+
local_dir=os.path.dirname(path),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
checkpoint = torch.load(downloaded_path, map_location=model_instance.device, weights_only=False)
|
|
137
|
+
|
|
138
|
+
required_keys = {"model_state_dict", "hyperparameters", "model_name"}
|
|
139
|
+
if not all(key in checkpoint for key in required_keys):
|
|
140
|
+
missing_keys = required_keys - set(checkpoint.keys())
|
|
141
|
+
raise ValueError(f"Checkpoint missing required keys: {missing_keys}")
|
|
142
|
+
|
|
143
|
+
parameter_status = []
|
|
144
|
+
for key, new_value in checkpoint["hyperparameters"].items():
|
|
145
|
+
if hasattr(model_instance, key):
|
|
146
|
+
old_value = getattr(model_instance, key)
|
|
147
|
+
is_changed = (old_value != new_value)
|
|
148
|
+
parameter_status.append({
|
|
149
|
+
"Parameter": key,
|
|
150
|
+
"Old Value": old_value,
|
|
151
|
+
"New Value": new_value,
|
|
152
|
+
"Status": "Changed" if is_changed else "Unchanged",
|
|
153
|
+
})
|
|
154
|
+
if is_changed:
|
|
155
|
+
setattr(model_instance, key, new_value)
|
|
156
|
+
|
|
157
|
+
if parameter_status:
|
|
158
|
+
print("\nHyperparameter Status:")
|
|
159
|
+
print("-" * 80)
|
|
160
|
+
print(f"{'Parameter':<20} {'Old Value':<20} {'New Value':<20} {'Status':<10}")
|
|
161
|
+
print("-" * 80)
|
|
162
|
+
parameter_status.sort(key=lambda x: (x["Status"] != "Changed", x["Parameter"]))
|
|
163
|
+
for param in parameter_status:
|
|
164
|
+
color = "\033[91m" if param["Status"] == "Changed" else "\033[92m"
|
|
165
|
+
print(
|
|
166
|
+
f"{param['Parameter']:<20} "
|
|
167
|
+
f"{str(param['Old Value']):<20} "
|
|
168
|
+
f"{str(param['New Value']):<20} "
|
|
169
|
+
f"{color}{param['Status']}\033[0m"
|
|
170
|
+
)
|
|
171
|
+
print("-" * 80)
|
|
172
|
+
changes_count = sum(1 for p in parameter_status if p["Status"] == "Changed")
|
|
173
|
+
print(
|
|
174
|
+
f"\nSummary: {changes_count} parameters changed, "
|
|
175
|
+
f"{len(parameter_status) - changes_count} unchanged"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
model_instance._initialize_model(model_instance.model_class, checkpoint)
|
|
179
|
+
model_instance.model_name = checkpoint["model_name"]
|
|
180
|
+
model_instance.is_fitted_ = True
|
|
181
|
+
model_instance.model = model_instance.model.to(model_instance.device)
|
|
182
|
+
print(f"Model successfully loaded from repository {repo_id}")
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
if os.path.exists(path):
|
|
186
|
+
os.remove(path) # Clean up partial downloads
|
|
187
|
+
raise RuntimeError(f"Failed to download or load model from repository '{repo_id}': {str(e)}")
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def push_to_huggingface(
|
|
191
|
+
model_instance,
|
|
192
|
+
repo_id: str,
|
|
193
|
+
task_id: str = "default",
|
|
194
|
+
metadata_dict: Optional[Dict[str, Any]] = None,
|
|
195
|
+
metrics: Optional[Dict[str, float]] = None,
|
|
196
|
+
commit_message: str = "Update model",
|
|
197
|
+
token: Optional[str] = None,
|
|
198
|
+
private: bool = False,
|
|
199
|
+
config_filename: str = "config.json",
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Push a task-specific model checkpoint to Hugging Face Hub."""
|
|
202
|
+
try:
|
|
203
|
+
from huggingface_hub import HfApi, create_repo, metadata_update
|
|
204
|
+
from .hf import merge_task_configs, get_existing_repo_data, create_model_card
|
|
205
|
+
except ImportError:
|
|
206
|
+
raise ImportError(
|
|
207
|
+
"huggingface_hub package is required to push to Hugging Face Hub. "
|
|
208
|
+
"Install it with: pip install huggingface_hub"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if not isinstance(repo_id, str) or "/" not in repo_id:
|
|
212
|
+
raise ValueError("repo_id must be in format '<username>/<model_name>'.")
|
|
213
|
+
if not task_id:
|
|
214
|
+
raise ValueError("task_id must be provided.")
|
|
215
|
+
if not model_instance.is_fitted_:
|
|
216
|
+
raise ValueError("Model must be fitted before pushing to Hugging Face Hub.")
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
api = HfApi(token=token)
|
|
220
|
+
repo_exists, existing_config, existing_readme = get_existing_repo_data(repo_id, token)
|
|
221
|
+
create_repo(repo_id, private=private, token=token, exist_ok=True)
|
|
222
|
+
|
|
223
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
224
|
+
local_path = os.path.join(tmp_dir, f"{model_instance.model_name}.pt")
|
|
225
|
+
# Use the local manager to save a copy
|
|
226
|
+
LocalCheckpointManager.save_model_to_local(model_instance, local_path)
|
|
227
|
+
|
|
228
|
+
# Prepare task-specific config
|
|
229
|
+
task_config = {
|
|
230
|
+
**({"task_type": model_instance.task_type} if hasattr(model_instance, "task_type") else {}),
|
|
231
|
+
"config": sanitize_config(model_instance.get_params(deep=True)),
|
|
232
|
+
"metrics": metrics or {},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Merge with any existing config
|
|
236
|
+
base_config = (
|
|
237
|
+
{
|
|
238
|
+
"model_name": model_instance.model_name,
|
|
239
|
+
"framework": "torch_molecule",
|
|
240
|
+
"date_created": existing_config.get(
|
|
241
|
+
"date_created", datetime.datetime.now().isoformat()
|
|
242
|
+
),
|
|
243
|
+
}
|
|
244
|
+
if not repo_exists
|
|
245
|
+
else existing_config
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
num_params = sum(p.numel() for p in model_instance.model.parameters())
|
|
249
|
+
final_config = merge_task_configs(
|
|
250
|
+
task_id=task_id,
|
|
251
|
+
existing_config=base_config,
|
|
252
|
+
new_task_config=task_config,
|
|
253
|
+
num_params=num_params,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Save config file
|
|
257
|
+
config_path = os.path.join(tmp_dir, config_filename)
|
|
258
|
+
with open(config_path, "w") as f:
|
|
259
|
+
json.dump(final_config, f, indent=2)
|
|
260
|
+
|
|
261
|
+
# Create model card
|
|
262
|
+
readme_content = create_model_card(
|
|
263
|
+
model_class=model_instance.__class__.__name__,
|
|
264
|
+
model_name=model_instance.model_name,
|
|
265
|
+
tasks_config=final_config.get("tasks", {}),
|
|
266
|
+
model_config=final_config,
|
|
267
|
+
repo_id=repo_id,
|
|
268
|
+
existing_readme=existing_readme,
|
|
269
|
+
)
|
|
270
|
+
readme_path = os.path.join(tmp_dir, "README.md")
|
|
271
|
+
with open(readme_path, "w") as f:
|
|
272
|
+
f.write(readme_content)
|
|
273
|
+
|
|
274
|
+
# Upload everything
|
|
275
|
+
api.upload_folder(
|
|
276
|
+
repo_id=repo_id,
|
|
277
|
+
folder_path=tmp_dir,
|
|
278
|
+
commit_message=f"{commit_message} - Task: {task_id}",
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Update or add repository metadata
|
|
282
|
+
metadata_dict = HF_METADATA if metadata_dict is None else metadata_dict
|
|
283
|
+
metadata_update(repo_id=repo_id, metadata=metadata_dict, token=token, overwrite=True)
|
|
284
|
+
|
|
285
|
+
# Print summary
|
|
286
|
+
task_info = final_config["tasks"][task_id]
|
|
287
|
+
print(f"Successfully pushed model for task {task_id} to {repo_id}")
|
|
288
|
+
print(f"Task version: {task_info['current_version']}")
|
|
289
|
+
if metrics:
|
|
290
|
+
print("Metrics:")
|
|
291
|
+
for metric, value in metrics.items():
|
|
292
|
+
print(f" {metric}: {value:.4f}")
|
|
293
|
+
except Exception as e:
|
|
294
|
+
raise RuntimeError(f"Failed to push to Hugging Face Hub: {str(e)}")
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import numpy as np
|
|
3
|
+
import types
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
def serialize_config(obj):
|
|
7
|
+
"""Helper function to make config JSON serializable.
|
|
8
|
+
|
|
9
|
+
Handles special cases like lambda functions, torch modules, and numpy arrays.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
obj : Any
|
|
14
|
+
The object to serialize
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
Any
|
|
19
|
+
JSON serializable representation of the object
|
|
20
|
+
"""
|
|
21
|
+
# Handle None
|
|
22
|
+
if obj is None:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
# Handle lambda and regular functions
|
|
26
|
+
if isinstance(obj, (types.LambdaType, types.FunctionType, types.MethodType)):
|
|
27
|
+
# For lambda functions, try to get the source code
|
|
28
|
+
try:
|
|
29
|
+
if isinstance(obj, types.LambdaType):
|
|
30
|
+
source = inspect.getsource(obj).strip()
|
|
31
|
+
return {"_type": "lambda", "source": source}
|
|
32
|
+
else:
|
|
33
|
+
return {"_type": "function", "name": obj.__name__}
|
|
34
|
+
except (IOError, TypeError):
|
|
35
|
+
# Fallback for built-in functions or when source isn't available
|
|
36
|
+
return {"_type": "function", "name": str(obj)}
|
|
37
|
+
|
|
38
|
+
# Handle PyTorch modules and optimizers
|
|
39
|
+
elif isinstance(obj, (torch.nn.Module, torch.optim.Optimizer)):
|
|
40
|
+
return {
|
|
41
|
+
"_type": "torch_class",
|
|
42
|
+
"class_name": obj.__class__.__name__,
|
|
43
|
+
"module": obj.__class__.__module__
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Handle PyTorch tensors
|
|
47
|
+
elif isinstance(obj, torch.Tensor):
|
|
48
|
+
# If it's a single number wrapped in a torch tensor, just return the number
|
|
49
|
+
if obj.numel() == 1:
|
|
50
|
+
return obj.item()
|
|
51
|
+
elif obj.numel() < 1000:
|
|
52
|
+
return {
|
|
53
|
+
"_type": "torch_tensor",
|
|
54
|
+
"data": obj.detach().cpu().tolist(),
|
|
55
|
+
"shape": list(obj.shape)
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
"_type": "torch_tensor",
|
|
59
|
+
"shape": list(obj.shape)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Handle numpy arrays
|
|
63
|
+
elif isinstance(obj, (np.ndarray, np.generic)):
|
|
64
|
+
# If it's a single number wrapped in a numpy array, just return the number
|
|
65
|
+
if obj.size == 1:
|
|
66
|
+
return obj.item()
|
|
67
|
+
elif obj.size < 1000:
|
|
68
|
+
return {
|
|
69
|
+
"_type": "numpy_array",
|
|
70
|
+
"data": obj.tolist(),
|
|
71
|
+
"shape": list(obj.shape)
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
"_type": "numpy_array",
|
|
75
|
+
"shape": list(obj.shape)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Handle sets and frozensets
|
|
79
|
+
elif isinstance(obj, (set, frozenset)):
|
|
80
|
+
return {
|
|
81
|
+
"_type": "set",
|
|
82
|
+
"data": list(obj)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Handle custom objects with __dict__
|
|
86
|
+
elif hasattr(obj, '__dict__'):
|
|
87
|
+
return {
|
|
88
|
+
"_type": "custom_class",
|
|
89
|
+
"class_name": obj.__class__.__name__,
|
|
90
|
+
"module": obj.__class__.__module__
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Handle basic types that are JSON serializable
|
|
94
|
+
elif isinstance(obj, (str, int, float, bool)):
|
|
95
|
+
return obj
|
|
96
|
+
|
|
97
|
+
# Handle any other types by converting to string
|
|
98
|
+
return {
|
|
99
|
+
"_type": "unknown",
|
|
100
|
+
"repr": str(obj)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def sanitize_config(config_dict):
|
|
105
|
+
"""Recursively sanitize config dictionary for JSON serialization.
|
|
106
|
+
|
|
107
|
+
Handles nested structures and special cases.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
config_dict : dict
|
|
112
|
+
Configuration dictionary to sanitize
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
dict
|
|
117
|
+
Sanitized configuration dictionary that is JSON serializable
|
|
118
|
+
"""
|
|
119
|
+
if not isinstance(config_dict, dict):
|
|
120
|
+
return serialize_config(config_dict)
|
|
121
|
+
|
|
122
|
+
clean_dict = {}
|
|
123
|
+
for key, value in config_dict.items():
|
|
124
|
+
# Skip private attributes and callable objects stored as attributes
|
|
125
|
+
if isinstance(key, str) and (key.startswith('_') or callable(value)):
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Handle nested dictionaries
|
|
129
|
+
if isinstance(value, dict):
|
|
130
|
+
clean_dict[key] = sanitize_config(value)
|
|
131
|
+
|
|
132
|
+
# Handle lists and tuples
|
|
133
|
+
elif isinstance(value, (list, tuple)):
|
|
134
|
+
clean_dict[key] = [sanitize_config(v) for v in value]
|
|
135
|
+
|
|
136
|
+
# Handle all other types
|
|
137
|
+
else:
|
|
138
|
+
clean_dict[key] = serialize_config(value)
|
|
139
|
+
|
|
140
|
+
return clean_dict
|