You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
adj_tile_dot is missing a block-wide broadcast of adj_ret #1374
adj_tile_dot (warp/native/tile_reduce.h:729-756) uses each thread's local adj_ret directly, but in reverse mode adj_ret is only non-zero on the thread(s) that consumed the forward scalar. When the idiomatic "one thread writes the result" pattern is used, only that thread receives a gradient, and the tile elements owned by every other thread get zero.
Forward value is correct (816.0); only the gradient is wrong.
Why the existing test_tile_dot_grad* tests don't catch it
They use 1-D i = wp.tid() in a tiled launch. launch_coord divides the linear index by block_dim before unraveling, so wp.tid() returns the tile index, which is the same for every thread in the block. With dim=[1], i == 0 is true on all 64 threads, every thread executes adj_array_store, and every thread pulls adj_out[0] into its own adj_ret. The gate never narrows execution to a single thread, so the tests only validate the uniform-adj_ret case.
Why this is structural to tile_dot and not other reductions
adj_tile_sum (whole-tile, tile_reduce.h:627) already broadcasts adj_ret from thread 0 through shared memory.
adj_tile_sum<Axis> (tile_reduce.h:805) broadcasts via tile_broadcast + tile_add_inplace.
adj_tile_reduce_impl, adj_tile_max, adj_tile_min, adj_tile_arg{max,min} are empty stubs.
adj_tile_axpy handles the symmetric scalar-input case via a single-thread accumulate guard on adj_alpha.
tile_dot is the only tile primitive whose forward returns a raw scalar broadcast to every thread (via __shared__ ScalarT scratch at tile_reduce.h:696 and :713-717). The forward path has a broadcast; the reverse path does not.
Context
Originally flagged during review of MR !2216 (the MR that added wp.tile_dot / wp.tile_axpy â GH-1363, GH-1364) but deferred to a follow-up so the primitives could land.
Rather than patching adj_tile_dot to broadcast adj_ret (as the issue title suggested), the fix restructures tile_dot itself to match the tile_sum / tile_reduce convention, which side-steps the "which thread holds the canonical gradient" question entirely.
Forward change:tile_dot now returns a single-element register tile (tile(dtype=T, shape=(1,))) instead of a raw scalar broadcast to all threads. The shared-memory broadcast that previously happened inside tile_dot is gone; only the reducing thread writes to output.data[0].
Adjoint change:adj_tile_dot now takes a 1-element tile for adj_ret and reuses the same __shared__ scratch broadcast pattern as adj_tile_sum, so every thread sees the correct gradient regardless of which thread wrote the forward result.
User-facing API change (vs. the originally-merged wp.tile_dot): Because tile_dot had not shipped in a release yet (still in Unreleased), we took the opportunity to change the return type rather than keep the scalar-returning signature. Callers must now index the result:
result=wp.tile_dot(a, b) # shape=(1,) tile, not a scalarifj==0:
out[i] =result[0] # was: out[i] = result
The documented equivalent long-form also simplifies, from wp.tile_extract(wp.tile_sum(wp.tile_map(wp.tensordot, a, b)), 0) to wp.tile_sum(wp.tile_map(wp.tensordot, a, b)).
tile_axpy, which internally calls tile_dot, was updated to read dot_result.data[0].
New regression tests test_tile_dot_grad_extract and test_tile_dot_grad_extract_shared cover the single-thread-writes-the-scalar pattern from the repro in this issue.
Activity
wp.tile_dot()for fused element-wise multiply and reduce #1364Fix missing block-wide broadcast in adj_tile_dot (NVIDIAGH-1374)
Make tile_dot return a single-element tile instead of a scalar (NVIDIâĶ
Add tile_dot gradient tests for single-thread write pattern (NVIDIAGHâĶ
Make tile_dot return a single-element tile instead of a scalar (NVIDIâĶ
shi-eric commentedon Apr 20, 2026
Fixed by b9af381.
Rather than patching
adj_tile_dotto broadcastadj_ret(as the issue title suggested), the fix restructurestile_dotitself to match thetile_sum/tile_reduceconvention, which side-steps the "which thread holds the canonical gradient" question entirely.Forward change:
tile_dotnow returns a single-element register tile (tile(dtype=T, shape=(1,))) instead of a raw scalar broadcast to all threads. The shared-memory broadcast that previously happened insidetile_dotis gone; only the reducing thread writes tooutput.data[0].Adjoint change:
adj_tile_dotnow takes a 1-element tile foradj_retand reuses the same__shared__ scratchbroadcast pattern asadj_tile_sum, so every thread sees the correct gradient regardless of which thread wrote the forward result.User-facing API change (vs. the originally-merged
wp.tile_dot): Becausetile_dothad not shipped in a release yet (still inUnreleased), we took the opportunity to change the return type rather than keep the scalar-returning signature. Callers must now index the result:The documented equivalent long-form also simplifies, from
wp.tile_extract(wp.tile_sum(wp.tile_map(wp.tensordot, a, b)), 0)towp.tile_sum(wp.tile_map(wp.tensordot, a, b)).tile_axpy, which internally callstile_dot, was updated to readdot_result.data[0].New regression tests
test_tile_dot_grad_extractandtest_tile_dot_grad_extract_sharedcover the single-thread-writes-the-scalar pattern from the repro in this issue.