From c071f35db26cb860cf99154b1a81d5ef2bacfeff Mon Sep 17 00:00:00 2001 From: Julianvnoortwijk <57176112+Julianvnoortwijk@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:31:10 +0200 Subject: [PATCH 1/7] Update tiger_generation_model.py decoder-only code --- .../semantic_id/tiger_generation_model.py | 520 +++++++++++++++++- 1 file changed, 519 insertions(+), 1 deletion(-) diff --git a/src/models/modules/semantic_id/tiger_generation_model.py b/src/models/modules/semantic_id/tiger_generation_model.py index ef8f83f..6e5760c 100755 --- a/src/models/modules/semantic_id/tiger_generation_model.py +++ b/src/models/modules/semantic_id/tiger_generation_model.py @@ -283,6 +283,8 @@ def _beam_search_one_step( device=candidate_logits.device, ).unsqueeze(1) ) + # valid_prefix_mask is (num_embeddings_per_hierarchy,); broadcasting over + # the batch handles all rows, so no further masking is needed here. candidate_logits[:, ~valid_prefix_mask] = float("-inf") else: # we prune all beams with prefixes that cannot be mapped to a valid item @@ -302,7 +304,7 @@ def _beam_search_one_step( dim=1, ) ).reshape(-1, self.num_embeddings_per_hierarchy) - candidate_logits[~valid_prefix_mask] = float("-inf") + candidate_logits[~valid_prefix_mask] = float("-inf") candidate_logits = torch.nn.functional.softmax(candidate_logits, dim=-1) proba, indices = torch.sort(candidate_logits, descending=True) @@ -953,6 +955,522 @@ def model_step( return model_output, loss +class SemanticIDDecoderOnly(SemanticIDGenerativeRecommender): + """ + Decoder-only generative recommender. + + Unlike the encoder-decoder TIGER model (``SemanticIDEncoderDecoder``), this model + uses a single causal transformer that consumes the whole semantic-ID sequence and + predicts the next semantic ID at each step (GPT-style). It is the decoder-only + variant that the GRID paper compares against but does not release code for. + + Two training objectives are supported via ``loss_on_all_positions``: + * ``True`` -> the loss is applied at every position of the sequence (full + next-token language-modelling loss). Each forward pass yields a training + signal at every item/hierarchy step, which is much more sample efficient. + * ``False`` -> the loss is applied only on the final item of the sequence, which + mirrors the encoder-decoder objective (loss on the single target item) and is + the apples-to-apples baseline. + + The causal backbone is passed through the ``huggingface_model`` config slot and must + be a standalone decoder (``is_decoder=True``, ``is_encoder_decoder=False``). The + ``decoder`` config slot is unused and should be ``null``. + """ + + def __init__( + self, + top_k_for_generation: int = 10, + codebooks: torch.Tensor = None, + embedding_dim: int = None, + num_hierarchies: int = None, + num_embeddings_per_hierarchy: int = None, + num_user_bins: Optional[int] = None, + mlp_layers: Optional[int] = None, + should_check_prefix: bool = False, + loss_on_all_positions: bool = True, + prediction_key_name: str = "user_id", + prediction_value_name: str = "semantic_ids", + **kwargs, + ) -> None: + """ + Initialize the SemanticIDDecoderOnly module. + + Parameters: + top_k_for_generation (int): the beam width used during generation. + codebooks (torch.Tensor): the codebooks for the semantic ID, + of shape (num_hierarchies, num_embeddings_per_hierarchy). + embedding_dim (int): the dimension of the embeddings. If None, inferred from + the causal backbone's hidden size. + num_hierarchies (int): the number of hierarchies in the codebooks. + num_embeddings_per_hierarchy (int): the number of embeddings per hierarchy. + num_user_bins (Optional[int]): number of user bins (None disables user tokens). + mlp_layers (Optional[int]): if set, replaces each T5 feed-forward block with a + multi-layer MLP (matching the encoder-decoder model). + should_check_prefix (bool): whether to prune beams to valid codebook prefixes. + loss_on_all_positions (bool): see the class docstring. + """ + + if num_hierarchies is None or num_embeddings_per_hierarchy is None: + num_hierarchies, num_embeddings_per_hierarchy = ( + codebooks.shape[0], + codebooks.max().item() + 1, + ) + if embedding_dim is None: + embedding_dim = kwargs["huggingface_model"].config.d_model + + super().__init__( + codebooks=codebooks, + num_hierarchies=num_hierarchies, + num_embeddings_per_hierarchy=num_embeddings_per_hierarchy, + embedding_dim=embedding_dim, + top_k_for_generation=top_k_for_generation, + should_check_prefix=should_check_prefix, + **kwargs, + ) + + self.loss_on_all_positions = loss_on_all_positions + + # the causal backbone arrives via the huggingface_model slot, which the base + # class stored as self.encoder. We re-wrap it and drop the encoder/decoder + # attributes so the model is unambiguously decoder-only. + self.backbone = SemanticIDCausalDecoderModule(decoder=self.encoder) + self.encoder = None + self.decoder = None + + if mlp_layers is not None: + # bloat the feed-forward blocks, matching the encoder-decoder model + # TODO: this currently only works for T5 + for name, module in self.named_modules(): + if isinstance(module, transformers.models.t5.modeling_t5.T5LayerFF): + parent_module, attr_name = get_parent_module_and_attr(self, name) + setattr( + parent_module, + attr_name, + T5MultiLayerFF( + config=self.backbone.decoder.config, + num_layers=mlp_layers, + ), + ) + + # bos token prompts the decoder so that the first sequence position has context + self.bos_token = torch.nn.Parameter( + torch.randn(1, self.embedding_dim), requires_grad=True + ) + + # one projection head per hierarchy; head h predicts tokens whose position in + # the flattened sequence satisfies (position % num_hierarchies) == h + self.heads = torch.nn.ModuleList( + [ + torch.nn.Linear( + self.embedding_dim, + self.num_embeddings_per_hierarchy, + bias=False, + ) + for _ in range(self.num_hierarchies) + ] + ) + + # single embedding table shared across hierarchies (offsets disambiguate them) + self.item_sid_embedding_table = self._spawn_embedding_tables( + num_embeddings=self.num_embeddings_per_hierarchy * self.num_hierarchies, + embedding_dim=self.embedding_dim, + ) + + self.user_embedding: torch.nn.Embedding = ( + self._spawn_embedding_tables( + num_embeddings=num_user_bins, + embedding_dim=self.embedding_dim, + ) + if num_user_bins + else None + ) + + self.prediction_key_name = prediction_key_name + self.prediction_value_name = prediction_value_name + + def _embed_sids( + self, + sids: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + """ + Embed a batch of raw semantic IDs into hierarchy-aware embeddings. + + Parameters: + sids (torch.Tensor): raw semantic IDs of shape (batch_size, seq_len), where + column j holds the (j % num_hierarchies)-th hierarchy of an item. + attention_mask (Optional[torch.Tensor]): if given, padded positions are + zeroed before the table lookup. + """ + shifted_sids = self._add_repeating_offset_to_rows( + input_sids=sids, + codebook_size=self.num_embeddings_per_hierarchy, + num_hierarchies=self.num_hierarchies, + attention_mask=attention_mask, + ) + return self.item_sid_embedding_table(shifted_sids) + + def _prepend_user_token( + self, + inputs_embeds: torch.Tensor, + attention_mask: torch.Tensor, + user_id: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + """Optionally prepend a user embedding to the sequence (mirrors the encoder).""" + if user_id is None or self.user_embedding is None: + return inputs_embeds, attention_mask + + user_id = user_id[:, 0] + user_embeds = self.user_embedding( + torch.remainder(user_id, self.user_embedding.num_embeddings) + ) + inputs_embeds = torch.cat([user_embeds.unsqueeze(1), inputs_embeds], dim=1) + user_attention_mask = torch.ones( + attention_mask.size(0), 1, device=attention_mask.device + ).long() + attention_mask = torch.cat([user_attention_mask, attention_mask], dim=1) + return inputs_embeds, attention_mask + + def model_step( + self, + model_input: SequentialModelInputData, + label_data: Optional[SequentialModuleLabelData] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Run a forward pass and, when labels are present, compute the training loss. + + Args: + model_input: the input data to the model. + label_data: the label data. Optional, as it is absent during inference. + """ + inputs = { + self.feature_to_model_input_map.get(k, k): v + for k, v in model_input.transformed_sequences.items() + } + input_ids = inputs["input_ids"] + user_id = inputs.get("user_id", None) + attention_mask = model_input.mask + + # inference / free-form generation + if label_data is None: + generated_ids, marginal_probs = self.generate( + attention_mask=attention_mask, + input_ids=input_ids, + user_id=user_id, + ) + return generated_ids, 0 + + fut_ids = None + for label in label_data.labels: + fut_ids = label_data.labels[label].reshape(attention_mask.size(0), -1) + + hidden, full_sequence, full_mask, rows, cols = self._decode_full_sequence( + input_ids=input_ids, + attention_mask=attention_mask, + future_ids=fut_ids, + user_id=user_id, + ) + loss = self._compute_loss( + hidden=hidden, + full_sequence=full_sequence, + full_mask=full_mask, + future_ids=fut_ids, + rows=rows, + cols=cols, + ) + return hidden, loss + + def _decode_full_sequence( + self, + input_ids: torch.Tensor, + attention_mask: torch.Tensor, + future_ids: torch.Tensor, + user_id: Optional[torch.Tensor] = None, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Reconstruct the full item sequence (history + target item), run it through the + causal backbone with a prepended bos token, and return the per-position hidden + states aligned so that ``hidden[:, i]`` predicts ``full_sequence[:, i]``. + + The data pipeline (NextKTokenMasking) hands us the history with the target item + masked out, plus the target item as ``future_ids``. Because the history always + consists of whole items, the number of valid history tokens is a multiple of + num_hierarchies, so the target item occupies the contiguous block right after it. + """ + batch_size, seq_len = input_ids.shape + num_h = self.num_hierarchies + device = input_ids.device + + history_len = attention_mask.sum(dim=1) # (batch_size,), multiple of num_h + hierarchy_offsets = torch.arange(num_h, device=device) + rows = ( + torch.arange(batch_size, device=device).unsqueeze(1).expand(batch_size, num_h) + ) + cols = history_len.unsqueeze(1) + hierarchy_offsets # (batch_size, num_h) + + # scatter the target item back into the sequence to recover the full sequence + full_sequence = input_ids.clone() + full_sequence[rows, cols] = future_ids.to(full_sequence.dtype) + full_mask = attention_mask.clone() + full_mask[rows, cols] = 1 + + inputs_embeds = self._embed_sids(full_sequence, attention_mask=full_mask) + inputs_embeds, dec_mask = self._prepend_user_token( + inputs_embeds, full_mask, user_id + ) + + # prepend bos so position 0 has context to predict the first token + bos = self.bos_token.unsqueeze(0).expand(inputs_embeds.size(0), 1, -1) + inputs_embeds = torch.cat([bos, inputs_embeds], dim=1) + dec_mask = torch.cat( + [torch.ones(dec_mask.size(0), 1, device=device).long(), dec_mask], dim=1 + ) + + decoder_output = self.backbone( + sequence_embedding=inputs_embeds, attention_mask=dec_mask + ) + + # drop the last output (predicting past the end) so the remaining positions + # align with the (sequence-token) targets. If a user token was prepended it sits + # right after bos, so we slice it off to realign with full_sequence columns. + num_prefix = 1 + (1 if self._uses_user_token(user_id) else 0) + hidden = decoder_output[:, num_prefix - 1 : -1, :] + return hidden, full_sequence, full_mask, rows, cols + + def _uses_user_token(self, user_id: Optional[torch.Tensor]) -> bool: + return user_id is not None and self.user_embedding is not None + + def _compute_loss( + self, + hidden: torch.Tensor, + full_sequence: torch.Tensor, + full_mask: torch.Tensor, + future_ids: torch.Tensor, + rows: torch.Tensor, + cols: torch.Tensor, + ) -> torch.Tensor: + """ + Cross-entropy loss summed over hierarchies. + + With ``loss_on_all_positions`` we score the next-token prediction at every valid + position; otherwise we only score the final target item, matching the + encoder-decoder objective. + """ + num_h = self.num_hierarchies + + if not self.loss_on_all_positions: + # only the last item contributes (history_len is a multiple of num_h, so the + # target token at column c has hierarchy c % num_h == its within-item index) + last_hidden = hidden[rows, cols] # (batch_size, num_h, emb_dim) + loss = 0 + for hierarchy in range(num_h): + logits = self.heads[hierarchy](last_hidden[:, hierarchy, :]) + loss = loss + self.loss_function( + input=logits, target=future_ids[:, hierarchy].long() + ) + return loss + + seq_len = full_sequence.size(1) + column_index = torch.arange(seq_len, device=full_sequence.device) + loss = 0 + for hierarchy in range(num_h): + position_mask = (column_index % num_h) == hierarchy + valid = full_mask[:, position_mask].bool() + if not valid.any(): + continue + selected_hidden = hidden[:, position_mask, :] + logits = self.heads[hierarchy](selected_hidden)[valid] + targets = full_sequence[:, position_mask][valid].long() + loss = loss + self.loss_function(input=logits, target=targets) + return loss + + def _build_generation_prompt( + self, + input_ids: torch.Tensor, + attention_mask: torch.Tensor, + user_id: Optional[torch.Tensor] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Build the left-padded prompt embeddings (bos + history) for generation. + + Left-padding moves all padding to the front so the last valid token is always + at the rightmost position. This lets us append generated tokens contiguously and + read the next-token prediction from position -1. + """ + batch_size = input_ids.size(0) + device = input_ids.device + + inputs_embeds = self._embed_sids(input_ids, attention_mask=attention_mask) + inputs_embeds, mask = self._prepend_user_token( + inputs_embeds, attention_mask, user_id + ) + + bos = self.bos_token.unsqueeze(0).expand(batch_size, 1, -1) + inputs_embeds = torch.cat([bos, inputs_embeds], dim=1) + mask = torch.cat( + [torch.ones(batch_size, 1, device=device).long(), mask], dim=1 + ) + + # roll each row right by its number of padding tokens -> padding ends up on left + total_len = mask.size(1) + pad_counts = (mask == 0).sum(dim=1) + roll_index = ( + torch.arange(total_len, device=device).unsqueeze(0) - pad_counts.unsqueeze(1) + ) % total_len + mask = torch.gather(mask, 1, roll_index) + inputs_embeds = torch.gather( + inputs_embeds, + 1, + roll_index.unsqueeze(-1).expand(-1, -1, inputs_embeds.size(-1)), + ) + return inputs_embeds, mask + + def generate( + self, + attention_mask: torch.Tensor, + input_ids: torch.Tensor, + user_id: torch.Tensor = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Generate the next item's semantic ID via constrained beam search. + + Generation is cache-free: at each hierarchy we re-run the backbone over the + (left-padded) prompt with the already-generated beam tokens appended. Since + num_hierarchies is small, this keeps the implementation simple and correct while + reusing the shared beam-search step. + """ + prompt_embeds, prompt_mask = self._build_generation_prompt( + input_ids=input_ids, attention_mask=attention_mask, user_id=user_id + ) + batch_size = input_ids.size(0) + emb_dim = prompt_embeds.size(-1) + device = prompt_embeds.device + + generated_ids = None + marginal_log_prob = None + + for hierarchy in range(self.num_hierarchies): + if generated_ids is None: + decoder_output = self.backbone( + sequence_embedding=prompt_embeds, attention_mask=prompt_mask + ) + else: + beams = generated_ids.reshape(-1, hierarchy) # (batch*top_k, hierarchy) + beam_embeds = self._embed_sids(beams) # (batch*top_k, hierarchy, emb_dim) + repeated_embeds = prompt_embeds.repeat_interleave( + self.top_k_for_generation, dim=0 + ) + repeated_mask = prompt_mask.repeat_interleave( + self.top_k_for_generation, dim=0 + ) + sequence_embeds = torch.cat([repeated_embeds, beam_embeds], dim=1) + sequence_mask = torch.cat( + [ + repeated_mask, + torch.ones(beams.size(0), hierarchy, device=device).long(), + ], + dim=1, + ) + decoder_output = self.backbone( + sequence_embedding=sequence_embeds, attention_mask=sequence_mask + ) + + latest_output_representation = decoder_output[:, -1, :] + candidate_logits = self.heads[hierarchy](latest_output_representation) + + generated_ids, marginal_log_prob, _ = self._beam_search_one_step( + candidate_logits=candidate_logits, + generated_ids=generated_ids, + marginal_log_prob=marginal_log_prob, + past_key_values=None, + hierarchy=hierarchy, + batch_size=batch_size, + ) + + return generated_ids, marginal_log_prob + + def forward( + self, + input_ids: torch.Tensor, + attention_mask: torch.Tensor, + future_ids: torch.Tensor, + user_id: Optional[torch.Tensor] = None, + **kwargs: Any, + ) -> torch.Tensor: + """Run the causal backbone over the full sequence; returns per-position hidden states.""" + hidden, _, _, _, _ = self._decode_full_sequence( + input_ids=input_ids, + attention_mask=attention_mask, + future_ids=future_ids, + user_id=user_id, + ) + return hidden + + def predict_step(self, batch: SequentialModelInputData): + generated_sids, _ = self.model_step(batch) + ids = [ + id.item() if isinstance(id, torch.Tensor) else id + for id in batch.user_id_list + ] + model_output = OneKeyPerPredictionOutput( + keys=ids, + predictions=generated_sids, + key_name=self.prediction_key_name, + prediction_name=self.prediction_value_name, + ) + return model_output + + def _make_deterministic(self, is_training: bool): + """Toggle the backbone's train/eval mode (mirrors the encoder-decoder model).""" + self.backbone.decoder.is_training = is_training + if is_training: + self.backbone.decoder.train() + else: + self.backbone.decoder.eval() + + +class SemanticIDCausalDecoderModule(torch.nn.Module): + """ + Wraps a standalone causal transformer (e.g. a T5Stack with is_decoder=True and no + encoder) for the decoder-only generative recommender. Self-attention only: no + encoder hidden states are passed, so the cross-attention sublayers are never invoked. + """ + + def __init__( + self, + decoder: transformers.PreTrainedModel, + ) -> None: + super().__init__() + assert decoder.config.is_decoder == True, "Backbone must be a decoder model" + assert ( + decoder.config.is_encoder_decoder == False + ), "Backbone must be a standalone decoder model" + + self.decoder = decoder + # drop the token embedding table; we feed inputs_embeds directly + delete_module(self.decoder, "embed_tokens") + delete_module(self.decoder, "shared") + reset_parameters(self.decoder) + + def forward( + self, + sequence_embedding: torch.Tensor, + attention_mask: torch.Tensor, + use_cache: bool = False, + past_key_values: Optional[DynamicCache] = None, + ) -> torch.Tensor: + decoder_outputs = self.decoder( + inputs_embeds=sequence_embedding, + attention_mask=attention_mask, + use_cache=use_cache, + past_key_values=past_key_values, + ) + embeddings = decoder_outputs.last_hidden_state + if use_cache: + return embeddings, decoder_outputs.past_key_values + return embeddings + + class SemanticIDDecoderModule(torch.nn.Module): """ This is an in-house replication of the decoder module proposed in TIGER paper, From 2f065ad6469eacfa14a27ff87fd3c1a83bc5861e Mon Sep 17 00:00:00 2001 From: Julianvnoortwijk <57176112+Julianvnoortwijk@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:32:33 +0200 Subject: [PATCH 2/7] Update tiger_train_flat.yaml --- configs/experiment/tiger_train_flat.yaml | 188 ----------------------- 1 file changed, 188 deletions(-) diff --git a/configs/experiment/tiger_train_flat.yaml b/configs/experiment/tiger_train_flat.yaml index 6a4d9e1..2ad72d0 100644 --- a/configs/experiment/tiger_train_flat.yaml +++ b/configs/experiment/tiger_train_flat.yaml @@ -148,191 +148,3 @@ data_loading: assign_files_by_size: true oov_token: ${data_loading.train_dataloader_config.dataloader.oov_token} masking_token: ${data_loading.train_dataloader_config.dataloader.masking_token} - sequence_length: ${data_loading.train_dataloader_config.dataloader.sequence_length} - padding_token: ${data_loading.train_dataloader_config.dataloader.padding_token} - drop_last: false - persistent_workers: false - collate_fn: - _target_: src.data.loading.components.collate_functions.collate_fn_train - _partial_: true - sequence_length: ${data_loading.train_dataloader_config.dataloader.sequence_length} - padding_token: ${data_loading.train_dataloader_config.dataloader.padding_token} - dataset_config: ${data_loading.train_dataloader_config.dataloader.dataset_config} - pin_memory: false - test_dataloader_config: - dataloader: - _target_: src.data.loading.components.interfaces.SequenceDataloaderConfig - dataset_class: - _target_: src.data.loading.components.dataloading.UnboundedSequenceIterable - _partial_: true - data_folder: ${paths.data_dir}/testing - should_shuffle_rows: false - labels: - sequence_data: - transform: - _target_: src.data.loading.components.label_function.NextKTokenMasking - next_k: ${model.num_hierarchies} - batch_size_per_device: 8 - num_workers: 8 - timeout: 60 - assign_files_by_size: true - oov_token: ${data_loading.train_dataloader_config.dataloader.oov_token} - masking_token: ${data_loading.train_dataloader_config.dataloader.masking_token} - sequence_length: ${data_loading.train_dataloader_config.dataloader.sequence_length} - padding_token: ${data_loading.train_dataloader_config.dataloader.padding_token} - drop_last: false - persistent_workers: false - collate_fn: - _target_: src.data.loading.components.collate_functions.collate_fn_train - _partial_: true - sequence_length: ${data_loading.train_dataloader_config.dataloader.sequence_length} - padding_token: ${data_loading.train_dataloader_config.dataloader.padding_token} - dataset_config: ${data_loading.train_dataloader_config.dataloader.dataset_config} - pin_memory: false - datamodule: - _target_: src.data.loading.datamodules.sequence_datamodule.SequenceDataModule - train_dataloader_config: ${..train_dataloader_config.dataloader} - val_dataloader_config: ${..val_dataloader_config.dataloader} - test_dataloader_config: ${..test_dataloader_config.dataloader} -model: - huggingface_model: - _target_: transformers.T5EncoderModel - config: - _target_: transformers.T5Config - vocab_size: 256 - d_model: 128 - num_heads: 6 - dropout_rate: 0.15 - d_ff: 1024 - d_kv: 64 - num_layers: 4 - _target_: src.models.modules.semantic_id.tiger_generation_model.SemanticIDEncoderDecoder - feature_to_model_input_map: - sequence_data: input_ids - user_id: user_id - postprocessor: null - aggregator: null - loss_function: ${loss.loss_function} - optimizer: ${optim.optimizer} - scheduler: ${optim.scheduler} - evaluator: ${eval.evaluator} - weight_tying: true - compile: false - decoder: - _target_: transformers.models.t5.modeling_t5.T5Stack - config: - _target_: transformers.models.t5.configuration_t5.T5Config - vocab_size: ${model.huggingface_model.config.vocab_size} - d_model: ${model.huggingface_model.config.d_model} - num_heads: ${model.huggingface_model.config.num_heads} - dropout_rate: 0.15 - d_ff: ${model.huggingface_model.config.d_ff} - d_kv: ${model.huggingface_model.config.d_kv} - num_layers: 4 - is_decoder: true - is_encoder_decoder: false - embed_tokens: - _target_: torch.nn.Embedding - num_embeddings: ${model.huggingface_model.config.vocab_size} - embedding_dim: ${model.huggingface_model.config.d_model} - num_hierarchies: ${num_hierarchies} - num_user_bins: null - codebooks: ${data_loading.train_dataloader_config.dataloader.dataset_config.semantic_id_map.sequence_data} - mlp_layers: 2 -callbacks: - model_checkpoint: - _target_: lightning.pytorch.callbacks.ModelCheckpoint - dirpath: ${paths.output_dir}/checkpoints - filename: checkpoint_{epoch:03d}_{step:06d} - monitor: val/recall@5 - verbose: true - save_last: null - save_top_k: 1 - mode: max - auto_insert_metric_name: true - save_weights_only: false - every_n_train_steps: null - train_time_interval: null - every_n_epochs: null - save_on_train_epoch_end: false - early_stopping: - _target_: lightning.pytorch.callbacks.EarlyStopping - monitor: ${callbacks.model_checkpoint.monitor} - min_delta: 0.0 - patience: 10 - verbose: true - mode: ${callbacks.model_checkpoint.mode} - strict: true - check_finite: true - stopping_threshold: null - divergence_threshold: null - check_on_train_epoch_end: false - model_summary: - _target_: lightning.pytorch.callbacks.RichModelSummary - max_depth: -1 - restart_job: - _target_: src.utils.restart_job.RestartAndLoadCheckpointCallback - metadata_dir: ${paths.metadata_dir} -logger: - csv: - _target_: lightning.pytorch.loggers.csv_logs.CSVLogger - save_dir: ${paths.output_dir} - name: csv/ - prefix: '' -trainer: - _target_: lightning.pytorch.trainer.Trainer - default_root_dir: ${paths.output_dir} - min_steps: 1 - max_steps: 320000 - max_epochs: 10 - accelerator: gpu - devices: -1 - num_nodes: 1 - precision: 32-true - log_every_n_steps: 100 - val_check_interval: 1600 - deterministic: false - accumulate_grad_batches: 16 - profiler: - _target_: lightning.pytorch.profilers.PassThroughProfiler - strategy: ddp - sync_batchnorm: true - num_sanity_val_steps: 0 - min_epochs: 0 -paths: - root_dir: . - data_dir: ${data_dir} - log_dir: ${paths.root_dir}/logs - output_dir: ${hydra:runtime.output_dir} - work_dir: ${hydra:runtime.cwd} - profile_dir: ${hydra:run.dir}/profile_output - metadata_dir: ${paths.output_dir}/metadata -extras: - ignore_warnings: false - enforce_tags: true - print_config_warnings: true - print_config: true -loss: - loss_function: - _target_: torch.nn.CrossEntropyLoss -optim: - optimizer: - _target_: torch.optim.Adam - _partial_: true - lr: 0.001 - weight_decay: 0.0001 - scheduler: null -eval: - evaluator: - _target_: src.components.eval_metrics.SIDRetrievalEvaluator - top_k_list: - - 5 - - 10 - metrics: - ndcg: - _target_: src.components.eval_metrics.NDCG - _partial_: true - recall: - _target_: src.components.eval_metrics.Recall - _partial_: true - From b46d735878e0df666270f4d8f51650d16f42bfe6 Mon Sep 17 00:00:00 2001 From: Julianvnoortwijk <57176112+Julianvnoortwijk@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:33:08 +0200 Subject: [PATCH 3/7] Update tiger_inference_flat.yaml --- configs/experiment/tiger_inference_flat.yaml | 32 +++++++------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/configs/experiment/tiger_inference_flat.yaml b/configs/experiment/tiger_inference_flat.yaml index d1924e0..c5c42b9 100644 --- a/configs/experiment/tiger_inference_flat.yaml +++ b/configs/experiment/tiger_inference_flat.yaml @@ -8,9 +8,9 @@ sequence_length: 120 model: huggingface_model: - _target_: transformers.T5EncoderModel + _target_: transformers.models.t5.modeling_t5.T5Stack config: - _target_: transformers.T5Config + _target_: transformers.models.t5.configuration_t5.T5Config vocab_size: 256 d_model: 128 num_heads: 6 @@ -18,7 +18,13 @@ model: d_ff: 1024 d_kv: 64 num_layers: 4 - _target_: src.models.modules.semantic_id.tiger_generation_model.SemanticIDEncoderDecoder + is_decoder: true + is_encoder_decoder: false + embed_tokens: + _target_: torch.nn.Embedding + num_embeddings: ${model.huggingface_model.config.vocab_size} + embedding_dim: ${model.huggingface_model.config.d_model} + _target_: src.models.modules.semantic_id.tiger_generation_model.SemanticIDDecoderOnly feature_to_model_input_map: sequence_data: input_ids user_id: user_id @@ -30,27 +36,12 @@ model: evaluator: null weight_tying: true compile: false - decoder: - _target_: transformers.models.t5.modeling_t5.T5Stack - config: - _target_: transformers.models.t5.configuration_t5.T5Config - vocab_size: ${model.huggingface_model.config.vocab_size} - d_model: ${model.huggingface_model.config.d_model} - num_heads: ${model.huggingface_model.config.num_heads} - dropout_rate: 0.15 - d_ff: ${model.huggingface_model.config.d_ff} - d_kv: ${model.huggingface_model.config.d_kv} - num_layers: 4 - is_decoder: true - is_encoder_decoder: false - embed_tokens: - _target_: torch.nn.Embedding - num_embeddings: ${model.huggingface_model.config.vocab_size} - embedding_dim: ${model.huggingface_model.config.d_model} + decoder: null num_hierarchies: ${num_hierarchies} num_user_bins: null codebooks: ${data_loading.predict_dataloader_config.dataloader.dataset_config.semantic_id_map.sequence_data} mlp_layers: 2 + loss_on_all_positions: true top_k_for_generation: 10 task_name: inference id: ${now:%Y-%m-%d}/${now:%H-%M-%S} @@ -218,4 +209,3 @@ extras: enforce_tags: true print_config_warnings: true print_config: true - From af0a67b523a768a59547906a7f7a7590f33c9ee1 Mon Sep 17 00:00:00 2001 From: Lucas Groot Date: Wed, 24 Jun 2026 13:38:56 +0200 Subject: [PATCH 4/7] Add decoder only inference job --- jobs/decoder_only_inference.job | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 jobs/decoder_only_inference.job diff --git a/jobs/decoder_only_inference.job b/jobs/decoder_only_inference.job new file mode 100644 index 0000000..07dfd2e --- /dev/null +++ b/jobs/decoder_only_inference.job @@ -0,0 +1,46 @@ +#!/bin/bash +#SBATCH --job-name=test_dec_only +#SBATCH --output=logs/test_dec_only_%j.out +#SBATCH --error=logs/test_dec_only_%j.err +#SBATCH --partition=gpu_mig +#SBATCH --gpus=1 +#SBATCH --time=00:15:00 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 + +module purge +module load 2023 +module load Anaconda3/2023.07-2 +module load CUDA/12.4.0 + +source $(conda info --base)/etc/profile.d/conda.sh +conda activate grid + +# Usage: First dataset (beauty, toys, sports), then +# model: (grid_decoder_only_all grid_decoder_only_all_noaug grid_decoder_only_last grid_decoder_only_last_noaug) + +DATASET=$1 +MODEL=$2 + +export HYDRA_FULL_ERROR=1 + +export OMP_NUM_THREADS=8 + +cd $HOME/GRID + +BASE=/projects/prjs2120/groups/group_08 + +# We can change to grid_decoder_only_last (best performing loss last), grid_decoder_only_all_noaug (best performing loss all) +CKPT=$(ls /projects/prjs2120/groups/group_08/results/decoder_only/${MODEL}/${DATASET}/checkpoints/checkpoint_epoch=*.ckpt | head -1) + +mkdir -p $HOME/GRID/${MODEL}/${DATASET}/outputs + +python -m src.inference experiment=tiger_inference_flat \ + data_dir=$BASE/data/amazon_data/$DATASET \ + semantic_id_path=$BASE/results/sid_rkmeans/${DATASET}/rkmeans_inference/pickle/merged_predictions_tensor.pt \ + ckpt_path="'$CKPT'" \ + num_hierarchies=4 \ + hydra.run.dir=$HOME/GRID/${MODEL}/${DATASET} \ + paths.output_dir=$HOME/GRID/${MODEL}/${DATASET}/outputs \ + ++should_skip_retry=True \ No newline at end of file From 8b425daec6d8687406fd8a0bbdec36af2ae1e184 Mon Sep 17 00:00:00 2001 From: Julianvnoortwijk <57176112+Julianvnoortwijk@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:22:04 +0200 Subject: [PATCH 5/7] Delete README.md --- README.md | 154 ------------------------------------------------------ 1 file changed, 154 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 9392934..0000000 --- a/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# Generative Recommendation with Semantic IDs (GRID) -[![PyTorch](https://img.shields.io/badge/pytorch-2.0%2B-red)](https://pytorch.org/) -[![Hydra](https://img.shields.io/badge/config-hydra-89b8cd)](https://hydra.cc/) -[![Lightning](https://img.shields.io/badge/pytorch-lightning-792ee5)](https://lightning.ai/) -[![arXiv](https://img.shields.io/badge/arXiv-2507.22224-b31b1b.svg)](https://arxiv.org/abs/2507.22224) - - -**GRID** (Generative Recommendation with Semantic IDs) is a state-of-the-art framework for generative recommendation systems using semantic IDs, developed by a group of scientists and engineers from [Snap Research](https://research.snap.com/team/user-modeling-and-personalization.html). This project implements novel approaches for learning semantic IDs from text embedding and generating recommendations through transformer-based generative models. - -## 🚀 Overview - -GRID facilitates generative recommendation three overarching steps: - -- **Embedding Generation with LLMs**: Converting item text into embeddings using any LLMs available on Huggingface. -- **Semantic ID Learning**: Converting item embedding into hierarchical semantic IDs using Residual Quantization techniques such as RQ-KMeans, RQ-VAE, RVQ. -- **Generative Recommendations**: Using transformer architectures to generate recommendation sequences as semantic ID tokens. - - -## 📦 Installation - -### Prerequisites -- Python 3.10+ -- CUDA-compatible GPU (recommended) - -### Setup Environment - -```bash -# Clone the repository -git clone https://github.com/snap-research/GRID.git -cd GRID - -# Install dependencies -pip install -r requirements.txt -``` - -## 🎯 Quick Start - -### 1. Data Preparation - -Prepare your dataset in the expected format: -``` -data/ -├── train/ # training sequence of user history -├── validation/ # validation sequence of user history -├── test/ # testing sequence of user history -└── items/ # text of all items in the dataset -``` - -We provide pre-processed Amazon data explored in the [P5 paper](https://arxiv.org/abs/2203.13366) [4]. The data can be downloaded from this [google drive link](https://drive.google.com/file/d/1B5_q_MT3GYxmHLrMK0-lAqgpbAuikKEz/view?usp=sharing). - -### 2. Embedding Generation with LLMs - -Generate embeddings from LLMs, which later will be transformed into semantic IDs. - -```bash -python -m src.inference experiment=sem_embeds_inference_flat data_dir=data/amazon_data/beauty # avaiable data includes 'beauty', 'sports', and 'toys' -``` - -### 3. Train and Generate Semantic IDs - -Learn semantic ID centroids for embeddings generated in step 2: - -```bash -python -m src.train experiment=rkmeans_train_flat \ - data_dir=data/amazon_data/beauty \ - embedding_path=/merged_predictions_tensor.pt \ # this can be found in the log dirs in step2 - embedding_dim=2048 \ # the model dimension of the LLMs you use in step 2. 2048 for flan-t5-xl as used in this example. - num_hierarchies=3 \ # we train 3 codebooks - codebook_width=256 \ # each codebook has 256 rows of centroids -``` - -Generate SIDs: - -```bash -python -m src.inference experiment=rkmeans_inference_flat \ - data_dir=data/amazon_data/beauty \ - embedding_path=/merged_predictions_tensor.pt \ - embedding_dim=2048 \ - num_hierarchies=3 \ - codebook_width=256 \ - ckpt_path= # this can be found in the log dir for training SIDs -``` - - -### 4. Train Generative Recommendation Model with Semantic IDs - -Train the recommendation model using the learned semantic IDs: - -```bash -python -m src.train experiment=tiger_train_flat \ - data_dir=data/amazon_data/beauty \ - semantic_id_path=/pickle/merged_predictions_tensor.pt \ - num_hierarchies=4 # Please note that we add 1 for num_hierarchies because in the previous step we appended one additional digit to de-duplicate the semantic IDs we generate. -``` - -### 4. Generate Recommendations - -Run inference to generate recommendations: - -```bash -python -m src.inference experiment=tiger_inference_flat \ - data_dir=data/amazon_data/beauty \ - semantic_id_path=/pickle/merged_predictions_tensor.pt \ - ckpt_path= \ # this can be found in the log dir for training GR models - num_hierarchies=4 \ # Please note that we add 1 for num_hierarchies because in the previous step we appended one additional digit to de-duplicate the semantic IDs we generate. -``` - -## Supported Models: - -### Semantic ID: - -1. Residual K-means proposed in One-Rec [2] -2. Residual Vector Quantization -3. Residual Quantization with Variational Autoencoder [3] - -### Generative Recommendation: - -1. TIGER [1] - -## 📚 Citation - -If you use GRID in your research, please cite: - -```bibtex -@inproceedings{grid, - title = {Generative Recommendation with Semantic IDs: A Practitioner's Handbook}, - author = {Ju, Clark Mingxuan and Collins, Liam and Neves, Leonardo and Kumar, Bhuvesh and Wang, Louis Yufeng and Zhao, Tong and Shah, Neil}, - booktitle = {Proceedings of the 34th ACM International Conference on Information and Knowledge Management (CIKM)}, - year = {2025} -} -``` - -## 🤝 Acknowledgments - -- Built with [PyTorch](https://pytorch.org/) and [PyTorch Lightning](https://lightning.ai/) -- Configuration management by [Hydra](https://hydra.cc/) -- Inspired by recent advances in generative AI and recommendation systems -- Part of this repo is built on top of https://github.com/ashleve/lightning-hydra-template - -## 📞 Contact - -For questions and support: -- Create an issue on GitHub -- Contact the development team: Clark Mingxuan Ju (mju@snap.com), Liam Collins (lcollins2@snap.com), Bhuvesh Kumar (bhuvesh@snap.com) and Leonardo Neves (lneves@snap.com). - -## Bibliography - -[1] Rajput, Shashank, et al. "Recommender systems with generative retrieval." Advances in Neural Information Processing Systems 36 (2023): 10299-10315. - -[2] Deng, Jiaxin, et al. "Onerec: Unifying retrieve and rank with generative recommender and iterative preference alignment." arXiv preprint arXiv:2502.18965 (2025). - -[3] Lee, Doyup, et al. "Autoregressive image generation using residual quantization." Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2022. - -[4] Geng, Shijie, et al. "Recommendation as language processing (rlp): A unified pretrain, personalized prompt & predict paradigm (p5)." Proceedings of the 16th ACM conference on recommender systems. 2022. From 8d3c2052db4a5605adaa82c3c71fcd9bf850bccc Mon Sep 17 00:00:00 2001 From: Julianvnoortwijk <57176112+Julianvnoortwijk@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:22:35 +0200 Subject: [PATCH 6/7] Updated readme --- README.md | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b898cfb --- /dev/null +++ b/README.md @@ -0,0 +1,194 @@ +# Generative Recommendation with Semantic IDs (GRID) +[![PyTorch](https://img.shields.io/badge/pytorch-2.0%2B-red)](https://pytorch.org/) +[![Hydra](https://img.shields.io/badge/config-hydra-89b8cd)](https://hydra.cc/) +[![Lightning](https://img.shields.io/badge/pytorch-lightning-792ee5)](https://lightning.ai/) +[![arXiv](https://img.shields.io/badge/arXiv-2507.22224-b31b1b.svg)](https://arxiv.org/abs/2507.22224) + + +**GRID** (Generative Recommendation with Semantic IDs) is a state-of-the-art framework for generative recommendation systems using semantic IDs, developed by a group of scientists and engineers from [Snap Research](https://research.snap.com/team/user-modeling-and-personalization.html). This project implements novel approaches for learning semantic IDs from text embedding and generating recommendations through transformer-based generative models. + +## 🚀 Overview + +GRID facilitates generative recommendation three overarching steps: + +- **Embedding Generation with LLMs**: Converting item text into embeddings using any LLMs available on Huggingface. +- **Semantic ID Learning**: Converting item embedding into hierarchical semantic IDs using Residual Quantization techniques such as RQ-KMeans, RQ-VAE, RVQ. +- **Generative Recommendations**: Using transformer architectures to generate recommendation sequences as semantic ID tokens. + + +## 📦 Installation + +### Prerequisites +- Python 3.10+ +- CUDA-compatible GPU (recommended) + +### Setup Environment + +```bash +# Clone the repository +git clone https://github.com/snap-research/GRID.git +cd GRID + +# Install dependencies +pip install -r requirements.txt +``` + +## 🎯 Quick Start + +### 1. Data Preparation + +Prepare your dataset in the expected format: +``` +data/ +├── train/ # training sequence of user history +├── validation/ # validation sequence of user history +├── test/ # testing sequence of user history +└── items/ # text of all items in the dataset +``` + +We provide pre-processed Amazon data explored in the [P5 paper](https://arxiv.org/abs/2203.13366) [4]. The data can be downloaded from this [google drive link](https://drive.google.com/file/d/1B5_q_MT3GYxmHLrMK0-lAqgpbAuikKEz/view?usp=sharing). + +### 2. Embedding Generation with LLMs + +Generate embeddings from LLMs, which later will be transformed into semantic IDs. + +```bash +python -m src.inference experiment=sem_embeds_inference_flat data_dir=data/amazon_data/beauty # avaiable data includes 'beauty', 'sports', and 'toys' +``` + +### 3. Train and Generate Semantic IDs + +Learn semantic ID centroids for embeddings generated in step 2: + +```bash +python -m src.train experiment=rkmeans_train_flat \ + data_dir=data/amazon_data/beauty \ + embedding_path=/merged_predictions_tensor.pt \ # this can be found in the log dirs in step2 + embedding_dim=2048 \ # the model dimension of the LLMs you use in step 2. 2048 for flan-t5-xl as used in this example. + num_hierarchies=3 \ # we train 3 codebooks + codebook_width=256 \ # each codebook has 256 rows of centroids +``` + +Generate SIDs: + +```bash +python -m src.inference experiment=rkmeans_inference_flat \ + data_dir=data/amazon_data/beauty \ + embedding_path=/merged_predictions_tensor.pt \ + embedding_dim=2048 \ + num_hierarchies=3 \ + codebook_width=256 \ + ckpt_path= # this can be found in the log dir for training SIDs +``` + + +### 4. Train Generative Recommendation Model with Semantic IDs + +Train the recommendation model using the learned semantic IDs: + +```bash +python -m src.train experiment=tiger_train_flat \ + data_dir=data/amazon_data/beauty \ + semantic_id_path=/pickle/merged_predictions_tensor.pt \ + num_hierarchies=4 # Please note that we add 1 for num_hierarchies because in the previous step we appended one additional digit to de-duplicate the semantic IDs we generate. +``` + +### 4. Generate Recommendations + +Run inference to generate recommendations: + +```bash +python -m src.inference experiment=tiger_inference_flat \ + data_dir=data/amazon_data/beauty \ + semantic_id_path=/pickle/merged_predictions_tensor.pt \ + ckpt_path= \ # this can be found in the log dir for training GR models + num_hierarchies=4 \ # Please note that we add 1 for num_hierarchies because in the previous step we appended one additional digit to de-duplicate the semantic IDs we generate. +``` + +### Decoder-Only Model (Extension) + +In addition to the default encoder-decoder (T5-style) recommender, this repo +provides a **decoder-only (GPT-style)** generative recommender, +`SemanticIDDecoderOnly`. It is enabled entirely through the config (no code +changes are needed) by selecting the decoder-only experiment instead of the +default `tiger_*_flat` one: + +```bash +# Train the decoder-only model +python -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=data/amazon_data/beauty \ + semantic_id_path=/pickle/merged_predictions_tensor.pt \ + num_hierarchies=4 \ + model.loss_on_all_positions=false # see loss toggle below +``` + +```bash +# Generate recommendations with the decoder-only model +python -m src.inference experiment=tiger_decoder_only_inference_flat \ + data_dir=data/amazon_data/beauty \ + semantic_id_path=/pickle/merged_predictions_tensor.pt \ + ckpt_path= \ + num_hierarchies=4 +``` + +**Loss objective toggle.** The decoder-only model exposes a next-token +prediction at every position, so it supports two training objectives via the +`model.loss_on_all_positions` flag (default `true` in the experiment config): + +- `model.loss_on_all_positions=false`: apply the loss only on the final target + item (**loss-last**), matching the encoder-decoder TIGER objective. +- `model.loss_on_all_positions=true`: apply the full causal next-token loss on + **all positions** (**loss-all**). + +All other knobs (Semantic IDs, model dimensions, optimizer, beam search) are +shared with the encoder-decoder baseline, so switching the `experiment` value is +sufficient to compare architectures under identical settings. + +## Supported Models: + +### Semantic ID: + +1. Residual K-means proposed in One-Rec [2] +2. Residual Vector Quantization +3. Residual Quantization with Variational Autoencoder [3] + +### Generative Recommendation: + +1. TIGER [1] (encoder-decoder) +2. Decoder-only (GPT-style) variant with selectable loss-last / loss-all objective + +## 📚 Citation + +If you use GRID in your research, please cite: + +```bibtex +@inproceedings{grid, + title = {Generative Recommendation with Semantic IDs: A Practitioner's Handbook}, + author = {Ju, Clark Mingxuan and Collins, Liam and Neves, Leonardo and Kumar, Bhuvesh and Wang, Louis Yufeng and Zhao, Tong and Shah, Neil}, + booktitle = {Proceedings of the 34th ACM International Conference on Information and Knowledge Management (CIKM)}, + year = {2025} +} +``` + +## 🤝 Acknowledgments + +- Built with [PyTorch](https://pytorch.org/) and [PyTorch Lightning](https://lightning.ai/) +- Configuration management by [Hydra](https://hydra.cc/) +- Inspired by recent advances in generative AI and recommendation systems +- Part of this repo is built on top of https://github.com/ashleve/lightning-hydra-template + +## 📞 Contact + +For questions and support: +- Create an issue on GitHub +- Contact the development team: Clark Mingxuan Ju (mju@snap.com), Liam Collins (lcollins2@snap.com), Bhuvesh Kumar (bhuvesh@snap.com) and Leonardo Neves (lneves@snap.com). + +## Bibliography + +[1] Rajput, Shashank, et al. "Recommender systems with generative retrieval." Advances in Neural Information Processing Systems 36 (2023): 10299-10315. + +[2] Deng, Jiaxin, et al. "Onerec: Unifying retrieve and rank with generative recommender and iterative preference alignment." arXiv preprint arXiv:2502.18965 (2025). + +[3] Lee, Doyup, et al. "Autoregressive image generation using residual quantization." Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2022. + +[4] Geng, Shijie, et al. "Recommendation as language processing (rlp): A unified pretrain, personalized prompt & predict paradigm (p5)." Proceedings of the 16th ACM conference on recommender systems. 2022. From cadab05955eb9a7d2fcc72a22397c50c1432d3b7 Mon Sep 17 00:00:00 2001 From: Julianvnoortwijk <57176112+Julianvnoortwijk@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:21:07 +0200 Subject: [PATCH 7/7] job files --- jobs/run_dec_1_beauty.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_1_beauty_noaug.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_1_sports.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_1_sports_noaug.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_1_toys.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_1_toys_noaug.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_2_beauty.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_2_beauty_noaug.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_2_sports.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_2_sports_noaug.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_2_toys.job | 48 +++++++++++++++++++++++++++++++++ jobs/run_dec_2_toys_noaug.job | 48 +++++++++++++++++++++++++++++++++ 12 files changed, 576 insertions(+) create mode 100644 jobs/run_dec_1_beauty.job create mode 100644 jobs/run_dec_1_beauty_noaug.job create mode 100644 jobs/run_dec_1_sports.job create mode 100644 jobs/run_dec_1_sports_noaug.job create mode 100644 jobs/run_dec_1_toys.job create mode 100644 jobs/run_dec_1_toys_noaug.job create mode 100644 jobs/run_dec_2_beauty.job create mode 100644 jobs/run_dec_2_beauty_noaug.job create mode 100644 jobs/run_dec_2_sports.job create mode 100644 jobs/run_dec_2_sports_noaug.job create mode 100644 jobs/run_dec_2_toys.job create mode 100644 jobs/run_dec_2_toys_noaug.job diff --git a/jobs/run_dec_1_beauty.job b/jobs/run_dec_1_beauty.job new file mode 100644 index 0000000..c3c4b66 --- /dev/null +++ b/jobs/run_dec_1_beauty.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/beauty +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/beauty/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_last/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=false \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_1_beauty_noaug.job b/jobs/run_dec_1_beauty_noaug.job new file mode 100644 index 0000000..befeb6d --- /dev/null +++ b/jobs/run_dec_1_beauty_noaug.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last_beauty_noaug +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/beauty +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/beauty/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_last_noaug/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_no_aug \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=false \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_1_sports.job b/jobs/run_dec_1_sports.job new file mode 100644 index 0000000..a3f0fb8 --- /dev/null +++ b/jobs/run_dec_1_sports.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last_sports +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/sports +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/sports/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_last/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=false \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_1_sports_noaug.job b/jobs/run_dec_1_sports_noaug.job new file mode 100644 index 0000000..cdadb31 --- /dev/null +++ b/jobs/run_dec_1_sports_noaug.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last_sports_noaug +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/sports +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/sports/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_last_noaug/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_no_aug \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=false \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_1_toys.job b/jobs/run_dec_1_toys.job new file mode 100644 index 0000000..f16e10e --- /dev/null +++ b/jobs/run_dec_1_toys.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last_toys +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/toys +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/toys/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_last/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=false \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_1_toys_noaug.job b/jobs/run_dec_1_toys_noaug.job new file mode 100644 index 0000000..b500cbb --- /dev/null +++ b/jobs/run_dec_1_toys_noaug.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last_toys_noaug +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/toys +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/toys/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_last_noaug/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_no_aug \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=false \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_2_beauty.job b/jobs/run_dec_2_beauty.job new file mode 100644 index 0000000..649eb42 --- /dev/null +++ b/jobs/run_dec_2_beauty.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_last +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/beauty +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/beauty/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_all/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=true \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_2_beauty_noaug.job b/jobs/run_dec_2_beauty_noaug.job new file mode 100644 index 0000000..9ea25f7 --- /dev/null +++ b/jobs/run_dec_2_beauty_noaug.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_all_beauty_noaug +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/beauty +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/beauty/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_all_noaug/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_no_aug \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=true \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_2_sports.job b/jobs/run_dec_2_sports.job new file mode 100644 index 0000000..2c58dc6 --- /dev/null +++ b/jobs/run_dec_2_sports.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_all_sports +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/sports +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/sports/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_all/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=true \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_2_sports_noaug.job b/jobs/run_dec_2_sports_noaug.job new file mode 100644 index 0000000..dc8a5cb --- /dev/null +++ b/jobs/run_dec_2_sports_noaug.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_all_sports_noaug +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/sports +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/sports/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_all_noaug/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_no_aug \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=true \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_2_toys.job b/jobs/run_dec_2_toys.job new file mode 100644 index 0000000..8973d27 --- /dev/null +++ b/jobs/run_dec_2_toys.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_all_toys +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/toys +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/toys/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_all/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_flat \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=true \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file diff --git a/jobs/run_dec_2_toys_noaug.job b/jobs/run_dec_2_toys_noaug.job new file mode 100644 index 0000000..bbbcdf4 --- /dev/null +++ b/jobs/run_dec_2_toys_noaug.job @@ -0,0 +1,48 @@ +#!/bin/bash +#SBATCH --job-name=grid_dec_all_toys_noaug +#SBATCH --partition=gpu_h100 +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=120G +#SBATCH --time=04:00:00 +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err + +set -e + +cd $HOME/code +mkdir -p logs +touch .project-root + +module purge +module load 2025 +module load Anaconda3/2025.06-1 + +PY=$HOME/.conda/envs/RecSys/bin/python + +echo "Job ID: $SLURM_JOB_ID" +echo "Running on: $SLURMD_NODENAME" +nvidia-smi +echo "--- env check ---" +$PY -c "import sys; print('python:', sys.executable)" +$PY -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())" +echo "-----------------" +date + +DATA_DIR=/projects/prjs2120/groups/group_08/data/amazon_data/toys +SID_PATH=/projects/prjs2120/groups/group_08/results/sid_rkmeans/toys/rkmeans_inference/pickle/merged_predictions_tensor.pt +NUM_HIER=4 +OUTDIR=/scratch-shared/$USER/grid_decoder_only_all_noaug/$SLURM_JOB_ID + +$PY -m src.train experiment=tiger_decoder_only_train_no_aug \ + data_dir=$DATA_DIR \ + semantic_id_path=$SID_PATH \ + num_hierarchies=$NUM_HIER \ + model.loss_on_all_positions=true \ + callbacks.model_checkpoint.save_last=true \ + hydra.run.dir=$OUTDIR \ + trainer.devices=1 \ + trainer.strategy=auto + +date +echo "Done." \ No newline at end of file