Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

JIT-compile lifted UDT ops on mixed operand types, and find conda's compiler on Windows - #652

Open
eriknw wants to merge 4 commits into
11a-py315-maintenancefrom
53-jit-improvements
Open

eriknw wants to merge 4 commits into
11a-py315-maintenancefrom
53-jit-improvements

Conversation

@eriknw

@eriknw eriknw commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Stacked on #636. The first three commits were written on the Tensor branch (#651) and
moved down here, since nothing in them is Tensor-specific: no line in this PR imports or
names the Tensor; the fourth fixes one of their tests. The diff is binary.py (one
elif), udt_codegen.py (the mixed C definitions), ss/jit_config.py and
test_ssjit.py.

  • ss.fix_jit_config finds a conda compiler on Windows. The conda repair looked for
    {the baked-in compiler's name, cc, clang, gcc} in $CONDA_PREFIX/bin only. Conda on
    Windows installs compilers in %CONDA_PREFIX%\Library\bin, with .exe names, and
    conda-forge's GraphBLAS there names the build machine's cl.exe, so the repair found
    nothing and kept a compiler the user's machine does not have. It now looks in both
    places, with and without .exe, and takes a Windows path's basename with either slash.
    Tested with a fake prefix; there was no Windows machine at hand. A compiler found this
    way that cannot take the baked-in flags fails fix_jit_config's probe compile, which
    leaves the JIT off, as before.

  • JIT C definitions for lifted ops on a scalar and a UDT, or on two UDT types. A
    lifted binary op (plus, minus, times, truediv, floordiv, min, max) whose operands are a
    scalar and an array or record UDT, in either order, or two UDTs of one layout with
    other element types, ran only through its Numba cfunc: the C definition was set for
    same-type pairs alone, with one C type for x, y and z. So a semiring such as
    plus_times[FP64, FP64[R]] ran SuiteSparse's generic kernel with a function call per
    multiply. Such an op now gets a C definition with a C type for each of z, x and y
    (udt_codegen._make_jit_c_mixed_definition). A scalar operand is read as (*x) for
    every element or leaf, and each operand is converted in C to the type the op computes
    in, as the cfunc converts it (udt._udt_op_types):

    binary.times[FP64, arr].jit_c_source    # arr: an FP64[13] UDT whose C name is c
    # 'void times_6_double_c (c *z, const double *x, const c *y) { for (int64_t i = 0 ;
    #  i < 13 ; i++) { z->v[i] = ((*x)) * (y->v[i]) ; } }'
    

    Before, jit_c_name and jit_c_source of these ops were None. Ops with no such form
    keep the cfunc: an array operand that broadcasts, an element not computed in the
    result's type (integer truediv), a record with an array leaf, and eq/ne on a mixed
    pair. The whole mxv with plus_times[FP64, FP64[R]] now compiles as one JIT kernel
    (burble: dot2, jit).

  • Test the mixed semiring's JIT kernel by burble. Burble names a JIT kernel only when
    it compiles or loads it, so a kernel loaded earlier in the process prints no
    (jit: ...). A UDT product has no kernel but the JIT one and the generic one, which
    burble does name, so the test checks that "generic" is absent with the JIT on and
    present with it off.

  • Vary the mixed JIT C name test's inputs, so its kernel runs on every SuiteSparse.
    test_udt_mixed_jit_c_name_is_its_own built its vectors from constant values.
    SuiteSparse before 10.5 makes a vector built from equal values iso, and an
    ewise_mult of iso vectors gives an iso result computed without the JIT kernel the
    test is about, so on those versions, which CI draws, it checked nothing. Its
    to_dense also raised there (17 CI jobs), from the iso array-UDT export bug fixed in
    Reject record UDT pairs whose leaf counts differ #591. The values now differ by index.

The C name is op_N_x_y, with N the length of x's C name (times_6_double_c above).
SuiteSparse finds a compiled kernel in memory by its op's name alone: the op's hash is
GB_jitifyer_hash(op_name), every UDT has the same type code, so the encodings match too,
and GB_jitifyer_lookup never compares the definition. A plain op_x_y is ambiguous:
plus on UDTs A and B would be plus_A_B, the name of the one-type plus on a UDT whose
C name is A_B, and whichever of the two ran second ran the other's kernel. Review
reproduced both orders: wrong values (1.5000000000000022 and 5e-323 for 11.5), and 16-byte
elements written into an array of 2-byte elements. With the length in it, a mixed name
reads back one way only, and it cannot equal a one-type name ({op}_{type}), since a C
type name never starts with a digit. It needs a UDT named like a pair, so it is rare, but
the failure is silent. test_udt_mixed_jit_c_name_is_its_own pins it.

Tests: test_udt_scalar_broadcast_skips_jit becomes
test_udt_scalar_broadcast_jit_routing (mixed arithmetic now gets a C definition, mixed
eq/ne still does not). New: test_udt_mixed_jit_c_source,
test_udt_mixed_jit_c_name_is_its_own, test_udt_mixed_jit_cfunc_parity,
test_udt_mixed_semiring_runs_jit and test_fix_from_conda_finds_windows_compilers. The
JIT tests need SuiteSparse:GraphBLAS 9 or later (the C definitions are set with its string
setters) and skip before it.

Checked locally (macOS arm64, SuiteSparse:GraphBLAS 10.5, clang):

  • Each op run with the JIT on and off, results compared byte for byte: 11 UDTs (arrays of
    float64, float32, int8, int32, uint8 and complex64, 2x3 arrays, aligned and packed
    records) against 7 scalar types in both orders, and 10 UDT pairs in both orders, 7 ops
    each, with NaN, inf, -0.0 and integer zeros in the data. All 878 ops that got a C
    definition give the cfunc's bytes. A second sweep by the reviewer (12 UDTs, 10 scalar
    types, nested records, integer min and max) got 1326 C definitions, all compiled, all
    equal to the cfunc apart from the padding bytes of nested aligned records, which the
    one-type path leaves differently too.
  • mxv and vxm with plus_times, min_minus and max_minus on [FP64, FP64[4]] and
    [FP64[4], FP64], with A and A.T: the JIT and generic kernels agree.
  • With no usable compiler, the mixed semiring gives the right values through the cfunc.
  • The full suite passes with --runslow, without Numba on both backends, and in CI's
    C-scalar pass; on Python 3.15, numpy 2.2, and SuiteSparse:GraphBLAS 8.2 (whose JIT works
    locally, where these tests skip), the only failures come from the local environments
    (test_version, and pandas 3.1 in the Python 3.15 one, see Reject record UDT pairs whose leaf counts differ #591).

For a reviewer:

  • Mixed names are longer. SuiteSparse refuses a name of 128 characters or more, and the
    code does not check that status, so jit_c_name and jit_c_source would show a
    definition SuiteSparse does not have while the op runs through the cfunc. The one-type
    path has the same gap; mixed names make it likelier.
  • Complex operands use C99 casts such as ((GxB_FC64_t) (x)), which MSVC's _Dcomplex
    would not accept. The one-type complex kernels already assume C99 complex.

@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.17476% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.57%. Comparing base (318fd8c) to head (1deed77).

Additional details and impacted files
@@                    Coverage Diff                    @@
##           11a-py315-maintenance     #652      +/-   ##
=========================================================
- Coverage                  98.83%   98.57%   -0.26%     
=========================================================
  Files                        104      104              
  Lines                      28923    29121     +198     
  Branches                    3417     3439      +22     
=========================================================
+ Hits                       28586    28707     +121     
- Misses                       215      270      +55     
- Partials                     122      144      +22     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…e names)

