torch-directml: F.cross_entropy(reduction='none') silently produces zero gradient on backward (0.2.5.dev240914 / torch 2.4.1+cpu)
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- python, pytorch
- Domain
- backend, machine-learning
Research direction
Start with the minimal reproduction described in the issue and compare F.cross_entropy with reduction='none' against the manual log_softmax+gather and reduction='mean' controls. Review diag_gradflow_repro.py and the fused cross-entropy forward/backward path on the DirectML device. Done means reduction='none' produces a nonzero upstream gradient and the regression is covered by a focused test.
Written by the indexing model from the issue text.
Description
Summary
On torch-directml==0.2.5.dev240914 (torch 2.4.1+cpu), calling
torch.nn.functional.cross_entropy(logits, targets, reduction='none')
and then combining/reducing the per-element losses manually (e.g. to
apply a custom per-token weighting before backward) produces a normal
forward pass, a normal-looking loss value, and a normal-looking autograd
graph — but .backward() on the resulting loss yields an all-zero
gradient on every upstream parameter. No error, warning, or NaN is
raised. Training appears to proceed normally (loss values printed each
step look plausible) while no parameter is actually being updated.
We hit this in a real LoRA fine-tuning run: after ~450 optimizer steps
with a per-token-weighted cross-entropy loss built on
reduction='none', none of the LoRA adapter's trainable tensors had
moved from their initial values, despite the run completing without
error and reporting a steadily-changing loss curve throughout.
Environment
torch==2.4.1+cputorch-directml==0.2.5.dev240914transformers==4.46.3,peft==0.20.0,accelerate==1.15.0- Windows, AMD Radeon 8060S-class integrated GPU, DirectML device
Minimal repro
The following isolates the bug to specifically the fused
reduction='none' kernel, by comparing three mathematically-equivalent
ways of computing the same per-token loss on the same model, inputs,
and device, and checking the gradient norm on a known trainable tensor
after a single backward() call:
import torch
import torch.nn.functional as F
import torch_directml
device = torch_directml.device()
# ... set up a small model with a trainable parameter (e.g. a linear
# layer or a LoRA adapter tensor) on `device`, and a batch of
# logits/targets on the same device ...
# 1. Fused kernel, reduction='none', then manual mean:
loss_a = F.cross_entropy(logits, targets, reduction='none').mean()
loss_a.backward()
print("fused reduction='none':", param.grad.norm().item()) # -> 0.0
# reset gradients, recompute logits fresh, then:
# 2. Manual unfused equivalent:
log_probs = F.log_softmax(logits, dim=-1)
per_token_loss = -log_probs.gather(-1, targets.unsqueeze(-1)).squeeze(-1)
loss_b = per_token_loss.mean()
loss_b.backward()
print("manual log_softmax+gather:", param.grad.norm().item()) # -> nonzero
# reset gradients, recompute logits fresh, then:
# 3. Control: built-in mean-reduction path (as used internally by
# Hugging Face Transformers' default loss computation):
loss_c = F.cross_entropy(logits, targets, reduction='mean')
loss_c.backward()
print("built-in reduction='mean':", param.grad.norm().item()) # -> nonzero
In our real repro (diag_gradflow_repro.py, run against a live LoRA
adapter tensor on a real training batch), the three gradient norms were:
| Path | grad_norm |
|---|---|
F.cross_entropy(reduction='none'), fused |
0.0 |
Manual log_softmax + gather |
0.0534 |
F.cross_entropy(reduction='mean') (HF-internal control) |
0.0699 |
Only the fused reduction='none' path returns a zero gradient. The
manual unfused equivalent and the built-in reduction='mean' control
both produce a real, nonzero gradient on the identical batch, model, and
device — which rules out the model, the data, or the optimizer as the
cause and isolates the problem to the fused kernel's backward pass under
reduction='none' specifically.
We're happy to share a fully self-contained, minimal script (no
transformers/peft dependency) reproducing this on a plain
nn.Linear if that's more useful for triage — let us know.
Why this is worth flagging as high-severity
There is no error, warning, or NaN anywhere in this failure. The loss
value printed at each step looks plausible and can even appear to
decrease over a run (if the reduction happens elsewhere in the
pipeline), so this is not something a user would discover by watching
training logs — only by explicitly checking that trainable parameters
actually changed after training. We only found it because we added a
pre-flight gradient-flow assertion as a general safety practice. We'd
guess this could silently affect anyone using a custom/per-token loss
built on reduction='none' on this backend, not just our specific
LoRA use case.
What we're NOT reporting here
This is unrelated to #702 (masked_fill uint8 overflow during
gpt-neo inference) — we hit that one separately during the same
project and worked around it, but it's a different bug in a different
op with a different (loud, exception-raising) failure mode. Filing this
as a new issue rather than a comment on #702 since the root cause,
affected op, and failure signature are all different.
- Dominant language
- C++
- Stars
- 2.6k
- Forks
- 338
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from microsoft/DirectML
-
Difficulty 1/5 Under an hour Newbie friendliness 68/100
-
Difficulty 4/5 3-5 days Newbie friendliness 62/100
-
Difficulty 4/5 3-5 days Newbie friendliness 25/100
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
All issues in microsoft/DirectML
Similar issues
-
ai_reviewed
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
ydb-platform/ydb#53869 · 3 comments ·
-
bug cert blocker needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
project-chip/connectedhomeip#74373 ·
-
upstream update
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
conan-io/conan-center-index#31035 ·
-
Bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 85/100
vllm-project/vllm-ascend#17329 ·