Derive the divmod remainder from its quotient on the GPU - #4108
Derive the divmod remainder from its quotient on the GPU#4108ayaangazali wants to merge 2 commits into
Conversation
zcbenz
left a comment
There was a problem hiding this comment.
Can you check how numpy/pytorch behaves on this?
|
Checked, and the answer argues for a bigger change than this PR makes. Measured on int32, mlx via the CPU stream: numpy, pytorch and python all floor both halves. mlx truncates both. All four satisfy So there are two ways to go:
I did not want to pick option 2 for you inside a PR scoped to the GPU, so tell me which you want and I will redo it that way. Also pushed a fix for the metal build failure, which was mine: for the narrow int types |
|
Thanks for checking, let's do option 2 and fix all things. |
Proposed changes
Fix #4119.
DivModon the GPU pairs a truncating quotient with a flooring remainder, so the two do not describe the same division:FloorDividetruncates despite the name (x / yfor integers,trunc(x / y)for floats).Remainderfloors, since it applies the usual sign correction:Mixing them breaks
quotient * y + remainder == x. Working the two structs through by hand for int32, on the eight mixed-sign pairs below, six come out wrong:The CPU backend does not have this problem, because it builds both halves from the same division:
and
mx.divmodthere satisfies the identity on all eight pairs. So this is the GPU disagreeing with the CPU as well as with itself.Taking the remainder from the quotient that is actually returned, which is the same thing the CPU does and leaves the quotient untouched:
test_divmoddid not catch it because it draws inputs fromnp.random.uniform(1, 100), so both operands are always positive, and it only comparesmx_out[0], never the remainder. The added case checks the identity on mixed signs.Checklist
Put an
xin the boxes that apply.pre-commit run --all-filesto format my code / installed pre-commit prior to committing changesBeing explicit about what I could and could not run: my box is a CPU only build, so I could not execute the Metal or CUDA path. The table above comes from working the two structs through by hand rather than from a GPU run, and
test_ops.py(149 tests) passes here. The new assertion passes on CPU both before and after, since the CPU was already consistent, so it is the GPU jobs in CI that should show it red on main and green here. If they do not, my reading of the two structs is wrong and I would rather find that out than have it merged.freshman contributor, i work through these with Claude Code. i got here sideways: i was diffing the same op across the three backends looking for places where one had a guard the others lacked, noticed
FloorDivideexists only on the GPU side, and followed it to its one caller.