ykumards: > @ykumards Please, don't forget to apply black on sources :) I can't commit in your repo to do it.
> 
> LGTM :)

Oh, we had a hook for that right?
sdesrozis: > > @ykumards Please, don't forget to apply black on sources :) I can't commit in your repo to do it.
> > LGTM :)
> 
> Oh, we had a hook for that right?

Oh maybe during merge because it missed something here. Btw, that's not really important and it was by pure chance that I saw that :)
ykumards: > > > @ykumards Please, don't forget to apply black on sources :) I can't commit in your repo to do it.
> > > LGTM :)
> > 
> > 
> > Oh, we had a hook for that right?
> 
> Oh maybe during merge because it missed something here. Btw, that's not really important and it was by pure chance that I saw that :)

@sdesrozis You were right! Black formatting was off, thanks pointing it out :)
diff --git a/ignite/contrib/handlers/param_scheduler.py b/ignite/contrib/handlers/param_scheduler.py
index a942c1c6..9f929939 100644
--- a/ignite/contrib/handlers/param_scheduler.py
+++ b/ignite/contrib/handlers/param_scheduler.py
@@ -660,8 +660,8 @@ class LRScheduler(ParamScheduler):
 def create_lr_scheduler_with_warmup(
     lr_scheduler,
     warmup_start_value,
-    warmup_end_value,
     warmup_duration,
+    warmup_end_value=None,
     save_history=False,
     output_simulated_values=None,
 ):
