Add MLX dispatch and a Metal kernel for gammainc and gammaincc - #2347
Open
jessegrabowski wants to merge 4 commits into
Open
Add MLX dispatch and a Metal kernel for gammainc and gammaincc#2347jessegrabowski wants to merge 4 commits into
jessegrabowski wants to merge 4 commits into
Conversation
Member
What is a Meta kernel, and the time thing mentioned here is it measured on a heterogenous vector or a single scalar? |
ricardoV94
approved these changes
Aug 13, 2026
Member
Author
The C code strings |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
gammaincandgammainccare the last ops between MLX andlogcdfforGamma,InverseGamma,PoissonandChiSquared.The usual series-below-
a+1, continued-fraction-above split does not port. Both need a term count growing likesqrt(a), andahere is a runtime tensor -- a shape parameter, usually a random variable -- so there is no count that is both fixed at graph-build time and sufficient ata = 50000. Temme's uniform asymptotic expansion (DLMF 8.12) covers the transition instead: its cost does not depend ona, and its accuracy improves asagrows, which is exactly where the other two fall apart. Thed[k][n]table is generated by the script scipy ships atscipy/special/_precompute/gammainc_asy.pyrather than transcribed.Neither tail is ever taken as one minus the other in the direction that cancels. At
a = 200, x = 0.5athe truePis 9.3e-19, so a complement reports zero with 100% error, and that point is nowhere near an obvious edge case. Temme supplies either tail directly byerfc(z) + erfc(-z) = 2, so flipping one sign moves it fromQtoPwith no subtraction anywhere; the series and the fraction each own the region where the tail they compute natively is the small one.Accuracy against scipy over
ain [1e-2, 1e5] andx/ain [1e-3, 50], ~30k points per tail:float32 is limited by the log prefactor, which subtracts two numbers of order
a log a-- ata = 200, x = 2athose are both near 800 and differ by 60. Temme has no such subtraction, which is what sets its window at[0.4a, 1.8a]: widening it takes float32 from 7.9e-4 to 2.2e-4 and costs float64 8.1e-12 to 2.2e-11, and past that float64 degrades while float32 stops improving.There is also a Metal kernel, because the vectorized form evaluates all three expansions for every element and discards two. Timed separately under
mx.compileat n = 1e6 they cost 1.6, 2.4 and 6.4 ms against 11.6 ms for the composite, so it is very nearly their sum. The kernel takes one branch and stops each series on convergence, which for the ascending series is a median of 8 terms out of the 50 the graph must always unroll.Both MLX columns are float32 on the GPU stream over identical buffers, and differ only in whether the dispatch may reach its kernel. scipy ships no float32 kernel for these, so that column is float64 and the comparison against it is not apples to apples on dtype --
passesis against one fused elementwise pass over the same buffers, which is the number that does not depend on scipy at all.Times are medians in ms from
pytest-benchmark; IQR is 1-4% on every kernel row except n = 1e5, where both MLX columns are dispatch-bound and scatter by a third.float64 has no kernel, since Metal has no float64, and runs about 2-3x slower than scipy rather than faster. That is structural: scipy iterates with an early exit where a branch-free graph runs every trip count for every element.
Two things worth a reviewer's attention. Metal cannot execute on CI at all, so the tests comparing the kernel against the vectorized path only ever run on a developer machine, and the vectorized implementation stays the reference rather than being retired in the kernel's favor. And a malformed kernel source aborts the process rather than raising -- Metal compiles lazily on first call -- so the
try/exceptaround building the kernel guards construction only and cannot stand in for a runtime fallback.Part of #2095.