Lightning-AI/pytorch-lightning · error · NotImplementedError
Support for `{epoch_end_name}` has been removed in v2.0.0. `
Error message
Support for `{epoch_end_name}` has been removed in v2.0.0. `{type(model).__name__}` implements this method. You can use the `on_{epoch_end_name}` hook instead. To access outputs, save them in-memory as instance attributes. You can find migration examples in https://github.com/Lightning-AI/pytorch-lightning/pull/16520. What it means
Lightning 2.0 removed the `validation_epoch_end` / `test_epoch_end` hooks. If your module still defines them as callable methods, Lightning raises NotImplementedError at run start and points you to the `on_validation_epoch_end` / `on_test_epoch_end` hooks instead, with outputs stored as instance attributes.
Source
Thrown at src/lightning/pytorch/trainer/configuration_validator.py:111
step_name = "validation_step" if stage == "val" else f"{stage}_step"
has_step = is_overridden(step_name, model)
# predict_step is not required to be overridden
if stage == "predict":
if model.predict_step is None:
raise MisconfigurationException("`predict_step` cannot be None to run `Trainer.predict`")
if not has_step and not is_overridden("forward", model):
raise MisconfigurationException("`Trainer.predict` requires `forward` method to run.")
else:
# verify minimum evaluation requirements
if not has_step:
trainer_method = "validate" if stage == "val" else stage
raise MisconfigurationException(f"No `{step_name}()` method defined to run `Trainer.{trainer_method}`.")
# check legacy hooks are not present
epoch_end_name = "validation_epoch_end" if stage == "val" else "test_epoch_end"
if callable(getattr(model, epoch_end_name, None)):
raise NotImplementedError(
f"Support for `{epoch_end_name}` has been removed in v2.0.0. `{type(model).__name__}` implements this"
f" method. You can use the `on_{epoch_end_name}` hook instead. To access outputs, save them in-memory"
" as instance attributes."
" You can find migration examples in https://github.com/Lightning-AI/pytorch-lightning/pull/16520."
)
def __verify_manual_optimization_support(trainer: "pl.Trainer", model: "pl.LightningModule") -> None:
if model.automatic_optimization:
return
if trainer.gradient_clip_val is not None and trainer.gradient_clip_val > 0:
raise MisconfigurationException(
"Automatic gradient clipping is not supported for manual optimization."
f" Remove `Trainer(gradient_clip_val={trainer.gradient_clip_val})`"
" or switch to automatic optimization."
)
if trainer.accumulate_grad_batches != 1:
raise MisconfigurationException(View on GitHub (pinned to 9fed5c27d2)
Solutions
- Delete `validation_epoch_end`/`test_epoch_end` and move aggregation logic into `on_validation_epoch_end`/`on_test_epoch_end`.
- Collect step outputs manually: append them to a list in `validation_step` and compute metrics in the epoch-end hook.
- If a dependency ships the legacy hook, upgrade that dependency or override the method with `pass` in your subclass.
- See migration examples in PR #16520 linked in the message.
Example fix
# before
class Model(L.LightningModule):
def validation_step(self, batch, idx):
return self(loss)
def validation_epoch_end(self, outputs):
self.log('val_loss', torch.stack(outputs).mean())
# after
class Model(L.LightningModule):
def __init__(self):
super().__init__()
self.val_outputs = []
def validation_step(self, batch, idx):
loss = self.step(batch)
self.val_outputs.append(loss)
return loss
def on_validation_epoch_end(self):
self.log('val_loss', torch.stack(self.val_outputs).mean())
self.val_outputs.clear() Defensive patterns
Strategy: validation
Validate before calling
import inspect
def has_legacy_epoch_end(model):
return any(callable(getattr(model, n, None)) and getattr(model, n, None) is not getattr(object, n, None)
for n in ('validation_epoch_end', 'test_epoch_end'))
if has_legacy_epoch_end(model):
raise RuntimeError('Migrate *_epoch_end hooks to on_*_epoch_end (Lightning 2.0)') Type guard
def is_lightning2_compatible(model) -> bool:
return not any(callable(getattr(model, n, None)) for n in ('validation_epoch_end', 'test_epoch_end')) Try / catch
try:
trainer.fit(model)
except NotImplementedError as e:
if 'removed in v2.0.0' in str(e):
# strip legacy hooks and re-run
...
raise Prevention
- Run the Lightning 2.0 migration guides / `lightning` upgrade checklist when bumping versions.
- Add a unit test that instantiates the trainer config for each model to catch validation errors early.
- Search the codebase for '_epoch_end(' during upgrades.
When it happens
Trigger: A LightningModule (including inherited base classes) defines `validation_epoch_end` or `test_epoch_end` and you call `trainer.fit/validate/test`. This includes code migrated from Lightning 1.x without updating the hooks.
Common situations: Upgrading a project from lightning <2.0 to >=2.0; old tutorials/examples; a shared corporate base model class still carrying the legacy hook.
Related errors
- Redirecting import of {module}.{name} to {new_module}.{name}
- Device should be CPU, got {device} instead.
- `devices` selected with `CPUAccelerator` should be an int >
- Device should be CUDA, got {device} instead.
- You requested to find {num_devices} devices but there are no
AI-assisted analysis of Lightning-AI/pytorch-lightning@9fed5c27d2 (2026-08-28).
Data as JSON: /api/errors/8adb4ac87ee2bbf4.
Report an issue: GitHub.