@@ -672,8 +672,9 @@ def create_lr_scheduler_with_warmup(
         lr_scheduler (ParamScheduler or subclass of `torch.optim.lr_scheduler._LRScheduler`): learning rate scheduler
             after the warm-up.
         warmup_start_value (float): learning rate start value of the warm-up phase.
-        warmup_end_value (float): learning rate end value of the warm-up phase.
         warmup_duration (int): warm-up phase duration, number of events.
+        warmup_end_value (float): learning rate end value of the warm-up phase, (default=None). If None,
+             warmup_end_value is set to optimizer initial lr.
         save_history (bool, optional): whether to log the parameter values to
             `engine.state.param_history`, (default=False).
         output_simulated_values (list, optional): optional output of simulated learning rate values.
@@ -716,6 +717,9 @@ def create_lr_scheduler_with_warmup(
     if not (isinstance(warmup_duration, numbers.Integral) and warmup_duration > 1):
         raise ValueError("Argument warmup_duration should be at least 2 events, but given {}".format(warmup_duration))
 
+    if warmup_end_value is None:
+        warmup_end_value = lr_scheduler.optimizer.param_groups[0]["lr"]
+
     milestones_values = [(0, warmup_start_value), (warmup_duration - 1, warmup_end_value)]
 
     if isinstance(lr_scheduler, _LRScheduler):

Make optional warmup_end_value in create_lr_scheduler_with_warmup
## 🚀 Feature

It would be interesting to setup a lr scheduler without specifying `warmup_end_value` which can be deduced from provided optimizer:
```python
import torch
from torch.optim.lr_scheduler import ExponentialLR

t1 = torch.tensor([0.0])
opt = torch.optim.SGD([t1], lr=1.0)

torch_lr_scheduler = ExponentialLR(optimizer=opt, gamma=0.98)
lr_values = [None] * 100
scheduler = create_lr_scheduler_with_warmup(torch_lr_scheduler,
                                            warmup_start_value=0.0,
                                            warmup_end_value=None,  # warmup_end_value -> 1.0
                                            warmup_duration=10,
                                            output_simulated_values=lr_values)

print(lr_values)
```

diff --git a/tests/ignite/contrib/handlers/test_param_scheduler.py b/tests/ignite/contrib/handlers/test_param_scheduler.py
index 5a74de52..7aced3ef 100644
--- a/tests/ignite/contrib/handlers/test_param_scheduler.py
+++ b/tests/ignite/contrib/handlers/test_param_scheduler.py
@@ -261,10 +261,10 @@ def test_concat_scheduler_asserts():
         ConcatScheduler(schedulers=[], durations=[])
 
     with pytest.raises(ValueError):
-        ConcatScheduler(schedulers=[scheduler_1,], durations=[10,])
+        ConcatScheduler(schedulers=[scheduler_1], durations=[10])
 
     with pytest.raises(TypeError):
-        ConcatScheduler(schedulers=[scheduler_1, 12], durations=[10,])
+        ConcatScheduler(schedulers=[scheduler_1, 12], durations=[10])
 
     with pytest.raises(ValueError):
         ConcatScheduler(schedulers=[scheduler_1, scheduler_2], durations=[10, 5])
@@ -277,7 +277,7 @@ def test_concat_scheduler_asserts():
 
     with pytest.raises(ValueError):
         ConcatScheduler.simulate_values(
-            num_events=123, schedulers=[scheduler_1, scheduler_2], durations=[15,], param_names="abc"
+            num_events=123, schedulers=[scheduler_1, scheduler_2], durations=[15], param_names="abc"
         )
 
 
@@ -286,9 +286,7 @@ def test_concat_scheduler_state_dict():
     optimizer = torch.optim.SGD([tensor], lr=0)
     scheduler_1 = LinearCyclicalScheduler(optimizer, "lr", start_value=1.0, end_value=0.0, cycle_size=10)
     scheduler_2 = CosineAnnealingScheduler(optimizer, "lr", start_value=0.0, end_value=1.0, cycle_size=10)
-    durations = [
-        10,
-    ]
+    durations = [10]
     concat_scheduler = ConcatScheduler(schedulers=[scheduler_1, scheduler_2], durations=durations, save_history=False)
     state_dict = concat_scheduler.state_dict()
 
@@ -319,9 +317,7 @@ def test_concat_scheduler_two_schedulers():
         scheduler_1 = LinearCyclicalScheduler(optimizer, "lr", start_value=1.0, end_value=0.0, cycle_size=10)
         scheduler_2 = CosineAnnealingScheduler(optimizer, "lr", start_value=0.0, end_value=1.0, cycle_size=10)
 
-        durations = [
-            10,
-        ]
+        durations = [10]
         if duration_vals_as_np_int:
             durations = [np.int64(t) for t in durations]
 
@@ -395,9 +391,7 @@ def test_concat_scheduler_two_linear():
     scheduler_1 = LinearCyclicalScheduler(optimizer, "lr", start_value=0.0, end_value=0.1, cycle_size=2)
     scheduler_2 = LinearCyclicalScheduler(optimizer, "lr", start_value=0.2, end_value=1.0, cycle_size=2)
 
-    durations = [
-        5,
-    ]
+    durations = [5]
     concat_scheduler = ConcatScheduler(schedulers=[scheduler_1, scheduler_2], durations=durations, save_history=True)
     state_dict = concat_scheduler.state_dict()
 
@@ -557,7 +551,7 @@ def test_lr_scheduler_asserts():
 
     t1 = torch.zeros([1], requires_grad=True)
     t2 = torch.zeros([1], requires_grad=True)
-    optimizer = torch.optim.SGD([{"params": t1, "lr": 0.1}, {"params": t2, "lr": 0.1},])
+    optimizer = torch.optim.SGD([{"params": t1, "lr": 0.1}, {"params": t2, "lr": 0.1}])
     lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer=optimizer, gamma=0.98)
 
     with pytest.raises(ValueError):
@@ -656,7 +650,7 @@ def test_piecewiselinear_asserts():
         PiecewiseLinear(optimizer, "lr", milestones_values=[])
 
     with pytest.raises(ValueError):
-        PiecewiseLinear(optimizer, "lr", milestones_values=[(0.5,),])
+        PiecewiseLinear(optimizer, "lr", milestones_values=[(0.5,)])
 
     with pytest.raises(ValueError):
         PiecewiseLinear(optimizer, "lr", milestones_values=[(10, 0.5), (0.6,)])
@@ -820,9 +814,7 @@ def test_simulate_and_plot_values():
     # ConcatScheduler = [LinearCyclicalScheduler, CosineAnnealingScheduler]
     scheduler_1 = LinearCyclicalScheduler(optimizer, "lr", start_value=1.0, end_value=0.0, cycle_size=20)
     scheduler_2 = CosineAnnealingScheduler(optimizer, "lr", start_value=0.0, end_value=1.0, cycle_size=10)
-    durations = [
-        10,
-    ]
+    durations = [10]
     _test(ConcatScheduler, optimizer=optimizer, schedulers=[scheduler_1, scheduler_2], durations=durations)
 
     # ConcatScheduler = [LinearCyclicalScheduler, LRScheduler]
@@ -831,9 +823,7 @@ def test_simulate_and_plot_values():
     torch_lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer=optimizer, gamma=1.5)
     scheduler_1 = LRScheduler(torch_lr_scheduler)
     scheduler_2 = LinearCyclicalScheduler(optimizer, "lr", start_value=0.1, end_value=0.0, cycle_size=10)
