Skip to content

[Draft, for CI] FaCe - #2448

Draft
ThrudPrimrose wants to merge 946 commits into
mainfrom
FaCe
Draft

ThrudPrimrose wants to merge 946 commits into
mainfrom
FaCe

Conversation

@ThrudPrimrose

@ThrudPrimrose ThrudPrimrose commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator

Draft PR to run CI on the FaCe branch, based on main. Not for review yet.

@ThrudPrimrose
ThrudPrimrose changed the base branch from ci-fix to main July 21, 2026 10:26
@ThrudPrimrose ThrudPrimrose reopened this Jul 21, 2026
tbennun and others added 27 commits September 30, 2026 12:19
- ``Range.size``: a dimension that is a single index of an integer, a symbol or a sum of those (with unit step and
  tile size) has size one, which ``ceiling((i + 1 - i) / 1)`` always evaluates to; it is returned without building
  and simplifying the expression.
- ``Range.free_symbols``: symbols and numbers are handled directly instead of through ``symlist``, and the result is
  updated in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scalar-ness of an input memlet (which computes its number of elements) is only needed to infer the type of a
connector that has none, so it is no longer computed for the other connectors. Code generation infers connector
types three times, and the later passes find almost all connectors typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
``symbols_defined_at_state`` walks from the SDFG down to the state and, at each level, adds the symbols the control
flow region defines (for a loop, inferring the type of its variable) and those of the inter-state edges leading to the
next block. For states nested in many regions, this redid the same work for every state. Both parts only depend on the
blocks around them, so the resolver now records these updates per region (and per region and block) and replays them,
in the same order, for the other states. The resulting symbols and their order are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
``cfg_id`` caches the position of a CFG in the CFG list, but the first query of each CFG searched the list linearly,
which is quadratic when all regions of a large SDFG are queried after ``reset_cfg_list`` (e.g., by the reachability
analyses). The reset now records the position of each CFG (its first occurrence) while propagating the new list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
to_json time is dominated by the traversal (json.dumps is <0.5s), mostly by printing symbolic expressions and
property bookkeeping. Output is unchanged (identical JSON on all tested SDFGs). On a test SDFG with ~4k states and
~8.5k control flow regions, to_json takes 10.8s instead of 49.6s; on a smaller SDFG with large states, 40s instead
of 89s.

- serialize_symbolic: print integers and lone symbols directly, and cache the serialization of other expressions
  keyed by the expression and the dtypes its DaCe symbols are printed with (SymPy equality ignores symbol dtypes).
  Cache the serializer keyword arguments of symbols.
- serialization_symbol_dtypes: push a lazily filtered view of the authoritative dtypes instead of filtering a copy
  of all SDFG symbols for every node and edge; edges look names up in both endpoints' scopes through a ChainMap
  instead of merging copies.
- CodeProperty / CodeBlock serialization: cache the parse/unparse normalization of code strings, and compare with
  None without unparsing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operands are read through broadcast_together (unidirectional), SPREAD is the NumPy rule with an
inserted extent-1 axis, and a one-element operand is a scalar of any rank. Both nodes validate
shapes before expansion. Tests are parametrized and cover scalars, slices with offsets, symbolic
extents, strides and type conversion; numpy.broadcast_to is listed in the NumPy support doc.
Cases that reused one SDFG name in a process loaded the library a previous
case had built, which gave wrong results and crashes.
networkx 3.6 no longer includes the start node (mapped to itself) in the
result of immediate_dominators. DaCe indexes the dominator of every block
and walks the dominator tree until a block is its own dominator, so code
generation failed with a KeyError (block_parent_tree, called from
determine_allocation_lifetime) and loop detection, control flow raising and
the symbol/scalar analyses broke the same way.

