kernel-elastic-autoencoder 2.0.0__tar.gz → 3.0.0__tar.gz

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.
Files changed (15) hide show
  1. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/PKG-INFO +2 -2
  2. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/pyproject.toml +2 -2
  3. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/src/kernel_elastic_autoencoder/__init__.py +2 -11
  4. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/src/kernel_elastic_autoencoder/model.py +9 -2
  5. kernel_elastic_autoencoder-3.0.0/src/kernel_elastic_autoencoder/pipeline.py +161 -0
  6. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/src/kernel_elastic_autoencoder/tokenizer.py +0 -68
  7. kernel_elastic_autoencoder-3.0.0/src/kernel_elastic_autoencoder/training.py +153 -0
  8. kernel_elastic_autoencoder-2.0.0/src/kernel_elastic_autoencoder/collate.py +0 -164
  9. kernel_elastic_autoencoder-2.0.0/src/kernel_elastic_autoencoder/pipeline.py +0 -203
  10. kernel_elastic_autoencoder-2.0.0/src/kernel_elastic_autoencoder/sample.py +0 -70
  11. kernel_elastic_autoencoder-2.0.0/src/kernel_elastic_autoencoder/training.py +0 -386
  12. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/README.md +0 -0
  13. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/src/kernel_elastic_autoencoder/config.py +0 -0
  14. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/src/kernel_elastic_autoencoder/layers.py +0 -0
  15. {kernel_elastic_autoencoder-2.0.0 → kernel_elastic_autoencoder-3.0.0}/src/kernel_elastic_autoencoder/losses.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kernel_elastic_autoencoder