-    durations = [
-        10,
-    ]
+    durations = [10]
     _test(ConcatScheduler, optimizer=optimizer, schedulers=[scheduler_1, scheduler_2], durations=durations)
 
     # PiecewiseLinear
@@ -889,6 +879,9 @@ def test_create_lr_scheduler_with_warmup():
             warmup_duration=warmup_duration,
             output_simulated_values=simulated_values,
         )
+        if warmup_end_value is None:
+            warmup_end_value = optimizer.param_groups[0]["lr"]
+
         state_dict = scheduler.state_dict()
         trainer = Engine(lambda engine, batch: None)
 
@@ -958,6 +951,11 @@ def test_create_lr_scheduler_with_warmup():
     )
     _test(lr_scheduler, optimizer, 0.01, 0.8, 2, 0.8 - (0.8 / 5.0))
 
+    # E) warmup_end_value is None: fall back to case B)
+    optimizer = torch.optim.SGD([t1], lr=0.2)
+    torch_lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer=optimizer, gamma=0.98)
+    _test(torch_lr_scheduler, optimizer, 0.01, None, 10, 0.2 * 0.98)
+
 
 def test_create_lr_scheduler_with_warmup_on_combined_scheduler():
     # Test with a complex scheduler
@@ -986,7 +984,7 @@ def test_create_lr_scheduler_with_warmup_on_combined_scheduler():
 
         lr_scheduler = ConcatScheduler(
             schedulers=[scheduler_1, scheduler_2],
-            durations=[num_iterations - warmup_duration - cooldown_duration,],
+            durations=[num_iterations - warmup_duration - cooldown_duration],
             save_history=False,
         )
         lr_values = [None] * num_iterations
@@ -1057,7 +1055,7 @@ def test_param_group_scheduler_asserts():
 
     t1 = torch.zeros([1], requires_grad=True)
     t2 = torch.zeros([1], requires_grad=True)