Add sdutil.immediate_dominators, which always maps the start node to
itself, and use it at every dominator and postdominator call site. Drop the
networkx<=3.5 pin from pyproject.toml and lock networkx 3.4.2 on Python
3.10, 3.6.1 on 3.11 and 3.7 on 3.12 and later, so CI exercises both the old
and the new behaviour.
An integral base truncates 1 / a**|b| toward zero, which is non-zero only for a base of +-1 and 0 for
a zero base; a floating-point or complex base gets the exact reciprocal; the typed pow and the
global ipow share one helper for the integral case. ipow in sympy refuses any negative constant
exponent, integer-valued or not. Tests compile the runtime under UBSan and compare with NumPy, and
cover the unknown-sign symbolic exponent.
A register array now carries stack_vla (Auto, Stack, Heap). The
ResolveStackAllocation pass, run before code generation, resolves Auto:
constant sizes below 2048 elements and max_stack_array_size bytes go on the
stack, symbolic sizes on the heap. An explicit Stack keeps a symbolic size on
the stack as a variable-length array, whose bound is Max(1, size) because a
zero-length VLA is undefined. Lifetimes that outlive the block and split
declarations still use the heap.
ipow.eval folds whenever base and exponent are numbers, integral or not; only
a negative numeric exponent is refused. Both test files call every test from
their __main__ block.
Conflicts take main's implementation where main already solves the problem:
DefinedNames, typed DACE_* environment overrides, LoopToMap (#2502), map
parameter type inference and the compiler driver. FaCe keeps the
distributed_compile error broadcast and Python's % (PyMod).

Removed from FaCe: the PMI/SIGCHLD build-environment workaround, the
OMP_MAX_THREADS reserved symbol, the OpenMP simd map marking (Map.omp_simd,
MarkSIMDMaps, compiler.cpu.simd_maps), compiler.cuda.emit_synchronization
with its __DACE_NO_SYNC override, and the library nodes no dace-fortran
application uses (BLAS Copy, Swap, Trsv, Trmv, Trsm, Trmm; LAPACK Geqrf,
Orgqr, Potrs). Axpy is main's again.
Every shift reused one SDFG name, so a later case loaded the library an
earlier shift had built and compared against the wrong rotation.
FaCe now carries #2515's implementation instead of its own copies.
…aring the size

A shape given by a constant whose symbol has another dtype than the constant stayed
symbolic after the constants check, so the size comparison raised on a sympy Relational
(Polybench doitgen).
…main's config tests

The guard rejected every non-str replacement, so a plain symbol rename from safe_replace
left the old key in place (main's replace_symbol_types_test). The env-override tests went
with the config.py change that main's #2602 replaced.
SpecializeMatMul matches on the squeezed operand sizes, but Gemm.validate, the GEMM
code generation and Transpose re-read the raw subset. An operand such as the
(NQ, 1, NP) reshape in doitgen reached Gemm and was rejected as not a matrix, and an
(N, 1) or (1, N) array was rejected by Transpose because it was squeezed to rank 1.

blas_helpers.matrix_view returns the raw subset when it is already 2D and the squeezed
one otherwise, together with the dimensions it kept, so sizes and strides cannot
disagree. matmul, gemm and transpose all read operands through it.
…te names

The walk moves into PowerRelaxer, the sign facts of an SDFG become a frozen SignFacts that
is passed down instead of saved and restored on the pass, and the provers are public
functions that the tests call directly. Behaviour is unchanged.
… NORM2 and CSHIFT

AllNode, AnyNode and CountLibraryNode reduce a mask, every non-zero value of which is true, with a Reduce node,
whole-array or along one dim; All and Any also expand to a short-circuiting loop. ArgMin and ArgMax find the
first (or, with back, last) index of the extreme element among those an optional mask selects. Norm2 sums squares
and takes the root; CShift rotates along one dim. A nested array keeps the strides of the container its memlet
reads.
…nsional, axis-batched, FFTW3 and hipFFT FFTs

Scal, Symv, Symm and Syrk get a pure expansion and an OpenBLAS/MKL one, and Ger an OpenBLAS/MKL one (?geru for
complex data, as Ger does not conjugate). A routine that updates an operand in place gets a copy of its input
operand in dataflow first, and reads matrices in their own layout: row- or column-major with their leading
dimensions, else the pure expansion. Dot takes conjugate (?dotc) and initializes a device result on the device.

FFT and IFFT take axis, a batched one-dimensional transform. The pure expansion transforms N-dimensional arrays of
any layout; cuFFT, hipFFT and FFTW3 plan C-ordered ones, and FFTW3 applies the normalization factor. MakePlan2d and
MakePlan3d no longer get a batch count, which they do not take. numpy.fft.fft along any axis and numpy.fft.fftn
lower to these nodes. General CI installs FFTW3.
…eads after it

ScalarWriteShadowScopes walked a read's dominators for a writing state only, so a read after an if/else that
writes the scalar in both arms found no write, counted as live-in, and kept every write in one scope. CloudSC's
ZFAC/ZFACI stayed one container shared by three JL loops, and none of them mapped. Such a conditional now
dominates the read; the final write of each branch joins one scope, so all arms write the version the read uses.

(cherry picked from commit 9673588)
…e element they write

The Python path matched only a binary operator, so ``a = max(a, x)`` and ``a = a or x`` (Fortran's MAX and
.OR. accumulations) stayed plain writes and their loops stayed sequential. It also took any input connector
as the accumulator, without the same-element check of the C++ path; an update now has to read the element
it writes.

(cherry picked from commit 96786cf)
isolate_tasklet named an undefined sdfg, so an update whose accumulator is written earlier in the same state
raised NameError before the state fission it needs.

(cherry picked from commit 196eb65)
…mbol uses

The ordered walk handled a conditional's out-edges before the branches below it, so a symbol both arms
assign and the edge after the conditional reads (CloudSC's ZFAC once fissioned per loop) counted as read
before assignment. The None that results was also or-ed into a set before being checked, raising TypeError
instead of refusing. A block now counts its own reads only (state dataflow, branch conditions, loop
init/condition/update); its region's nested reads come with the nested blocks.

(cherry picked from commit 437238d)
# Conflicts:
#	tests/transformations/interstate/loop_to_map_test.py
# Conflicts:
#	dace/codegen/targets/framecode.py
#	dace/sdfg/state.py
# Conflicts:
#	dace/libraries/standard/nodes/__init__.py
#	dace/libraries/standard/nodes/argminmax.py
#	dace/libraries/standard/nodes/cshift.py
#	dace/libraries/standard/nodes/norm2.py
#	tests/library/argminmax_test.py
#	tests/library/cshift_test.py
# Conflicts:
#	dace/libraries/mpi/nodes/abort.py
#	dace/libraries/mpi/nodes/barrier.py
#	dace/libraries/mpi/nodes/bcast.py
#	dace/libraries/mpi/nodes/comm_f2c.py
#	dace/libraries/mpi/nodes/comm_rank.py
#	dace/libraries/mpi/nodes/comm_size.py
#	dace/libraries/mpi/nodes/comm_split.py
#	dace/libraries/mpi/nodes/gatherv.py
# Conflicts:
#	.github/workflows/general-ci.yml
#	dace/frontend/python/replacements/fft.py
#	dace/libraries/blas/blas_helpers.py
#	dace/libraries/blas/nodes/ger.py
#	dace/libraries/blas/nodes/scal.py
#	dace/libraries/blas/nodes/symm.py
#	dace/libraries/blas/nodes/symv.py
#	dace/libraries/blas/nodes/syrk.py
#	dace/libraries/fft/algorithms/dft.py
#	dace/libraries/fft/nodes/fft.py
#	tests/library/fft_test.py
A copy, transpose or reduction has a single tensor operand and its own library node, so lifting it to
an Einsum only turned a Reduce into an Einsum that expands back into one. The lift tests now assert the
reduction stays a Reduce, as on extended.
s275's g = aa[0, i] guards the aa recurrence: demoted, its read merges with the recurrence's
writes and the RAW hint is lost. A guard read from data nothing writes (CloudSC's
iphase[jm]) demotes like any value, so the loops it guards still fuse. Ported from extended.
The s275 RAW hint is proven from the element accesses without it, and a guard kept as a symbol
splits the loops it guards.
# Conflicts:
#	dace/libraries/standard/nodes/__init__.py
#	dace/libraries/standard/nodes/cshift.py
#	dace/sdfg/sdfg.py
#	dace/sdfg/utils.py
#	dace/transformation/passes/loop_local_memory_reduction.py
#	tests/library/cshift_test.py
#	tests/transformations/map_fusion_vertical_test.py
…es of PRs #2689 and #2690

FaCe's own versions carried staging idempotence, a regex scan for control-flow reads and a longer UniqueLoopIterators;
the PR versions do the same with main's APIs.
…e writes

It compared only the subsets, so out = max(x, a) writing A[1] picked x when x read X[1].
It kept a copy of every symbol for every state it resolved: QE h_psi code generation held 14478 states of
about 12500 symbols each, 15 GB, more than a CI runner has. Callers resolve one state's nodes together, so the
last state per SDFG keeps the reuse; h_psi now generates the identical code in 1.4 GB.

This branch has not been deployed

No deployments
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.

2 participants