3
- Version: 2.0.0
3
+ Version: 3.0.0
4
4
  Summary: Implementation of Kernel-Elastic Autoencoder for Molecular Design (https://doi.org/10.1093/pnasnexus/pgae168)
5
5
  License: MIT
6
6
  Author: Felix Rotter-McCartney
@@ -11,8 +11,8 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: accelerate (>=1.14.0,<2.0.0)
14
15
  Requires-Dist: huggingface-hub (>=1.22.0,<2.0.0)
15
- Requires-Dist: pandas (>=3.0.3,<4.0.0)
16
16
  Requires-Dist: pydantic (>=2.13.4,<3.0.0)
17
17
  Requires-Dist: safetensors (>=0.8.0,<0.9.0)
18
18
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kernel_elastic_autoencoder"
3
- version = "2.0.0"
3
+ version = "3.0.0"
4
4
  description = "Implementation of Kernel-Elastic Autoencoder for Molecular Design (https://doi.org/10.1093/pnasnexus/pgae168)"
5
5
  authors = [
6
6
  { name = "Felix Rotter-McCartney", email = "felix.rotter@mail.utoronto.ca" }
@@ -11,8 +11,8 @@ requires-python = ">=3.12,<3.15"
11
11
  dependencies = [
12
12
  "pydantic (>=2.13.4,<3.0.0)",
13
13
  "huggingface-hub (>=1.22.0,<2.0.0)",
14
- "pandas (>=3.0.3,<4.0.0)",
15
14
  "safetensors (>=0.8.0,<0.9.0)",
15
+ "accelerate (>=1.14.0,<2.0.0)",
16
16
  ]
17
17
 
18
18
  [tool.poetry]
@@ -1,4 +1,3 @@
1
- from kernel_elastic_autoencoder.collate import Collated, Collator, DataframeCollator
2
1
  from kernel_elastic_autoencoder.config import (
3
2
  ExperimentConfig,
4
3
  ModelCommonConfig,
@@ -13,16 +12,11 @@ from kernel_elastic_autoencoder.config import (
13
12
  )
14
13
  from kernel_elastic_autoencoder.losses import Loss
15
14
  from kernel_elastic_autoencoder.model import Model
16
- from kernel_elastic_autoencoder.pipeline import Completion, Pipeline
17
- from kernel_elastic_autoencoder.sample import Sampler, Top1Sampler
15
+ from kernel_elastic_autoencoder.pipeline import Pipeline
18
16
  from kernel_elastic_autoencoder.tokenizer import Tokenizer
19
- from kernel_elastic_autoencoder.training import Trainer, TrainerCallback
17
+ from kernel_elastic_autoencoder.training import Trainer
20
18
 
21
19
  __all__ = [
22
- "Collated",
23
- "Collator",
24
- "Completion",
25
- "DataframeCollator",
26
20
  "ExperimentConfig",
27
21
  "Loss",
28
22
  "Model",
@@ -32,11 +26,8 @@ __all__ = [
32
26
  "ModelEncoderConfig",
33
27
  "ModelInputConfig",
34
28
  "Pipeline",
35
- "Sampler",
36
29
  "Tokenizer",
37
- "Top1Sampler",
38
30
  "Trainer",
39
- "TrainerCallback",
40
31
  "TrainingCommonConfig",
41
32
  "TrainingConfig",
42
33
  "TrainingHyperparameterConfig",
@@ -1,4 +1,3 @@
1
-
2
1
  import torch
3
2
  from huggingface_hub import PyTorchModelHubMixin
4
3
  from torch import nn
@@ -174,5 +173,13 @@ class Model(
174
173
  ).to(torch.bool)
175
174
  return self.decoder(current_output, latents, condition_embeddings, padding_mask)
176
175
 
177
- def embed_conditions(self, conditions):
176
+ def embed_conditions(self, conditions: torch.Tensor) -> torch.Tensor:
177
+ """Basic interface for the separate embedding of condition vectors.
178
+
179
+ Args:
180
+ conditions: Tensor of dimension (B, C) containing condition values for each sequence.
181
+
182
+ Returns:
183
+ torch.Tensor: Tensor of dimension (B, C, E) containing condition embeddings for each sequence.
184
+ """
178
185
  return self.encoder.embedding.conditional_embedding(conditions)
@@ -0,0 +1,161 @@
1
+ from collections.abc import Iterable
2
+
3
+ import torch
4
+
5
+ from kernel_elastic_autoencoder.model import Model
6
+ from kernel_elastic_autoencoder.tokenizer import Tokenizer
7
+
8
+
9
+ class Pipeline:
10
+ """User-facing pipeline for inference.
11
+
12
+ Defines an easy-to-use API for decoder-only inference with a pretrained model.
13
+
14
+ Examples:
15
+ Wrapping a pretrained model and tokenizer:
16
+ >>> model = Model.from_pretrained("./checkpoint")
17
+ >>> tokenizer = MyTokenizer.from_pretrained("./checkpoint/tokenizer")
18
+ >>> pipe = Pipeline(model, tokenizer)
19
+
20
+ Getting a completion for sequences:
21
+ >>> compl = pipe.completion(latents, ["abc", "def", "ghi"], [[1.0, 0.5], [2.0, 1.0], [3.0, 1.5]])
22
+ >>> print(compl.outputs)
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ model: Model,
28
+ tokenizer: Tokenizer,
29
+ device: torch.device | None = None,
30
+ ) -> None:
31
+ """Instantiates a Pipeline object.
32
+
33
+ Args:
34
+ model: Pre-trained Model object for inference. Can be obtained from Model.from_pretrained.
35
+ tokenizer: Pre-configured Tokenizer object. The Tokenizer protocol supports tokenizers
36
+ inheriting from transformers.PreTrainedTokenizerBase, so such tokenizers may be loaded
37
+ from HuggingFace Hub.
38
+ device: Torch device used for inference.
39
+ """
40
+ self.model = model.to(device)
41
+ """Pre-trained Model object for inference. Moved to Pipeline.device, and placed in eval() mode."""
42
+ self.model.eval()
43
+ self.tokenizer = tokenizer
44
+ """Pre-configured Tokenizer object."""
45
+ self.device = device
46
+ """Torch device used for inference."""
47
+
48
+ def _ingest(
49
+ self,
50
+ sequences: list[str],
51
+ conditions: list[list[float]] | torch.Tensor,
52
+ device: torch.device | None,
53
+ **kwargs,
54
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
55
+ input_ids = self.tokenizer.encode(
56
+ seq=sequences,
57
+ padding=False,
58
+ max_length=self.model.config_typed.input.max_len,
59
+ add_special_tokens=False,
60
+ **kwargs,
61
+ )
62
+ conditions = torch.as_tensor(conditions, dtype=torch.float, device=device)
63
+ condition_mask = (
64
+ conditions != self.model.config_typed.common.padding_value
65
+ ).to(torch.bool)
66
+ return input_ids, conditions, condition_mask
67
+
68
+ @torch.inference_mode()
69
+ def _completion_entry(
70
+ self,
71
+ latents: torch.Tensor,
72
+ sequences: list[str],
73
+ conditions: list[list[float]] | torch.Tensor,
74
+ device: torch.device | None = None,
75
+ **kwargs,
76
+ ) -> dict[str, torch.Tensor]:
77
+ input_ids, conds, cond_mask = self._ingest(
78
+ sequences, conditions, device, **kwargs
79
+ )
80
+ conds_embed = self.model.embed_conditions(conds)
81
+ return {
82
+ "latents": latents,
83
+ "input_ids": input_ids,
84
+ "batches_completed": torch.zeros(input_ids.size(0), dtype=torch.bool),
85
+ "condition_embeddings": conds_embed,
86
+ "condition_mask": cond_mask,
87
+ }
88
+
89
+ @torch.inference_mode()
90
+ def _completion_step(
91
+ self, intermediate: dict[str, torch.Tensor]
92
+ ) -> dict[str, torch.Tensor]:
93
+ intermediate["input_ids"] = torch.cat(
94
+ [
95
+ torch.full(
96
+ (intermediate["input_ids"].size(0), 1), self.tokenizer.bos_token_id
97
+ ),
98
+ intermediate["input_ids"],
99
+ ],
100
+ dim=1,
101
+ )
102
+ logits = self.model.decode(
103
+ current_output=intermediate["input_ids"],
104
+ latents=intermediate["latents"],
105
+ condition_embeddings=intermediate["condition_embeddings"],
106
+ token_mask=None,
107
+ condition_mask=intermediate["condition_mask"],
108
+ )
109
+ new_toks = (
110
+ torch.topk(logits[:, -1:], k=1, dim=-1).indices.squeeze(-1).to(torch.long)
111
+ )
112
+ intermediate["batches_completed"] |= (
113
+ new_toks.squeeze(-1) == self.tokenizer.eos_token_id
114
+ )
115
+ new_toks = torch.where(
116
+ intermediate["batches_completed"].unsqueeze(-1),
117
+ self.tokenizer.pad_token_id,
118
+ new_toks,
119
+ )
120
+ intermediate["input_ids"] = torch.cat([intermediate["input_ids"], new_toks], dim=1)
121
+ return intermediate
122
+
123
+ @torch.inference_mode()
124
+ def _completion_exit(self, intermediate: dict[str, torch.Tensor]) -> Iterable[str]:
125
+ return self.tokenizer.decode(intermediate["input_ids"], skip_special_tokens=True)
126
+
127
+ def completion(
128
+ self,
129
+ latents: torch.Tensor,
130
+ sequences: list[str],
131
+ conditions: list[list[float]] | torch.Tensor,
132
+ device: torch.device | None = None,
133
+ **kwargs,
134
+ ) -> Iterable[str]:
135
+ """Completes each conditioned input sequence.
136
+
137
+ The model completes each sequence in the provided list using decoder-only inference. Tokens are
138
+ sampled greedily.
139
+
140
+ Args:
141
+ latents: Tensor of dimension (B, P * E) containing latent vectors for the batch.
142
+ sequences: List of text sequences to complete.
143
+ conditions: List of condition value lists per batch.
144
+ device: Torch device used for inference.
145
+ **kwargs: Additional keyword arguments passed to Tokenizer.encode.
146
+
147
+ Returns:
148
+ Iterable[str]: List of completed sequences, stripped of special tokens.
149
+ """
150
+ intermediate = self._completion_entry(
151
+ latents=latents,
152
+ sequences=sequences,
153
+ conditions=conditions,
154
+ device=device,
155
+ **kwargs,
156
+ )
157
+ while (
158
+ intermediate["input_ids"].size(1) < self.model.config_typed.input.max_len
159
+ ) and (not intermediate["batches_completed"].all()):
160
+ intermediate = self._completion_step(intermediate=intermediate)
161
+ return self._completion_exit(intermediate=intermediate)
@@ -78,71 +78,3 @@ class Tokenizer(Protocol):
78
78
  Tokenizer: Pretrained tokenizer.
79
79
  """
80
80
  ...
81
-
82
-
83
- class _DummySlowTokenizer(Tokenizer):
84
- def __init__(self, train: Iterable[str]):
85
- self.pad_token = "_"
86
- self.bos_token = "?"
87
- self.eos_token = "!"
88
-
89
- vocab = set[str]()
90
- for b in train:
91
- vocab = vocab.union(set(b))
92
-
93
- special_tokens = [self.pad_token, self.bos_token, self.eos_token]
94
- special_tokens.extend(list(vocab))
95
- self.vocab = special_tokens
96
- self.vocab_size = len(self.vocab)
97
-
98
- self.pad_token_id = 0
99
- self.bos_token_id = 1
100
- self.eos_token_id = 2
101
-
102
- def encode(
103
- self,
104
- seq: Iterable[str],
105
- padding: bool,
106
- max_length: int,
107
- add_special_tokens: bool,
108
- **kwargs,
109
- ) -> torch.Tensor:
110
- ids_col = list[torch.Tensor]()
111
- for b in seq:
112
- toks = list(b)
113
- if add_special_tokens:
114
- toks.insert(0, self.bos_token)
115
- toks.append(self.eos_token)
116
- if padding:
117
- toks += [self.pad_token] * (max_length - len(toks))
118
- ids = torch.tensor([self.vocab.index(t) for t in toks], dtype=torch.long)
119
- ids_col.append(ids)
120
- return torch.stack(ids_col, dim=0)
121
-
122
- def decode(
123
- self, ids: torch.Tensor, skip_special_tokens: bool, **kwargs
124
- ) -> Iterable[str]:
125
- if skip_special_tokens:
126
- return [
127
- str(
128
- [
129
- (
130
- self.vocab[idx]
131
- if idx
132
- not in [
133
- self.pad_token_id,
134
- self.bos_token_id,
135
- self.eos_token_id,
136
- ]
137
- else ""
138
- )
139
- for idx in b
140
- ]
141
- )
142
- for b in ids
143
- ]
144
- else:
145
- return [str([self.vocab[idx] for idx in b]) for b in ids]
146
-
147
- @classmethod
148
- def from_pretrained(cls, pretrained_model_name_or_path: str | Path, **kwargs): ...
@@ -0,0 +1,153 @@
1
+ import os
2
+ from collections.abc import Iterable
3
+
4
+ import torch
5
+ from accelerate import Accelerator
6
+ from accelerate.utils import tqdm
7
+
8
+ from kernel_elastic_autoencoder.config import TrainingConfig
9
+ from kernel_elastic_autoencoder.losses import Loss
10
+ from kernel_elastic_autoencoder.model import Model
11
+ from kernel_elastic_autoencoder.tokenizer import Tokenizer
12
+
13
+
14
+ class Trainer:
15
+ def __init__(
16
+ self,
17
+ config: dict | TrainingConfig,
18
+ ):
19
+ """Instantiates a Trainer object.
20
+
21
+ Args:
22
+ config: Dictionary or TrainingConfig schema defining model parameters. Will be validated with TrainingConfig
23
+ regardless of input type.
24
+ """
25
+ self.config = config
26
+ """Configuration object for Hugging Face Hub compatible serialization. Not recommended to use, as it is
27
+ internal-use. Use Trainer.config_typed instead."""
28
+ config_typed = TrainingConfig.model_validate(config)
29
+ self.config_typed: TrainingConfig = config_typed
30
+ """Type-validated config in a Pydantic TrainingConfig schema, recommended for public API use."""
31
+
32
+ def train(
33
+ self,
34
+ model: Model,
35
+ tokenizer: Tokenizer,
36
+ sequences: Iterable[str],
37
+ conditions: Iterable[Iterable[float]] | torch.Tensor,
38
+ train_split: float = 0.9,
39
+ checkpoint: str = "./checkpoint",
40
+ ):
41
+ """Trains a model. Optionally, resumes from an existing checkpoint.
42
+
43
+ Args:
44
+ model: Freshly instantiated model.
45
+ tokenizer: Pre-configured Tokenizer object. The Tokenizer protocol supports tokenizers
46
+ inheriting from transformers.PreTrainedTokenizerBase, so such tokenizers may be loaded
47
+ from HuggingFace Hub.
48
+ sequences: Iterable of text sequences.
49
+ conditions: Iterable of iterables of condition values per sequence.
50
+ train_split: Fraction of dataset used for training. Must be between 0 and 1.
51
+ checkpoint: Path of local checkpoint to be saved and/or resumed.
52
+ """
53
+ accelerator = Accelerator()
54
+
55
+ loss_fn = Loss(
56
+ hp_lambda=self.config_typed.hyperparameters.hp_lambda,
57
+ hp_delta=self.config_typed.hyperparameters.hp_delta,
58
+ hp_sigma=self.config_typed.hyperparameters.hp_sigma,
59
+ kernel_dist_size=self.config_typed.hyperparameters.kernel_dist_size,
60
+ padding_idx=model.config_typed.common.padding_idx,
61
+ embedding_dim=model.config_typed.common.embedding_dim,
62
+ pooling_dim=model.config_typed.common.pooling_dim,
63
+ )
64
+ optimizer = self.config_typed.optimizer.optimizer_fn(
65
+ model.parameters(), **self.config_typed.optimizer.optimizer_params
66
+ )
67
+ scheduler = self.config_typed.optimizer.scheduler_fn(
68
+ optimizer, **self.config_typed.optimizer.scheduler_params
69
+ )
70
+
71
+ input_ids = tokenizer.encode(
72
+ seq=sequences,
73
+ padding=True,
74
+ max_length=model.config_typed.input.max_len,
75
+ add_special_tokens=True,
76
+ )
77
+ conditions = torch.as_tensor(conditions, dtype=torch.float)
78
+ token_mask = (input_ids != model.config_typed.common.padding_idx).to(torch.bool)
79
+ condition_mask = (conditions != model.config_typed.common.padding_value).to(
80
+ torch.bool
81
+ )
82
+
83
+ dataset = torch.utils.data.TensorDataset(
84
+ input_ids, conditions, token_mask, condition_mask
85
+ )
86
+ dataset_train, dataset_test = torch.utils.data.random_split(
87
+ dataset, [train_split, 1 - train_split]
88
+ )
89
+ dataloader_train = torch.utils.data.DataLoader(
90
+ dataset_train,
91
+ batch_size=self.config_typed.common.batch_size,
92
+ )
93
+ dataloader_test = torch.utils.data.DataLoader(
94
+ dataset_test,
95
+ batch_size=self.config_typed.common.batch_size,
96
+ )
97
+ curr_epoch = 0
98
+
99
+ model, optimizer, dataloader_train, dataloader_test, scheduler, curr_epoch = (
100
+ accelerator.prepare(
101
+ model,
102
+ optimizer,
103
+ dataloader_train,
104
+ dataloader_test,
105
+ scheduler,
106
+ curr_epoch,
107
+ )
108
+ )
109
+ accelerator.register_for_checkpointing(scheduler, curr_epoch)
110
+
111
+ if os.path.exists(checkpoint):
112
+ accelerator.load_state(checkpoint)
113
+
114
+ for epoch in range(curr_epoch, self.config_typed.common.max_epochs):
115
+ model.train()
116
+ for input_ids, conditions, token_mask, condition_mask in tqdm(
117
+ dataloader_train, desc=f"Epoch {epoch}, Train Batch"
118
+ ):
119
+ optimizer.zero_grad()
120
+ prediction, prediction_noise, latents_noise = model(
121
+ input_ids, conditions, token_mask, condition_mask
122
+ )
123
+ loss = loss_fn(
124
+ prediction, prediction_noise, input_ids[:, 1:], latents_noise
125
+ )
126
+ accelerator.backward(loss)
127
+ optimizer.step()
128
+
129
+ model.eval()
130
+ for input_ids, conditions, token_mask, condition_mask in tqdm(
131
+ dataloader_test, desc=f"Epoch {epoch}, Test Batch"
132
+ ):
133
+ with torch.no_grad():
134
+ prediction, prediction_noise, latents_noise = model(
135
+ input_ids,
136
+ conditions,
137
+ token_mask,
138
+ condition_mask,
139
+ )
140
+ loss = loss_fn(
141
+ prediction, prediction_noise, input_ids[:, 1:], latents_noise
142
+ )
143
+
144
+ scheduler.step(epoch)
145
+
146
+ accelerator.wait_for_everyone()
147
+ curr_epoch += 1
148
+ os.makedirs(os.path.join(checkpoint, "dist/"), exist_ok=True)
149
+ model.save_pretrained(os.path.join(checkpoint, "dist/"))
150
+ self.config_typed.to_json(
151
+ os.path.join(checkpoint, "dist/train_config.json")
152
+ )
153
+ accelerator.save_state(checkpoint)
@@ -1,164 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- import torch
4
- from pandas import DataFrame
5
- from pydantic import BaseModel, ConfigDict, Field, model_validator
6
-
7
- from kernel_elastic_autoencoder.config import ModelConfig
8
- from kernel_elastic_autoencoder.tokenizer import Tokenizer
9
-
10
-
11
- class Collated(BaseModel):
12
- """Return schema for Collator.__call__. Represents a store of tensors ready for direct input to a Model."""
13
-
14
- model_config = ConfigDict(arbitrary_types_allowed=True)
15
- input_ids: torch.Tensor = Field(
16
- ...,
17
- description="Tensor of dimension (B, S) containing indices of input tokens, generated by a Tokenizer.",
18
- )
19
- conditions: torch.Tensor = Field(
20
- ...,
21
- description="Tensor of dimension (B, C) containing condition values for each sequence.",
22
- )
23
- token_mask: torch.Tensor = Field(
24
- ...,
25
- description="Tensor of dimension (B, S) containing a boolean padding mask for each sequence.",
26
- )
27
- condition_mask: torch.Tensor = Field(
28
- ...,
29
- description="Tensor of dimension (B, C) containing a boolean condition mask for each sequence",
30
- )
31
-
32
- @model_validator(mode="after")
33
- def _check_shapes(self) -> "Collated":
34
- if (self.input_ids.shape != self.token_mask.shape) or (
35
- self.conditions.shape != self.condition_mask.shape
36
- ):
37
- raise ValueError("Mask shapes don't match source")
38
- if (self.input_ids.dim() != 2) or (self.conditions.dim() != 2):
39
- raise ValueError("Tensors should have a batch dimension")
40
- if (self.token_mask.dtype != torch.bool) or (
41
- self.condition_mask.dtype != torch.bool
42
- ):
43
- raise ValueError("Mask tensors should have bool dtype")
44
- return self
45
-
46
-
47
- class Collator[T](ABC):
48
- """Base class for collators.
49
-
50
- Provides a specification for preparing various data types for model input.
51
- """
52
-
53
- def __init__(self, model_config: ModelConfig, tokenizer: Tokenizer):
54
- """Instantiates a Collator object.
55
-
56
- Args:
57
- model_config: Model configuration object.
58
- tokenizer: Object implementing the Tokenizer protocol.
59
- """
60
- self.model_config = model_config
61
- """Model configuration object."""
62
- self.tokenizer = tokenizer
63
- """Object implementing the Tokenizer protocol."""
64
-
65
- def __call__(
66
- self,
67
- dataset: T,
68
- seq_feature: str,
69
- cond_features: list[str],
70
- padding: bool,
71
- add_special_tokens: bool,
72
- device: torch.device | None,
73
- **kwargs,
74
- ) -> Collated:
75
- return self.collate_data(
76
- dataset,
77
- seq_feature,
78
- cond_features,
79
- padding,
80
- add_special_tokens,
81
- device,
82
- **kwargs,
83
- )
84
-
85
- @abstractmethod
86
- def collate_data(
87
- self,
88
- dataset: T,
89
- seq_feature: str,
90
- cond_features: list[str],
91
- padding: bool,
92
- add_special_tokens: bool,
93
- device: torch.device | None,
94
- **kwargs,
95
- ) -> Collated:
96
- """Interface method for implementing data collation.
97
-
98
- Args:
99
- dataset: Dataset object of target data type.
100
- seq_feature: Name of sequence feature or column.
101
- cond_features: List of names of condition features or columns.
102
- padding: Whether or not to uniformly pad outputs to the maximum length specified in the model configuration.
103
- add_special_tokens: Whether or not to add special tokens according to a template. Special tokens will be
104
- added by default during training, but will be omitted during inference, where the specified bos_token_id
105
- will be appended before each pass through the decoder.
106
- device: Torch device to which the produced tensors are moved.
107
- **kwargs: Keyword arguments.
108
-
109
- Returns:
110
- Collated: Return schema representing a store of tensors ready for direct input to a Model.
111
- """
112
- ...
113
-
114
-
115
- class DataframeCollator(Collator):
116
- """Collator designed for pandas.DataFrame inputs."""
117
-
118
- def collate_data(
119
- self,
120
- dataset: DataFrame,
121
- seq_feature: str,
122
- cond_features: list[str],
123
- padding: bool,
124
- add_special_tokens: bool,
125
- device: torch.device | None,
126
- **kwargs,
127
- ) -> Collated:
128
- """Implementation of data collation for pandas.DataFrame inputs.
129
-
130
- Args:
131
- dataset: Dataset object of pandas.DataFrame type.
132
- seq_feature: Name of sequence column.
133
- cond_features: List of names of condition columns.
134
- padding: Whether or not to uniformly pad outputs to the maximum length specified in the model configuration.
135
- add_special_tokens: Whether or not to add special tokens according to a template. Special tokens will be
136
- added by default during training, but will be omitted during inference, where the specified bos_token_id
137
- will be appended before each pass through the decoder.
138
- device: Torch device to which the produced tensors are moved.
139
- **kwargs: Keyword arguments passed to Tokenizer.encode.
140
-
141
- Returns:
142
- Collated: Return schema representing a store of tensors ready for direct input to a Model.
143
- """
144
- df = dataset.copy()
145
- input_ids = self.tokenizer.encode(
146
- seq=df[seq_feature],
147
- padding=padding,
148
- max_length=self.model_config.input.max_len,
149
- add_special_tokens=add_special_tokens,
150
- **kwargs,
151
- )
152
- conditions = torch.from_numpy(df[cond_features].to_numpy()).to(
153
- device=device, dtype=torch.float
154
- )
155
- token_mask = (input_ids != self.model_config.common.padding_idx).to(torch.bool)
156
- condition_mask = (conditions != self.model_config.common.padding_value).to(
157
- torch.bool
158
- )
159
- return Collated(
160
- input_ids=input_ids,
161
- conditions=conditions,
162
- token_mask=token_mask,
163
- condition_mask=condition_mask,
164
- )
@@ -1,203 +0,0 @@
1
- from collections.abc import Callable, Iterable
2
-
3
- import torch
4
- from pydantic import BaseModel, ConfigDict
5
-
6
- from kernel_elastic_autoencoder.collate import Collator, DataframeCollator
7
- from kernel_elastic_autoencoder.config import ModelConfig
8
- from kernel_elastic_autoencoder.model import Model
9
- from kernel_elastic_autoencoder.sample import Sampler, Top1Sampler
10
- from kernel_elastic_autoencoder.tokenizer import Tokenizer
11
-
12
-
13
- class CompletionIntermediate(BaseModel):
14
- model_config = ConfigDict(arbitrary_types_allowed=True)
15
- latents: torch.Tensor
16
- input_ids: torch.Tensor
17
- batches_completed: torch.Tensor
18
- condition_embeddings: torch.Tensor
19
- condition_mask: torch.Tensor
20
-
21
-
22
- class Completion(BaseModel):
23
- """Return schema for Pipeline.completion."""
24
-
25
- model_config = ConfigDict(arbitrary_types_allowed=True)
26
- outputs: Iterable[str]
27
- """Iterable of generated output sequences."""
28
- condition_embeddings: torch.Tensor
29
- """Tensor containing generated condition embeddings for reuse."""
30
- condition_mask: torch.Tensor
31
- """Tensor containing generated condition masks for reuse."""
32
-
33
-
34
- class Pipeline[T]:
35
- """User-facing pipeline for inference.
36
-
37
- Defines an easy-to-use API for decoder-only inference with a pretrained model.
38
-
39
- Examples:
40
- Wrapping a pretrained model and tokenizer:
41
- >>> model = Model.from_pretrained("./checkpoint")
42
- >>> tokenizer = MyClassImplementingTokenizer.from_pretrained("./checkpoint/tokenizer")
43
- >>> pipe = Pipeline(model, tokenizer, MyClassImplementingCollator, MyClassImplementingSampler)
44
- >>> compl = pipe.completion(latents, dataset, seq_feature, cond_features)
45
- >>> print(compl.outputs)
46
-
47
- TODO: Complete examples.
48
- """
49
-
50
- def __init__(
51
- self,
52
- model: Model,
53
- tokenizer: Tokenizer,
54
- collator: Callable[[ModelConfig, Tokenizer], Collator[T]] = DataframeCollator,
55
- sampler: Callable[[Tokenizer], Sampler] = Top1Sampler,
56
- device: torch.device | None = None,
57
- ) -> None:
58
- """Instantiates a Pipeline object.
59
-
60
- TODO: Pipeline setup will be streamlined such that collators and samplers are by default fetched from model
61
- configuration.
62
-
63
- Args:
64
- model: Pre-trained Model object for inference. Can be obtained from Model.from_pretrained.
65
- tokenizer: Pre-configured Tokenizer object. The Tokenizer protocol supports tokenizers inheriting from
66
- transformers.PreTrainedTokenizerBase, so such tokenizers may be loaded from HuggingFace Hub.
67
- collator: Constructor for a data collator inheriting from Collator. Determines input types to Pipeline methods.
68
- sampler: Constructor for a data sampler inheriting from Sampler. Determines how outputs are produced from
69
- Pipeline methods.
70
- device: Torch device used for inference.
71
- """
72
- self.model = model.to(device)
73
- """Pre-trained Model object for inference. Moved to Pipeline.device, and placed in eval() mode."""
74
- self.model.eval()
75
- self.tokenizer = tokenizer
76
- """Pre-configured Tokenizer object. The Tokenizer Protocol supports tokenizers inheriting from
77
- transformers.PreTrainedTokenizerBase, so such tokenizers may be loaded from HuggingFace Hub."""
78
- self.device = device
79
- """Torch device used for inference."""
80
-
81
- self.collator = collator(model.config_typed, tokenizer)
82
- """Data collator inheriting from Collator, instantiated by passing Pipeline.model.config_typed and Pipeline.tokenizer."""
83
- self.sampler = sampler(tokenizer)
84
- """Data sampler inheriting from Sampler, instantiated by passing Pipeline.tokenizer."""
85
-
86
- @torch.inference_mode()
87
- def _completion_entry(
88
- self,
89
- latents: torch.Tensor,
90
- dataset: T,
91
- seq_feature: str,
92
- cond_features: list[str],
93
- device: torch.device | None = None,
94
- **kwargs,
95
- ) -> CompletionIntermediate:
96
- ds = self.collator(
97
- dataset=dataset,
98
- seq_feature=seq_feature,
99
- cond_features=cond_features,
100
- padding=False,
101
- add_special_tokens=False,
102
- device=device,
103
- **kwargs,
104
- )
105
- conds_embed = self.model.embed_conditions(ds.conditions)
106
- return CompletionIntermediate(
107
- latents=latents,
108
- input_ids=ds.input_ids,
109
- batches_completed=torch.zeros(ds.input_ids.size(0), dtype=torch.bool),
110
- condition_embeddings=conds_embed,
111
- condition_mask=ds.condition_mask,
112
- )
113
-
114
- @torch.inference_mode()
115
- def _completion_step(
116
- self, intermediate: CompletionIntermediate, **kwargs
117
- ) -> CompletionIntermediate:
118
- intermediate.input_ids = torch.cat(
119
- [
120
- torch.full(
121
- (intermediate.input_ids.size(0), 1), self.tokenizer.bos_token_id
122
- ),
123
- intermediate.input_ids,
124
- ],
125
- dim=1,
126
- )
127
- logits = self.model.decode(
128
- current_output=intermediate.input_ids,
129
- latents=intermediate.latents,
130
- condition_embeddings=intermediate.condition_embeddings,
131
- token_mask=None,
132
- condition_mask=intermediate.condition_mask,
133
- )
134
- new_toks = self.sampler.sample_ids(logits[:, -1:])
135
- intermediate.batches_completed |= (
136
- new_toks.squeeze(-1) == self.tokenizer.eos_token_id
137
- )
138
- torch.where(
139
- intermediate.batches_completed.unsqueeze(-1),
140
- self.tokenizer.pad_token_id,
141
- new_toks,
142
- )
143
- intermediate.input_ids = torch.cat([intermediate.input_ids, new_toks], dim=1)
144
- return intermediate
145
-
146
- @torch.inference_mode()
147
- def _completion_exit(
148
- self, intermediate: CompletionIntermediate, **kwargs
149
- ) -> Completion:
150
- return Completion(
151
- outputs=self.sampler(
152
- intermediate.input_ids, skip_special_tokens=True
153
- ),
154
- condition_embeddings=intermediate.condition_embeddings,
155
- condition_mask=intermediate.condition_mask,
156
- )
157
-
158
- def completion(
159
- self,
160
- latents: torch.Tensor,
161
- dataset: T,
162
- seq_feature: str,
163
- cond_features: list[str],
164
- device: torch.device | None = None,
165
- **kwargs,
166
- ) -> Completion:
167
- """Completes each batch in an input dataset with latent and condition guidance through the decoder.
168
-
169
- Using decoder-only inference, the model completes each batch in the provided dataset. Dataset input typing is
170
- uniquely defined by the used Collator. The input dataset is passed through the collator, then subject to
171
- autoregressive decoder-only inference through the model, where new tokens are sampled from intermediates and
172
- stored in a tensor format. Finally, on reaching end-of-sequence tokens or the maximum sequence length, the
173
- sequence is decoded by the tokenizer and returned along with the generated condition embeddings and masks in a
174
- Completion schema.
175
-
176
- TODO: Callbacks are planned to enable easy custom inference functions and streaming of text.
177
-
178
- Args:
179
- latents: Tensor of dimension (B, P * E) containing latent vectors for the batches.
180
- dataset: Dataset of type specified in Pipeline.collator.
181
- seq_feature: String to pass to collator containing the feature or column containing text sequences to
182
- complete.
183
- cond_features: List of strings to pass to collator containing features or columns containing numerical
184
- condition values.
185
- device: Torch device used for inference.
186
- **kwargs: Additional keyword arguments passed to Pipeline.collator.
187
-
188
- Returns:
189
- Completion: Completion object, containing outputs, as well as condition embeddings and masks for reuse.
190
- """
191
- intermediate = self._completion_entry(
192
- latents=latents,
193
- dataset=dataset,
194
- seq_feature=seq_feature,
195
- cond_features=cond_features,
196
- device=device,
197
- **kwargs,
198
- )
199
- while (
200
- intermediate.input_ids.size(1) < self.model.config_typed.input.max_len
201
- ) and (not intermediate.batches_completed.all()):
202
- intermediate = self._completion_step(intermediate=intermediate)
203
- return self._completion_exit(intermediate=intermediate)
@@ -1,70 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from collections.abc import Iterable
3
-
4
- import torch
5
-
6
- from kernel_elastic_autoencoder.tokenizer import Tokenizer
7
-
8
-
9
- class Sampler(ABC):
10
- """Base class for samplers.
11
-
12
- Provides a specification for sampling text sequences from logits.
13
- """
14
-
15
- def __init__(self, tokenizer: Tokenizer, **kwargs):
16
- """Instantiates a Sampler object.
17
-
18
- Args:
19
- tokenizer: Object implementing the Tokenizer protocol.
20
- **kwargs: Sampler-specific keyword args.
21
- """
22
- self.tokenizer = tokenizer
23
- """Object implementing the Tokenizer protocol."""
24
-
25
- @abstractmethod
26
- def __call__(
27
- self, logits: torch.Tensor, skip_special_tokens: bool, **kwargs
28
- ) -> Iterable[str]:
29
- """Interface method for implementing the last sampling step where outputs are decoded through the tokenizer.
30
-
31
- Args:
32
- logits: Tensor of dimension (B, S) containing IDs at the last step of inference.
33
- **kwargs: Keyword arguments.
34
-
35
- Returns:
36
- Iterable[str]: Iterable of strings decoded by the tokenizer.
37
- """
38
- ...
39
-
40
- @abstractmethod
41
- def sample_ids(self, logits: torch.Tensor, **kwargs) -> torch.Tensor:
42
- """Interface method for implementing index sampling from logits in intermediate steps.
43
-
44
- Args:
45
- logits: Tensor of dimension (B, S, L) containing logits.
46
- **kwargs: Keyword arguments.
47
-
48
- Returns:
49
- torch.Tensor: Tensor of dimension (B, S) containing vocabulary indices.
50
- """
51
- ...
52
-
53
-
54
- class Top1Sampler(Sampler):
55
- def __call__(self, logits: torch.Tensor, skip_special_tokens: bool, **kwargs) -> Iterable[str]:
56
- return self.tokenizer.decode(logits, skip_special_tokens=skip_special_tokens)
57
-
58
- def sample_ids(self, logits: torch.Tensor, **kwargs) -> torch.Tensor:
59
- """Implementation of index sampling from logits choosing the highest-probability token.
60
-
61
- Args:
62
- logits: Tensor of dimension (B, S, L) containing logits.
63
- **kwargs: Keyword arguments.
64
-
65
- Returns:
66
- torch.Tensor: Tensor of dimension (B, S) containing vocabulary indices.
67
- """
68
- return (
69
- torch.topk(logits, k=1, dim=-1, **kwargs).indices.squeeze(-1).to(torch.long)
70
- )
@@ -1,386 +0,0 @@
1
- import os
2
- from typing import Protocol
3
-
4
- import torch
5
- import torch.distributed as dist
6
- from pydantic import BaseModel, ConfigDict
7
- from torch import nn
8
- from torch.nn.parallel import DistributedDataParallel as DDP
9
- from torch.utils.data import DistributedSampler
10
-
11
- from kernel_elastic_autoencoder.collate import Collated
12
- from kernel_elastic_autoencoder.config import TrainingConfig
13
- from kernel_elastic_autoencoder.losses import Loss
14
- from kernel_elastic_autoencoder.model import Model
15
-
16
-
17
- def _is_local():
18
- return (
19
- dist.is_torchelastic_launched() and (dist.get_rank() == 0)
20
- ) or not dist.is_torchelastic_launched()
21
-
22
-
23
- class TrainerCallbackCtx(BaseModel):
24
- model_config = ConfigDict(arbitrary_types_allowed=True)
25
- dist: bool
26
- device_type: str
27
- local_rank: int | None
28
- model: Model | DDP
29
- optimizer: torch.optim.Optimizer
30
- scheduler: torch.optim.lr_scheduler.LRScheduler
31
- dataloader_train: torch.utils.data.DataLoader
32
- dataloader_test: torch.utils.data.DataLoader
33
-
34
- epoch: int | None = None
35
- rel_epoch: int | None = None
36
- batch: int | None = None
37
- train_loss: torch.Tensor | None = None
38
- test_loss: torch.Tensor | None = None
39
-
40
-
41
- class TrainerCallback(Protocol):
42
- """Protocol to be implemented for callback classes to a Trainer.
43
-
44
- On each hook, except before the Trainer is initialized, a TrainerCallbackCtx schema is passed with the appropriate
45
- information passed.
46
- """
47
-
48
- def before_init(self):
49
- """Called before training setup."""
50
- ...
51
-
52
- def after_init(self, ctx: TrainerCallbackCtx):
53
- """Called after training setup."""
54
- ...
55
-
56
- def before_epoch(self, ctx: TrainerCallbackCtx):
57
- """Called before each epoch."""
58
- ...
59
-
60
- def before_train_batch(self, ctx: TrainerCallbackCtx):
61
- """Called before each forward pass of a single training batch."""
62
- ...
63
-
64
- def after_train_batch(self, ctx: TrainerCallbackCtx):
65
- """Called after each forward pass of a single training batch."""
66
- ...
67
-
68
- def before_test_batch(self, ctx: TrainerCallbackCtx):
69
- """Called before each forward pass of a single test batch."""
70
- ...
71
-
72
- def after_test_batch(self, ctx: TrainerCallbackCtx):
73
- """Called after each forward pass of a single test batch."""
74
- ...
75
-
76
- def after_epoch(self, ctx: TrainerCallbackCtx):
77
- """Called after each epoch."""
78
- ...
79
-
80
- def after_training(self, ctx: TrainerCallbackCtx):
81
- """Called after training ends."""
82
- ...
83
-
84
-
85
- class TrainerDefaultCallback:
86
- # TODO: Implement sensible default logging.
87
- def before_init(self):
88
- pass
89
-
90
- def after_init(self, ctx: TrainerCallbackCtx):
91
- pass
92
-
93
- def before_epoch(self, ctx: TrainerCallbackCtx):
94
- pass
95
-
96
- def before_train_batch(self, ctx: TrainerCallbackCtx):
97
- pass
98
-
99
- def after_train_batch(self, ctx: TrainerCallbackCtx):
100
- pass
101
-
102
- def before_test_batch(self, ctx: TrainerCallbackCtx):
103
- pass
104
-
105
- def after_test_batch(self, ctx: TrainerCallbackCtx):
106
- pass
107
-
108
- def after_epoch(self, ctx: TrainerCallbackCtx):
109
- pass
110
-
111
- def after_training(self, ctx: TrainerCallbackCtx):
112
- pass
113
-
114
-
115
- class Trainer:
116
- def __init__(
117
- self,
118
- config: dict | TrainingConfig,
119
- callbacks: tuple[TrainerCallback] = (TrainerDefaultCallback(),),
120
- ):
121
- """Instantiates a Trainer object.
122
-
123
- Args:
124
- config: Dictionary or TrainingConfig schema defining model parameters. Will be validated with TrainingConfig
125
- regardless of input type.
126
- callbacks: Tuple of callback classes implementing TrainerCallback.
127
- """
128
- self.config = config
129
- """Configuration object for Hugging Face Hub compatible serialization. Not recommended to use, as it is
130
- internal-use. Use Trainer.config_typed instead."""
131
- config_typed = TrainingConfig.model_validate(config)
132
- self.config_typed: TrainingConfig = config_typed
133
- """Type-validated config in a Pydantic TrainingConfig schema, recommended for public API use."""
134
- self.callbacks = callbacks
135
- """Tuple of callback classes implementing TrainerCallback."""
136
-
137
- self._ctx: TrainerCallbackCtx
138
-
139
- self._model: DDP | Model
140
- self._dist: bool
141
- self._loss_fn: nn.Module
142
- self._optimizer: torch.optim.Optimizer
143
- self._scheduler: torch.optim.lr_scheduler.LRScheduler
144
- self._dataloader_train: torch.utils.data.DataLoader
145
- self._dataloader_test: torch.utils.data.DataLoader
146
- self._device_type: str
147
- self._local_rank: int | None
148
-
149
- def train(
150
- self,
151
- model: Model,
152
- ds: Collated,
153
- train_split: float = 0.9,
154
- checkpoint: str = "./checkpoint",
155
- ):
156
- """Trains a model with a Collated dataset. Optionally resumes from an existing checkpoint.
157
-
158
- Args:
159
- model: Freshly instantiated model.
160
- ds: Tensor dataset following the Collated schema.
161
- train_split: Fraction of dataset used for training. Must be between 0 and 1.
162
- checkpoint: Path of local checkpoint to be saved and/or resumed.
163
- """
164
- next_epoch = self._setup(model, ds, train_split, checkpoint)
165
- for epoch in range(next_epoch, self.config_typed.common.max_epochs):
166
- self._ctx.rel_epoch = epoch - next_epoch
167
- self._epoch(epoch, checkpoint)
168
- self._ctx.epoch = None
169
- self._ctx.rel_epoch = None
170
- if _is_local():
171
- for cb in self.callbacks:
172
- cb.after_training(self._ctx)
173
-
174
- def _setup(
175
- self,
176
- model: Model,
177
- ds: Collated,
178
- train_split: float,
179
- checkpoint: str,
180
- ):
181
- if _is_local():
182
- for cb in self.callbacks:
183
- cb.before_init()
184
- self._model = model
185
- self._dist = dist.is_torchelastic_launched()
186
- if self._dist:
187
- self._model, self._device_type, self._local_rank = self._setup_ddp()
188
- else:
189
- self._device_type, self._local_rank = self._setup_no_ddp()
190
- self._loss_fn = self._setup_loss(model)
191
- self._optimizer, self._scheduler = self._setup_optimizer()
192
- self._dataloader_train, self._dataloader_test = self._setup_dataloaders(
193
- ds, train_split
194
- )
195
- next_epoch = 0
196
- if os.path.exists(checkpoint):
197
- next_epoch = self._resume(checkpoint)
198
- self._ctx = TrainerCallbackCtx(
199
- dist=self._dist,
200
- device_type=self._device_type,
201
- local_rank=self._local_rank,
202
- model=self._model,
203
- optimizer=self._optimizer,
204
- scheduler=self._scheduler,
205
- dataloader_train=self._dataloader_train,
206
- dataloader_test=self._dataloader_test,
207
- )
208
- if _is_local():
209
- for cb in self.callbacks:
210
- cb.after_init(self._ctx)
211
- return next_epoch
212
-
213
- def _setup_loss(self, model: Model):
214
- _loss_fn = Loss(
215
- hp_lambda=self.config_typed.hyperparameters.hp_lambda,
216
- hp_delta=self.config_typed.hyperparameters.hp_delta,
217
- hp_sigma=self.config_typed.hyperparameters.hp_sigma,
218
- kernel_dist_size=self.config_typed.hyperparameters.kernel_dist_size,
219
- padding_idx=model.config_typed.common.padding_idx,
220
- embedding_dim=model.config_typed.common.embedding_dim,
221
- pooling_dim=model.config_typed.common.pooling_dim,
222
- )
223
- return _loss_fn
224
-
225
- def _setup_optimizer(self):
226
- optimizer = self.config_typed.optimizer.optimizer_fn(
227
- self._model.parameters(), **self.config_typed.optimizer.optimizer_params
228
- )
229
- scheduler = self.config_typed.optimizer.scheduler_fn(
230
- optimizer, **self.config_typed.optimizer.scheduler_params
231
- )
232
- return optimizer, scheduler
233
-
234
- def _setup_ddp(self) -> tuple[DDP, str, int]:
235
- device_type, vendor_backend = self._get_backend()
236
- dist.init_process_group(backend=vendor_backend)
237
- local_rank = int(os.environ["LOCAL_RANK"])
238
- model = DDP(self._model.to(local_rank))
239
- return model, device_type, local_rank
240
-
241
- def _setup_no_ddp(self):
242
- device_type, _ = self._get_backend()
243
- return device_type, None
244
-
245
- def _setup_dataloaders(self, ds: Collated, train_split: float):
246
- dataset = torch.utils.data.TensorDataset(
247
- ds.input_ids, ds.conditions, ds.token_mask, ds.condition_mask
248
- )
249
- dataset_train, dataset_test = torch.utils.data.random_split(
250
- dataset, [train_split, 1 - train_split]
251
- )
252
- if self._dist:
253
- sampler_train = DistributedSampler(dataset_train)
254
- sampler_test = DistributedSampler(dataset_test)
255
- else:
256
- sampler_train = torch.utils.data.RandomSampler(dataset_train)
257
- sampler_test = torch.utils.data.SequentialSampler(dataset_test)
258
- _dataloader_train = torch.utils.data.DataLoader(
259
- dataset_train,
260
- batch_size=self.config_typed.common.batch_size,
261
- sampler=sampler_train,
262
- pin_memory=True,
263
- )
264
- _dataloader_test = torch.utils.data.DataLoader(
265
- dataset_test,
266
- batch_size=self.config_typed.common.batch_size,
267
- sampler=sampler_test,
268
- pin_memory=True,
269
- )
270
- return _dataloader_train, _dataloader_test
271
-
272
- def _resume(
273
- self,
274
- checkpoint: str,
275
- ):
276
- train_state = torch.load(os.path.join(checkpoint, "train_state.pt"))
277
- self._optimizer.load_state_dict(train_state["optimizer"])
278
- self._scheduler.load_state_dict(train_state["scheduler"])
279
- self._model.from_pretrained(checkpoint) # type: ignore
280
- return train_state["next_epoch"]
281
-
282
- def _epoch(
283
- self,
284
- epoch: int,
285
- checkpoint: str,
286
- ):
287
- self._ctx.epoch = epoch
288
- if _is_local():
289
- for cb in self.callbacks:
290
- cb.before_epoch(self._ctx)
291
-
292
- self._model.train()
293
- self._ctx.batch = 0
294
- for input_ids, conditions, token_mask, condition_mask in self._dataloader_train:
295
- if _is_local():
296
- for cb in self.callbacks:
297
- cb.before_train_batch(self._ctx)
298
- self._ctx.train_loss = self._batch_train(
299
- input_ids, conditions, token_mask, condition_mask
300
- )
301
- if _is_local():
302
- for cb in self.callbacks:
303
- cb.after_train_batch(self._ctx)
304
- self._ctx.batch += 1
305
-
306
- self._model.eval()
307
- self._ctx.batch = 0
308
- for input_ids, conditions, token_mask, condition_mask in self._dataloader_test:
309
- if _is_local():
310
- for cb in self.callbacks:
311
- cb.before_test_batch(self._ctx)
312
- self._ctx.test_loss = self._batch_test(
313
- input_ids, conditions, token_mask, condition_mask
314
- )
315
- if _is_local():
316
- for cb in self.callbacks:
317
- cb.after_test_batch(self._ctx)
318
- self._ctx.batch += 1
319
-
320
- self._end_epoch(epoch, checkpoint)
321
- self._ctx.train_loss = None
322
- self._ctx.test_loss = None
323
- self._ctx.batch = None
324
- if _is_local():
325
- for cb in self.callbacks:
326
- cb.after_epoch(self._ctx)
327
-
328
- def _end_epoch(self, epoch: int, checkpoint: str):
329
- self._scheduler.step(epoch)
330
- self._save_state(checkpoint, epoch)
331
-
332
- def _batch_train(self, input_ids, conditions, token_mask, condition_mask):
333
- self._optimizer.zero_grad()
334
- with torch.amp.autocast(self._device_type):
335
- prediction, prediction_noise, latents_noise = self._model.forward(
336
- input_ids, conditions, token_mask, condition_mask
337
- )
338
- loss = self._loss_fn(
339
- prediction, prediction_noise, input_ids[:, 1:], latents_noise
340
- )
341
- loss.backward()
342
- self._optimizer.step()
343
- return loss.detach()
344
-
345
- def _batch_test(self, input_ids, conditions, token_mask, condition_mask):
346
- with torch.no_grad():
347
- with torch.amp.autocast(self._device_type):
348
- prediction, prediction_noise, latents_noise = self._model.forward(
349
- input_ids,
350
- conditions,
351
- token_mask,
352
- condition_mask,
353
- )
354
- loss = self._loss_fn(
355
- prediction, prediction_noise, input_ids[:, 1:], latents_noise
356
- )
357
- return loss.detach()
358
-
359
- def _save_state(self, checkpoint: str, epoch: int):
360
- if _is_local():
361
- os.makedirs(checkpoint, exist_ok=True)
362
- self._model.save_pretrained(checkpoint) # type: ignore
363
- torch.save(
364
- {
365
- "next_epoch": epoch + 1,
366
- "optimizer": self._optimizer.state_dict(),
367
- "scheduler": self._scheduler.state_dict(),
368
- },
369
- os.path.join(checkpoint, "train_state.pt"),
370
- )
371
- self.config_typed.to_json(os.path.join(checkpoint, "train_config.json"))
372
-
373
- def _get_backend(self) -> tuple[str, str]:
374
- if torch.accelerator.is_available():
375
- device_type = torch.accelerator.current_accelerator().type # type: ignore
376
- vendor_backend = torch.distributed.get_default_backend_for_device(
377
- device_type
378
- )
379
-
380
- else:
381
- device_type = torch.device("cpu").type
382
- vendor_backend = torch.distributed.get_default_backend_for_device(
383
- device_type
384
- )
385
-
386
- return device_type, vendor_backend