-    optimizer = torch.optim.SGD([{"params": t1, "lr": 0.1}, {"params": t2, "lr": 0.1},])
+    optimizer = torch.optim.SGD([{"params": t1, "lr": 0.1}, {"params": t2, "lr": 0.1}])
 
     lr_scheduler1 = LinearCyclicalScheduler(
         optimizer, "lr", param_group_index=0, start_value=1.0, end_value=0.0, cycle_size=10
@@ -1076,7 +1074,7 @@ def test_param_group_scheduler_asserts():
         ParamGroupScheduler(schedulers=[lr_scheduler1, lr_scheduler2], names="ab")
 
     with pytest.raises(ValueError):
-        ParamGroupScheduler(schedulers=[lr_scheduler1, lr_scheduler2], names=["a",])
+        ParamGroupScheduler(schedulers=[lr_scheduler1, lr_scheduler2], names=["a"])
 
     scheduler = ParamGroupScheduler(schedulers=[lr_scheduler1, lr_scheduler2], names=["a", "b"])
     with pytest.raises(ValueError, match=r"Required state attribute 'schedulers' is absent in provided state_dict"):
@@ -1117,7 +1115,7 @@ def test_param_group_scheduler():
 
     t1 = torch.zeros([1], requires_grad=True)
     t2 = torch.zeros([1], requires_grad=True)
-    optimizer = torch.optim.SGD([{"params": t1, "lr": 0.1}, {"params": t2, "lr": 0.1},])
+    optimizer = torch.optim.SGD([{"params": t1, "lr": 0.1}, {"params": t2, "lr": 0.1}])
 
     lr_scheduler1 = LinearCyclicalScheduler(
         optimizer, "lr", param_group_index=0, start_value=1.0, end_value=0.0, cycle_size=10

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc pip install torch torchvision -f https://download.pytorch.org/whl/cpu/torch_stable.html -U
3.7
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
exceptiongroup==1.2.2
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
numpy==1.21.6
packaging==24.0
Pillow==9.5.0
pluggy==1.2.0
pytest==7.4.4
-e git+https://github.com/pytorch/ignite.git@d48588f7972daf7d6d8e4dd91295c5f406e980d1#egg=pytorch_ignite
requests==2.31.0
tomli==2.0.1
torch==1.13.1+cpu
torchvision==0.14.1+cpu
typing_extensions==4.7.1
urllib3==2.0.7
zipp==3.15.0

name: ignite
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - charset-normalizer==3.4.1
      - exceptiongroup==1.2.2
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - numpy==1.21.6
      - packaging==24.0
      - pillow==9.5.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - pytorch-ignite==0.4.0
      - requests==2.31.0
      - tomli==2.0.1
      - torch==1.13.1+cpu
      - torchvision==0.14.1+cpu
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - zipp==3.15.0
prefix: /opt/conda/envs/ignite

tests/ignite/contrib/handlers/test_param_scheduler.py::test_create_lr_scheduler_with_warmup
tests/ignite/contrib/handlers/test_param_scheduler.py::test_simulate_and_plot_values
tests/ignite/contrib/handlers/test_param_scheduler.py::test_linear_scheduler tests/ignite/contrib/handlers/test_param_scheduler.py::test_linear_scheduler_cycle_size_two tests/ignite/contrib/handlers/test_param_scheduler.py::test_cosine_annealing_scheduler tests/ignite/contrib/handlers/test_param_scheduler.py::test_concat_scheduler_asserts tests/ignite/contrib/handlers/test_param_scheduler.py::test_concat_scheduler_state_dict tests/ignite/contrib/handlers/test_param_scheduler.py::test_concat_scheduler_two_schedulers tests/ignite/contrib/handlers/test_param_scheduler.py::test_concat_scheduler_two_linear tests/ignite/contrib/handlers/test_param_scheduler.py::test_concat_scheduler_3_schedulers tests/ignite/contrib/handlers/test_param_scheduler.py::test_save_param_history tests/ignite/contrib/handlers/test_param_scheduler.py::test_lr_scheduler_asserts tests/ignite/contrib/handlers/test_param_scheduler.py::test_lr_scheduler tests/ignite/contrib/handlers/test_param_scheduler.py::test_piecewiselinear_asserts tests/ignite/contrib/handlers/test_param_scheduler.py::test_piecewiselinear tests/ignite/contrib/handlers/test_param_scheduler.py::test_create_lr_scheduler_with_warmup_on_combined_scheduler tests/ignite/contrib/handlers/test_param_scheduler.py::test_create_lr_scheduler_with_warmup_with_real_model tests/ignite/contrib/handlers/test_param_scheduler.py::test_param_group_scheduler_asserts tests/ignite/contrib/handlers/test_param_scheduler.py::test_param_group_scheduler
ignite/contrib/handlers/param_scheduler.py
ignite/contrib/handlers/param_scheduler.py