Repository: pykeen/pykeen
Issue: AdamW 'decoupled_weight_decay' issue
Root cause: Interface Contract Mismatch

### Describe the bug

The AdamW used in pykeen is using a decoupled_weight_decay keyword argument but the PyTorch AdamW is expecting a weight_decay keyword. Is this something that is solvable currently in the framework?  I tried defining explicitly but this still led to the same issue 

```python
optimizer_kwargs=dict(
    weight_decay=0.0001
)
```

### How to reproduce
```python
# !pip install pykeen
from pykeen.pipeline import pipeline
from pykeen.datasets import nations
from torch.optim import AdamW

pipeline = pipeline(
    dataset="nations",
    model="TransE",
    device='cpu',
    optimizer=AdamW,
)
```

```python-traceback
Error:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 pipeline = pipeline(
      2     dataset="nations",
      3     model="TransE",
      4     device='cpu',
      5     optimizer=AdamW,
      6 )

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/pipeline/api.py:1540, in pipeline(dataset, dataset_kwargs, training, testing, validation, evaluation_entity_whitelist, evaluation_relation_whitelist, model, model_kwargs, interaction, interaction_kwargs, dimensions, loss, loss_kwargs, regularizer, regularizer_kwargs, optimizer, optimizer_kwargs, clear_optimizer, lr_scheduler, lr_scheduler_kwargs, training_loop, training_loop_kwargs, negative_sampler, negative_sampler_kwargs, epochs, training_kwargs, stopper, stopper_kwargs, evaluator, evaluator_kwargs, evaluation_kwargs, result_tracker, result_tracker_kwargs, metadata, device, random_seed, use_testing_data, evaluation_fallback, filter_validation_when_testing, use_tqdm)
   1519 training_loop_instance = _handle_training_loop(
   1520     _result_tracker=_result_tracker,
   1521     model_instance=model_instance,
   (...)   1530     negative_sampler_kwargs=negative_sampler_kwargs,
   1531 )
   1533 evaluator_instance, evaluation_kwargs = _handle_evaluator(
   1534     _result_tracker=_result_tracker,
   1535     evaluator=evaluator,
   1536     evaluator_kwargs=evaluator_kwargs,
   1537     evaluation_kwargs=evaluation_kwargs,
   1538 )
-> 1540 stopper_instance, configuration, losses, train_seconds = _handle_training(
   1541     _result_tracker=_result_tracker,
   1542     training=training,
   1543     validation=validation,
   1544     model_instance=model_instance,
   1545     evaluator_instance=evaluator_instance,
   1546     training_loop_instance=training_loop_instance,
   1547     clear_optimizer=clear_optimizer,
   1548     evaluation_kwargs=evaluation_kwargs,
   1549     epochs=epochs,
   1550     training_kwargs=training_kwargs,
   1551     stopper=stopper,
   1552     stopper_kwargs=stopper_kwargs,
   1553     use_tqdm=use_tqdm,
   1554 )
   1556 metric_results, evaluate_seconds = _handle_evaluation(
   1557     _result_tracker=_result_tracker,
   1558     model_instance=model_instance,
   (...)   1569     use_tqdm=use_tqdm,
   1570 )
   1571 _result_tracker.end_run()

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/pipeline/api.py:1181, in _handle_training(_result_tracker, training, validation, model_instance, evaluator_instance, training_loop_instance, clear_optimizer, evaluation_kwargs, epochs, training_kwargs, stopper, stopper_kwargs, use_tqdm)
   1179 # Train like Cristiano Ronaldo
   1180 training_start_time = time.time()
-> 1181 losses = training_loop_instance.train(
   1182     triples_factory=training,
   1183     stopper=stopper_instance,
   1184     clear_optimizer=clear_optimizer,
   1185     **training_kwargs,
   1186 )
   1187 assert losses is not None  # losses is only none if it's doing search mode
   1188 training_end_time = time.time() - training_start_time

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/training/training_loop.py:383, in TrainingLoop.train(self, triples_factory, num_epochs, batch_size, slice_size, label_smoothing, sampler, continue_training, only_size_probing, use_tqdm, use_tqdm_batch, tqdm_kwargs, stopper, sub_batch_size, num_workers, clear_optimizer, checkpoint_directory, checkpoint_name, checkpoint_frequency, checkpoint_on_failure, drop_last, callbacks, callbacks_kwargs, gradient_clipping_max_norm, gradient_clipping_norm_type, gradient_clipping_max_abs_value, pin_memory)
    380             temporary_directory = exit_stack.enter_context(TemporaryDirectory())
    381             best_epoch_model_file_path = pathlib.Path(temporary_directory).joinpath("best_model.pt")
--> 383         result = self._train(
    384             num_epochs=num_epochs,
    385             batch_size=batch_size,
    386             slice_size=slice_size,
    387             label_smoothing=label_smoothing,
    388             sampler=sampler,
    389             continue_training=continue_training,
    390             only_size_probing=only_size_probing,
    391             use_tqdm=use_tqdm,
    392             use_tqdm_batch=use_tqdm_batch,
    393             tqdm_kwargs=tqdm_kwargs,
    394             stopper=stopper,
    395             sub_batch_size=sub_batch_size,
    396             num_workers=num_workers,
    397             save_checkpoints=save_checkpoints,
    398             checkpoint_path=checkpoint_path,
    399             checkpoint_frequency=checkpoint_frequency,
    400             checkpoint_on_failure_file_path=checkpoint_on_failure_file_path,
    401             best_epoch_model_file_path=best_epoch_model_file_path,
    402             last_best_epoch=last_best_epoch,
    403             drop_last=drop_last,
    404             callbacks=callbacks,
    405             callbacks_kwargs=callbacks_kwargs,
    406             gradient_clipping_max_norm=gradient_clipping_max_norm,
    407             gradient_clipping_norm_type=gradient_clipping_norm_type,
    408             gradient_clipping_max_abs_value=gradient_clipping_max_abs_value,
    409             triples_factory=triples_factory,
    410             pin_memory=pin_memory,
    411         )
    413 # Ensure the release of memory
    414 torch.cuda.empty_cache()

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/training/training_loop.py:589, in TrainingLoop._train(self, triples_factory, num_epochs, batch_size, slice_size, label_smoothing, sampler, continue_training, only_size_probing, use_tqdm, use_tqdm_batch, tqdm_kwargs, stopper, sub_batch_size, num_workers, save_checkpoints, checkpoint_path, checkpoint_frequency, checkpoint_on_failure_file_path, best_epoch_model_file_path, last_best_epoch, drop_last, callbacks, callbacks_kwargs, gradient_clipping_max_norm, gradient_clipping_norm_type, gradient_clipping_max_abs_value, pin_memory)
    581 # This will find necessary parameters to optimize the use of the hardware at hand
    582 if (
    583     not only_size_probing
    584     and self.automatic_memory_optimization
   (...)    587 ):
    588     # return the relevant parameters slice_size and batch_size
--> 589     sub_batch_size, slice_size = self.sub_batch_and_slice(
    590         batch_size=batch_size, sampler=sampler, triples_factory=triples_factory
    591     )
    593 if sub_batch_size is None or sub_batch_size == batch_size:  # by default do not split batches in sub-batches
    594     sub_batch_size = batch_size

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/training/training_loop.py:962, in TrainingLoop.sub_batch_and_slice(self, batch_size, sampler, triples_factory)
    954 def sub_batch_and_slice(
    955     self,
    956     *,
   (...)    959     triples_factory: CoreTriplesFactory,
    960 ) -> tuple[int, int | None]:
    961     """Check if sub-batching and/or slicing is necessary to train the model on the hardware at hand."""
--> 962     sub_batch_size, finished_search, supports_sub_batching = self._sub_batch_size_search(
    963         batch_size=batch_size,
    964         sampler=sampler,
    965         triples_factory=triples_factory,
    966     )
    967     # If the sub_batch_size did not finish search with a possibility that fits the hardware, we have to try slicing
    968     if finished_search:

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/training/training_loop.py:1036, in TrainingLoop._sub_batch_size_search(self, batch_size, sampler, triples_factory)
   1034     self._free_graph_and_cache()
   1035     logger.debug(f"Trying {batch_size=:_} for training now.")
-> 1036     self._train(
   1037         triples_factory=triples_factory,
   1038         num_epochs=1,
   1039         batch_size=batch_size,
   1040         sub_batch_size=sub_batch_size,
   1041         sampler=sampler,
   1042         only_size_probing=True,
   1043     )
   1044 except RuntimeError as runtime_error:
   1045     self._free_graph_and_cache()

File ~/Documents/Documents - Sean’s MacBook Pro/ML6/Bitbucket/venv3/lib/python3.12/site-packages/pykeen/training/training_loop.py:614, in TrainingLoop._train(self, triples_factory, num_epochs, batch_size, slice_size, label_smoothing, sampler, continue_training, only_size_probing, use_tqdm, use_tqdm_batch, tqdm_kwargs, stopper, sub_batch_size, num_workers, save_checkpoints, checkpoint_path, checkpoint_frequency, checkpoint_on_failure_file_path, best_epoch_model_file_path, last_best_epoch, drop_last, callbacks, callbacks_kwargs, gradient_clipping_max_norm, gradient_clipping_norm_type, gradient_clipping_max_abs_value, pin_memory)
    612 # Create new optimizer
    613 optimizer_kwargs = _get_optimizer_kwargs(self.optimizer)
--> 614 self.optimizer = self.optimizer.__class__(
    615     params=self.model.get_grad_params(),
    616     **optimizer_kwargs,
    617 )
    619 if self.lr_scheduler is not None:
    620     # Create a new lr scheduler and add the optimizer
    621     lr_scheduler_kwargs = _get_lr_scheduler_kwargs(self.lr_scheduler)

TypeError: AdamW.__init__() got an unexpected keyword argument 'decoupled_weight_decay'
```

### Environment

| Key             | Value                    |
|-----------------|--------------------------|
| OS              | posix                    |
| Platform        | Darwin                   |
| Release         | 24.6.0                   |
| Time            | Wed Dec  3 15:06:56 2025 |
| Python          | 3.12.11                  |
| PyKEEN          | 1.11.1                   |
| PyKEEN Hash     | a7cb19dc                 |
| PyKEEN Branch   | feat/link_prediction     |
| PyTorch         | 2.9.1                    |
| CUDA Available? | false                    |
| CUDA Version    | N/A                      |
| cuDNN Version   | N/A                      |

### Additional information

_No response_

### Issue Template Checks

- [x] This is not a feature request (use a different issue template if it is)
- [x] This is not a question (use the [discussions](https://github.com/pykeen/pykeen/discussions/new) forum instead)
- [x] I've read the text explaining why including environment information is important and understand if I omit this information that my issue will be dismissed