The conda repair looked for {baked-in name, cc, clang, gcc} in
$CONDA_PREFIX/bin only. Conda on Windows installs compilers in
%CONDA_PREFIX%\Library\bin, and their names end in .exe, so the repair
found nothing there and left SuiteSparse's build-time compiler, which a
user's machine does not have. It now looks in both places, with and
without .exe, and takes a Windows path's basename with either slash.
Tested with a fake prefix (no Windows machine at hand).
… types

A lifted binary op (plus, minus, times, truediv, floordiv, min, max)
whose operands are a scalar and an array or record UDT, in either
order, or two UDTs of one layout with other element types, ran only
through its Numba cfunc: the C definition was set for same-type pairs
alone, with one C type for x, y and z. So a semiring such as
plus_times[FP64, FP64[R]] ran SuiteSparse's generic kernel with a
function call per multiply.

Such an op now gets a C definition with a type for each of z, x and y
(udt_codegen._make_jit_c_mixed_definition), named op_N_x_y with N the
length of x's C name, so that no two ops share a name (SuiteSparse finds
a compiled kernel in memory by its op's name): a scalar operand is read
as (*x) for every element or leaf, and each operand is converted in C to
the type the op computes in, as the cfunc converts it
(udt._udt_op_types). Ops with no such form keep the cfunc: an array
operand that broadcasts, an element not computed in the result's type
(integer truediv), a record with an array leaf, and eq/ne on a mixed
pair.

The whole mxv with plus_times[FP64, FP64[R]] now compiles as one JIT
kernel (burble: dot2, jit). test_udt_scalar_broadcast_skips_jit becomes
test_udt_scalar_broadcast_jit_routing: mixed arithmetic now JITs, mixed
eq/ne still does not.
…present

The burble names a JIT kernel only when it compiles or loads it, so a
kernel loaded earlier in the process prints no '(jit: ...)'. A UDT
product has no kernel but the JIT one and the generic one, which burble
names, so the test checks that 'generic' is absent with the JIT on and
present with it off.
…SuiteSparse

test_udt_mixed_jit_c_name_is_its_own built its vectors from constant
values. SuiteSparse before 10.5 makes a vector built from equal values
iso, and an ewise_mult of iso vectors gives an iso result computed
without the JIT kernel the test is about, so on those versions it
checked nothing. On them its to_dense also raised "TypeError:
unsupported operand type(s) for +: 'int' and 'tuple'" (wave 2 CI, 17
jobs, e.g. 122633175811 and 122633175893), the iso array-UDT export
bug fixed in #591. The values now differ by index.
@eriknw
eriknw force-pushed the 53-jit-improvements branch from 52836c1 to 1deed77 Compare October 29, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant