Skip to content

Preserve tensor learning rates across scheduler updates - #8202

Merged
delock merged 2 commits into
deepspeedai:masterfrom
n33levo:nsarkar/preserve-tensor-lr
Aug 2, 2026
Merged

Preserve tensor learning rates across scheduler updates#8202
delock merged 2 commits into
deepspeedai:masterfrom
n33levo:nsarkar/preserve-tensor-lr

Conversation

@n33levo

@n33levo n33levo commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

What it is

DeepSpeed's tensor learning-rate support currently replaces an optimizer's LR tensor on every scheduler update. A scalar float64 LR tensor becomes a new one-dimensional tensor during scheduler initialization, changing its identity, shape, and initially its dtype. This also leaves any caller-held reference to the supplied LR tensor pointing at the stale value.

The root cause is update_lr() constructing tensor([lr]) instead of updating the tensor supplied through the optimizer. This is a follow-up to the tensor LR support added in #7633.

How it works

  • Fill the optimizer's existing LR tensor in place, matching PyTorch scheduler behavior.
  • Squeeze a calculated one-element tensor to the scalar value expected by fill_, covering both zero-dimensional and one-element LR tensors.
  • Snapshot tensor base LRs in WarmupCosineLR before initialization updates the optimizer tensor, preserving the original value used by later schedule steps.
  • Leave the scalar LR assignment path unchanged.
  • Add WarmupLR and WarmupCosineLR regression tests that check object identity, shape, dtype, initialization, and scheduled values.

E2E Top-hatting

On current master, a zero-dimensional float64 LR tensor is replaced by a different one-dimensional tensor. With this change, both zero-dimensional and one-element LR tensors retain their original identity, shape, and dtype while reaching the expected warmup values. WarmupCosineLR also retains an independent base value, preventing its schedule from remaining at zero after initialization.

A clean GitHub-hosted Ubuntu 24.04 CPU run used Python 3.11 and PyTorch 2.10:

  • pytest --forked -n 4 unit/runtime/test_lr_schedulers.py --torch_ver=2.10
  • 70 passed (run)

Checks

  • pre-commit run --files deepspeed/runtime/lr_schedules.py tests/unit/runtime/test_lr_schedulers.py
  • All formatting, lint, license, spelling, and custom Torch checks passed.

Signed-off-by: n33levo <n33levo@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80d036c974

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deepspeed/runtime/lr_schedules.py
Comment thread deepspeed/runtime/lr_schedules.py
Signed-off-by: n33levo <n33levo@users.noreply.github.com>
@delock
delock added this pull request to the merge queue Aug 2, 2026
Merged via the queue into deepspeedai:master with commit 3b64148 Aug 2, 2026
13 checks passed
banxingmjj pushed a commit to openanolis/DeepSpeed that referenced this pull request Aug 3, 2026
Follow up deepspeedai#8202:

When an optimizer starts with a tensor learning rate, OneCycle
initialization replaces it with a Python scalar while applying
`cycle_min_lr`. This loses the caller's tensor identity, shape, and
dtype before later scheduler updates can preserve them.

This PR initializes OneCycle learning rates through the existing
tensor-aware update helper, matching the path used by subsequent
scheduler steps.

Signed-off-by: Masahiro Tanaka <mtanaka@anyscale.com>
banxingmjj pushed a commit to openanolis/DeepSpeed that referenced this pull request Aug 4, 2026
…peedai#8206)

## What

Replaces the three per-scheduler `*_preserves_tensor_lr` tests
(`WarmupLR`, `WarmupCosineLR`, `OneCycle`) with a single parametrized
contract test driven by a `TENSOR_LR_CONTRACTS` table, plus a
registry-coverage assertion that fails collection when a scheduler
listed in `VALID_LR_SCHEDULES` has no contract row.

## Why

deepspeedai#8205 recently fixed `OneCycle._initialize_lr` bypassing `update_lr()`,
which silently replaced a caller-supplied tensor LR with a Python scalar
on construction. WarmupLR and WarmupCosineLR had the same class of bug
fixed in deepspeedai#8202. Each fix was caught only because someone happened to
write a hand-rolled regression test for that specific scheduler.

This PR turns that pattern into a contract: every scheduler in
`VALID_LR_SCHEDULES` is pinned by one table row asserting tensor
identity / shape / dtype at init and after `step(1)`. Two failure modes
are covered:

1. **Registered but buggy** (writes `group['lr'] = scalar` in its
constructor) — the parametrized contract case fails on the identity
assertion.
2. **Forgotten** (a new scheduler is added to `VALID_LR_SCHEDULES`
without a contract row) —
`test_all_schedulers_covered_by_tensor_lr_contract` fails at collection
with the exact missing class name.

Both guards were verified empirically against this branch:

- Applied the contract test on top of `pr-8202` (i.e. before deepspeedai#8205): the
two `OneCycle` cases fail with `assert 0.01 is tensor(0.1000,
dtype=torch.float64)`, exactly the symptom deepspeedai#8205 fixes. The other 8
cases pass, so no false positives.
- Removed the `OneCycle` row from `TENSOR_LR_CONTRACTS`: the coverage
test fails with `missing tensor-LR contract for: ['OneCycle']`.

## Test footprint

CPU-only, no GPU/distributed runner required:

```
pytest tests/unit/runtime/test_lr_schedulers.py -k "preserves_tensor or covered_by_tensor"
# 11 passed (5 schedulers x 2 shapes + 1 coverage)
```

`pre-commit run --files tests/unit/runtime/test_lr_schedulers.py` passes
(yapf, flake8, check-torchdist, check-license, codespell).

---

Authored by an AI coding agent (opencode, model: glm-5.2) under
interactive human direction and review.

Signed-off-by: Guokai Ma <guokai.ma@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants