Skip to content

Core/template compile - #298

Open
jlee-kitware wants to merge 7 commits into
Kitware:mainfrom
jlee-kitware:core/template-compile
Open

jlee-kitware wants to merge 7 commits into
Kitware:mainfrom
jlee-kitware:core/template-compile

Conversation

@jlee-kitware

Copy link
Copy Markdown
Collaborator

Template methods: held templates as arguments, fewer copied parameters, methods resolved on demand; explicit CUDA contractions

Problem

  • A template method could not pass a template it holds to another held template's method (self.values.scaled(self.mesh, i)). The callee was not resolved yet, and the rewrite raised a KeyError for its resolved name.
  • Every resolved method took the whole template tree's synthetic parameters, and each inlined call copied all of them into locals. A kernel reading an order-2 field through a view that holds it came to 18k IR nodes, against 5.5k for the field read directly.
  • Every method of every template was resolved for every kernel, whether called or not.
  • With --fmad=true, NVRTC fused two inlined copies of one function differently depending on the code around them. Two kernels sharing a computation could differ in the last bit: streamlines with and without the locator's previous-cell try, on curved wedges.

Approach

  • A method passed a held template is resolved when that call is reached.
  • A fixpoint over the calls between resolved methods keeps in each method only the synthetic parameters it reads, itself or through its callees, and drops the rest from its signature and its calls.
  • Methods are resolved from the kernel's calls outward. A method no kernel reaches is never resolved, so one that would not resolve is an error only if a kernel calls it.
  • The CUDA generator writes the contractions the language contract permits as __fmaf_rn/__fma_rn: a + or - with a product operand, the left product when both are. Every other product is __fmul_rn/__dmul_rn, and NVRTC compiles with --fmad=false, so one expression rounds the same wherever it is inlined. This is the part that touches the floating-point contract; the numerical-semantics, language-contract, backend and vendor-workaround docs are updated with the reasoning.

Effect
First compile with a cold disk cache on a DGX Spark, contouring an order-2 H1 field on a small grid: 6.1 s to 3.5 s on CPU, and 10.5 to 12.1 s down to 3.2 to 4.6 s on CUDA. The IR for a kernel reading an order-2 field drops from 5.5k to 1.9k nodes. Warm-cache launches and the speed of compiled kernels (checked on an LBM solver step) are unchanged.

Verified
tack-core and tack-vis on CPU and CUDA, including a new test passing a held template to a held template's method, and the curved-wedge streamline test matching bit for bit.

The callee is resolved on demand when a call binding a template into it is
reached before its own turn.
Each inlined call copied the whole template tree's arrays and runtime scalars;
a fixpoint over the calls between resolved methods keeps what each needs.
A view's other methods cost nothing, and one that would not resolve is never
reached.
NVRTC's --fmad=true fused differently in two kernels inlining the same
function, depending on the code around it, so streamlines with and without the
locator's previous-cell try differed in the last bit on curved wedges. The
generator now chooses: a sum or difference with a product operand is
__fmaf_rn/__fma_rn, every other product __fmul_rn/__dmul_rn, and NVRTC runs
with --fmad=false.
@berkgeveci berkgeveci mentioned this pull request Nov 4, 2026
4 tasks
@berkgeveci

Copy link
Copy Markdown
Collaborator

The failing shares (py3.11/3.12/3.13 and Metal 3/4) are one cause: the host C++ preludes this PR extends with

static inline float __fmaf_rn(float a, float b, float c) { return fmaf(a, b, c); }
static inline double __fma_rn(double a, double b, double c) { return fma(a, b, c); }

call fmaf/fma, but three of the six preludes don't include <cmath>, so clang++ stops at use of undeclared identifier 'fmaf':

  • test_atomic_contract.py: test_cuda_hip_wide_atomic_syntax (all 18 cases)
  • test_reduction_semantics.py: test_native_reduction_cpp_syntax (all 6; its preamble includes only <stdint.h>)
  • test_gpu_identifiers.py: test_generated_cpp_compiles_and_executes_collisions (every *-host case; its prelude includes <cstdio> but not <cmath>)

The other three (test_float_division.py, test_float_literals.py, test_float_semantics.py) already include <cmath> and pass. Adding #include <cmath> to those three preludes (or std::fma/std::fmaf with the include) should clear all of it.

While you're in there: the branch is 17 commits behind main, and main now requires an SPDX header on every source file (#292). This PR adds no new files, so a merge of main is all it needs.

@berkgeveci berkgeveci added this to the 0.7 milestone Nov 4, 2026
jlee-kitware added a commit to jlee-kitware/tack that referenced this pull request Nov 4, 2026
# Conflicts:
#	packages/tack-core/tests/test_atomic_contract.py
#	packages/tack-core/tests/test_float_division.py
#	packages/tack-core/tests/test_float_literals.py
#	packages/tack-core/tests/test_float_semantics.py
#	packages/tack-core/tests/test_gpu_identifiers.py
#	packages/tack-core/tests/test_reduction_semantics.py

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