Repository navigation
[tt-crank] fuse cross entropy onto the ttml cross_entropy_fw/bw composites - #9324
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved correctness and integration issues remain in the fallback lowerings and partition hook.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds fused cross-entropy forward/backward composites, fallback lowerings, native bindings, and coverage across optimization levels.
Changes:
- Adds composite builders and TTNN promotion/decomposition support.
- Rewrites supported cross-entropy graphs with fallback handling.
- Adds fusion, fallback, and artifact-based tests.
File summaries
| File | Reviewed changes | Findings |
|---|---|---|
tt-crank/tests/python/test_cross_entropy.py |
Cross-entropy fusion and fallback coverage | — |
tt-crank/src/torch/ops/builders.hpp |
Builder exports | — |
tt-crank/src/torch/_native.cpp |
Native builder bindings | — |
tt-crank/src/engine/ttir_module_builder.hpp |
Builder API declarations | — |
tt-crank/src/engine/ttir_module_builder.cpp |
Composite construction and decomposition | — |
tt-crank/python/tt_crank/torch/_compile.py |
Fusion, fallback lowerings, and partition integration | Critical: support or reject N-D inputs correctly. Moderate: use stable log-softmax computation, handle weighted cross-entropy fallback, and preserve the GQA rewrite during partition integration. |
Review details
Suppressed comments (4)
tt-crank/python/tt_crank/torch/_compile.py:1012
- For mean reduction with every row set to
ignore_index,total_weightis zero. Dividing by it and then multiplying the resulting infinity by the all-zero one-hot mask yields NaN gradients, while PyTorch leaves ignored-row gradients at zero. Guard the zero-count case before the division (and preserve the NaN forward loss).
grad = (
mb.div(grad_output, total_weight)
if reduction == _NLL_REDUCTION_MEAN
else grad_output
)
tt-crank/python/tt_crank/torch/_compile.py:1103
- The fused mean path has the same all-ignored edge case:
grad / total_weightbecomes infinite when every row is ignored, socross_entropy_bwcan generate NaNs before the later validity mask zeros the rows. Guard the denominator or feed a zero gradient to the kernel whentotal_weight == 0, matching PyTorch's zero gradients for ignored targets.
if reduction == _NLL_REDUCTION_MEAN:
grad = emit(_aten.div.Tensor, grad, total_weight)
d_logits = emit(
_aten.mul.Tensor,
emit(_CROSS_ENTROPY_BW, grad, logits, clamped),
tt-crank/python/tt_crank/torch/_compile.py:975
- The PR description says cases outside the fused kernel use the new ATen fallback, but this lowering still raises when
weightis provided. A valid weightedF.cross_entropytherefore fails compilation instead of taking the fallback; implement class-weight handling in the forward/backward lowerings or narrow the advertised fallback scope.
@_lowering(_aten.nll_loss_forward.default)
@_skip_prepare(_aten.nll_loss_forward.default)
@_needs_node
def _(mb, node, log_probs, target, weight, reduction, ignore_index):
if weight is not None:
raise NotImplementedError("tt-crank nll_loss: class weights are not supported")
tt-crank/python/tt_crank/torch/_compile.py:982
- For a rank-greater-than-two fallback,
rowsandvalidretain every non-class dimension, but both reductions here only reduce dimension 0.reduction="sum"/"mean"must produce a scalar andtotal_weightmust count all valid target elements; with NCHW inputs this leaves an HxW tensor and changes the result shape and normalization.
rows = mb.neg(mb.sum(mb.mul(log_probs, onehot), [1], False))
total_weight = mb.sum(valid, [0], False)
if reduction == _NLL_REDUCTION_NONE:
return rows, total_weight
loss = mb.sum(rows, [0], False)
- Files reviewed: 6/6 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| classes = mb.arange(0, log_probs.shape[1], 1, _to_runtime_dtype(torch.int32)) | ||
| onehot = mb.typecast( | ||
| mb.eq( | ||
| mb.unsqueeze(mb.typecast(target, _to_runtime_dtype(torch.int32)), 1), | ||
| classes, |
| @_lowering(_aten._log_softmax.default) | ||
| def _(mb, x, dim, half_to_float=False): | ||
| return mb.log(mb.softmax(x, int(dim))) |
| if _fuse_cross_entropy(joint_module.graph): | ||
| joint_module.recompile() | ||
| return default_partition(joint_module, joint_inputs, **kwargs) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9324 +/- ##
==========================================
- Coverage 73.83% 73.82% -0.01%
==========================================
Files 515 515
Lines 108129 108129
==========================================
- Hits 79834 79826 -8
- Misses 28295 28303 +8 ☔ View full report in Codecov by Harness. |
…ck) (#9339) ### Ticket Tracking: tenstorrent/tt-blacksmith#685 ### Problem description `F.cross_entropy` decomposes to `aten._log_softmax` + `aten.nll_loss_forward` (backward: `nll_loss_backward` + `_log_softmax_backward_data`). None of them lowered, so single-chip Llama training did not compile. ### What's changed Lowerings for the four ops: `[rows x C]` log-probabilities, integer targets with `ignore_index`, mean/sum/none, no class weights. `_log_softmax` is max-shifted log-sum-exp (plain `log(softmax)` hits -inf past a logit gap of ~87). The mean backward clamps the row count to >= 1 so an all-ignored batch gives zero gradients like torch. Stacked on the max PR for `ModuleBuilder.max`. The fused `cross_entropy_fw/bw` path (#9324) rewrites this pattern onto the ttml kernels and keeps these as its fallback. ### Checklist - [x] New/Existing tests provide coverage for changes: `tests/python/test_cross_entropy.py` (5 reductions/dtypes vs CPU, large-logit finiteness, all-ignored batch, rank-1 rejection)
Emits a ttcore.composite together with a private decomposition function built by the caller through the usual build_* helpers. tt-mlir promotes the composite to its typed op when the name is in the resolver registry and inlines the decomposition otherwise (or at OPT_LEVEL 0), so one builder serves both the kernel and the fallback path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
a97281b to
82f4e92
Compare
| options: dict[CompileOption, str | int | bool] | None = None, | ||
| ): | ||
| """Top-level dynamo backend. Delegates to aot_module_simplified.""" | ||
| rewrite_cross_entropy(gm) |
There was a problem hiding this comment.
good place to have a wrapper rewrite_custom_ops and group all new thing, but we can let it be like this for now
| if average: | ||
| t.copy_(t.div(self.size())) |
pilkicTT
left a comment
There was a problem hiding this comment.
i don't like the rewriting of the FX graph and believe that this can be done in a more native way to torch, let's sync...
| options: dict[CompileOption, str | int | bool] | None = None, | ||
| ): | ||
| """Top-level dynamo backend. Delegates to aot_module_simplified.""" | ||
| rewrite_cross_entropy(gm) |
There was a problem hiding this comment.
this is not ok - i think we should be able to implement proper AutogradPrivateUse1 kernels and avoid rewriting the FX graph ourselves, but let torch do it... we can sync offline...
Ticket
Tracking: tenstorrent/tt-blacksmith#685
Problem description
We have
cross_entropy_fwandcross_entropy_bwinttml, one kernel each way, and we want the loss to use them.aten._log_softmax[1023, 128256]f32, 500 MiBttnn.cross_entropy_fw(logits, target)aten.nll_loss_forwardaten.nll_loss_backward[1023, 128256]ttnn.cross_entropy_bw(grad, logits, target)aten._log_softmax_backward_data[1023, 128256]f32What's changed
We inject a custom torch op for
F.cross_entropyand lower it onto thettmlkernels.Note
We swap the
F.cross_entropycall itself, not a pattern of decomposed ops. A hand written_log_softmaxplusnll_losshas no such call to swap, so it keeps the aten lowerings from #9339 and is not fused.Checklist
tests/python/test_cross_entropy.py, swapped and aten paths atOPT_LEVEL 1andOPT_LEVEL 0against CPU (mean/sum,ignore_index, off tile shapes, 8k classes,noneand f32 fallbacks), a promotion check on the TTIR and TTNN dumps, and edge cases (large logits, a batch where every row is ignored, logits that are not finite in ignored rows, rank 1 rejection).OPT_LEVEL 1cases arexfail(run=False)under ttsim.