Repository navigation
Add a Python and cuTile backend and lowering pipeline - #2708
Open
Spielopoly wants to merge 106 commits into
Open
Spielopoly wants to merge 106 commits into
Spielopoly wants to merge 106 commits into
Conversation
Port the Python code generation backend and cuTile Python target from the tile-ir branch onto the multi-dim-tileops base. New files: - dace/codegen/py/: Complete Python backend (target, framecode, cutile_target, control_flow, compiled_sdfg, prettycode, utils) - tests/codegen/python_backend/: 16 test files (426 tests) Modified files: - dace/dtypes.py: BackendLanguage enum, CuTile_Tile/CuTile/GPU_Warp types, PYTHON_TYPES/NUMPY_TYPES dicts, gpuStream_t, can_access/ can_allocate updates, struct.emit_python_definition() - dace/sdfg/sdfg.py: backend property, _update_code() helper (backwards-compatible, defaults to C++), compile() Python path - dace/codegen/codegen.py: Python backend dispatch via match statement - dace/codegen/dispatcher.py: PythonCodeIOStream support, TargetCodeGeneratorBase type hints, GPUStream enum - dace/codegen/target.py: Split TargetCodeGenerator into TargetCodeGeneratorBase (no registry) + TargetCodeGenerator (C++ with registry and CMake support)
Integrate cuTile library node expansions with the VectorizeCPUMultiDim pipeline and Python backend codegen. This enables end-to-end GPU execution via cuda.tile. Bug fixes: - Fix connector name mismatches in all 9 cutile expansion classes (tile_load, tile_store, tile_binop, tile_unop, tile_mask_gen, tile_merge, tile_gather, tile_scatter, tile_reduce) to match library node connectors (e.g., _src/_dst instead of __src/__output) - Add CUTILE to _VALID_ISAS in VectorizeCPUMultiDim - Add Python target extension loop in codegen.py to instantiate CuTilePythonCodeGen (was never instantiated, dispatcher registrations never happened) - Fix PythonCodeGen double-instantiation causing 'Schedule already mapped' errors - Fix scope connector resolution in cutile_target.py _generate_Tasklet (MapEntry OUT_A -> actual array name A, MapExit IN_C -> array C) - Add output pre-binding for store tasklets (ct.scatter needs _dst bound to global array before tasklet body executes) Tests: - 60 new integration tests (test_cutile_integration.py): full pipeline from SDFG -> vectorize -> cutile expand -> Python backend -> GPU run - 30 unit tests (test_cutile_tasklet_scope_connectors.py): scope connector resolution - Updated 28 existing structural tests for new connector names
Introduce PythonTimerProvider, a timing instrumentation provider for the Python codegen backend. It uses a compiled C++ shared library (_dace_timer.so) loaded via ctypes for high-resolution timestamps, falling back to time.perf_counter_ns when compilation is unavailable. Supported instrumentation levels: - SDFG-level (whole program) - State-level (individual states) - Scope-level (maps / nested SDFGs) Reports are written in Chrome Tracing JSON format, compatible with sdfg.get_latest_report() and the existing C++ backend report API. PythonCompiledSDFG gains _cfunc, _libhandle, and do_not_execute attributes for compatibility with CompiledSDFGProfiler (npbench). The provider is swapped in automatically by codegen.py when sdfg.backend == BackendLanguage.Python, replacing TimerProvider. New files: - dace/codegen/instrumentation/_dace_timer.cpp - dace/codegen/instrumentation/py_timer.py - tests/codegen/python_backend/test_py_instrumentation.py Modified: - dace/codegen/codegen.py (provider swap logic) - dace/codegen/py/framecode.py (SDFG/state hooks) - dace/codegen/py/python_target.py (scope entry/exit hooks) - dace/codegen/py/compiled_sdfg.py (profiler compatibility)
Rework PythonTimerProvider to reach parity with the C++ TimerProvider while
removing accidental complexity, and register it cleanly instead of overriding.
Provider selection
- Add InstrumentationType.PythonTimer and register PythonTimerProvider against
it via @registry.autoregister_params (no collision with the C++ Timer type).
- Drop the backend-aware get_provider_mapping(backend=...) override and the
post-hoc isinstance swap in codegen.py. Select PythonTimer explicitly on the
element to time (e.g. sdfg.instrument = InstrumentationType.PythonTimer).
Timer source
- Delete the compiled C++ shared library (_dace_timer.cpp) and its ctypes/
subprocess machinery; use time.perf_counter_ns()//1000 unconditionally
(identical microsecond resolution).
Emission & per-invocation semantics
- Emit timer/event-list/import setup once at module scope for the top-level
SDFG only (no redundant per-SDFG emission).
- Honor instrumentation.report_each_invocation:
* true (default): clear __dace_perf_events per call + save a fresh report
each call (parity with C++ report.reset() + per-call save). Fixes a bug
where events accumulated across invocations.
* false: accumulate across calls and write one report from the generated
__dace_exit_<name>() function (run by PythonCompiledSDFG.finalize()/__del__),
the analog of C++ __dace_exit. os/json/time are imported once at module
scope so the exit-function body does no imports � avoids an __import__-hook
failure at interpreter shutdown (sys.meta_path is None).
Granularity
- Implement node-level (tasklet) timing for the plain Python target.
- Reject node-level instrumentation inside cuTile kernels (RuntimeError).
- Make cuTile kernels timeable: always emit a stream synchronize() after
ct.launch, and wrap launch+sync with scope timing (pass the exit node to
on_scope_exit so tbegin/tend ids match).
Misc
- Add TODOs for the still-missing on_sdfg_init_*/on_sdfg_exit_* hook dispatch
in the Python frame code (and where false-mode should move once they exist).
- Tests: end-to-end runtime coverage (SDFG/state/map/node, per-invocation
reset, symbolic + non-divisible sizes, report schema, frozen sdfgHash),
real-GPU cuTile runtime tests, cuTile structural checks, report_each_invocation
(true/false), and provider registration.
2. Added: TODOs for init/exit hook dispatch
Three TODOs now mark the missing on_sdfg_init_*/on_sdfg_exit_* dispatch and tie it to the false-mode wiring:
- framecode.py generate_code (next to the on_sdfg_begin dispatch).
- framecode.py _build_lifecycle_functions (where __dace_init/__dace_exit are built � the natural dispatch point).
- py_timer.py on_sdfg_end (noting the false-mode save should move from reaching into codegen._exitcode int hooks exist).
Tests & docs
- Updated the two false-mode tests in test_py_instrumentation_2.py: E2 now asserts the writer lives in __dace_exit_<name>() (no atexit); E4 calls csdfg.finalize() and checks the accumulated report.
- Both instrumentation test files: 80 passed; full python_backend suite: 547 passed, 1 skipped, 9 xfailed noise.
- Updated the design doc (TileIR/design/PYTHON_INSTRUMENTATION_DESIGN.md §4, §8, §9), CLAUDE.md (and corrected its stale doc path), and the session report.
One note carried into the docs as a known limitation: at interpreter shutdown specifically, the flush ridert (true of any finalizer doing I/O); finalize() and mid-program GC are deterministic.
� Sautéed for 11m 23s
� recap: Goal: refactor the DaCe Python-backend timing instrumentation to match the C++ backend. The provider now uses a dedicated PythonTimer type, and false-mode reports save via the SDFG finalizer; all 547 backend tests pass. Next: confirm with you whether anything else is needed.
� Give me a good commit message for both TileIR and dace
Let me check what's actually changed in each repo so the messages are accurate.
Ran 2 shell commands
t changed in each.
dace repo
refactor(codegen): overhaul Python-backend timing instrumentation
Rework PythonTimerProvider to reach parity with the C++ TimerProvider while
removing accidental complexity, and register it cleanly instead of overriding.
Provider selection
- Add InstrumentationType.PythonTimer and register PythonTimerProvider against
it via @registry.autoregister_params (no collision with the C++ Timer type).
- Drop the backend-aware get_provider_mapping(backend=...) override and the
post-hoc isinstance swap in codegen.py. Select PythonTimer explicitly on the
element to time (e.g. sdfg.instrument = InstrumentationType.PythonTimer).
Timer source
- Delete the compiled C++ shared library (_dace_timer.cpp) and its ctypes/
subprocess machinery; use time.perf_counter_ns()//1000 unconditionally
(identical microsecond resolution).
Emission & per-invocation semantics
resh report
each call (parity with C++ report.reset() + per-call save). Fixes a bug
where events accumulated across invocations.
* false: accumulate across calls and write one report from the generated
__dace_exit_<name>() function (run by PythonCompiledSDFG.finalize()/__del__),
the analog of C++ __dace_exit. os/json/time are imported once at module
scope so the exit-function body does no imports � avoids an __import__-hook
failure at interpreter shutdown (sys.meta_path is None).
Granularity
- Implement node-level (tasklet) timing for the plain Python target.
- Reject node-level instrumentation inside cuTile kernels (RuntimeError).
- Make cuTile kernels timeable: always emit a stream synchronize() after
ct.launch, and wrap launch+sync with scope timing (pass the exit node to
on_scope_exit so tbegin/tend ids match).
Misc
- Add TODOs for the still-missing on_sdfg_init_*/on_sdfg_exit_* hook dispatch
in the Python frame code (and where false-mode should move once they exist).
- Tests: end-to-end runtime coverage (SDFG/state/map/node, per-invocation
reset, symbolic + non-divisible sizes, report schema, frozen sdfgHash),
real-GPU cuTile runtime tests, cuTile structural checks, report_each_invocation
(true/false), and provider registration.
This reverts commit a65684a.
…trided loads, permutations, scalars and symbols
…ons, improve masking handling
…edError from ExpandTileMergeCutile
…(True) == hash(1), which will cause the function to return whichever was passed first instead of the actual value
…ementations casted to ints, which is actually unnecessary, so it's removed now.
# Conflicts: # dace/codegen/py/cutile_target.py # dace/transformation/passes/vectorization/vectorize_cutile.py
Code-review fixes for the vectorization / validation passes: - clamp_propagated_oob_memlets: gate the clamp so only tileops-adjacent or allow_oob-marked memlets are narrowed. Genuine unmasked OOB memlets are left untouched (with a diagnostic warning) so validation rejects them loudly instead of silently compiling a truncated footprint. Also recompute memlet.volume after narrowing, clamp other_subset under the same gating, and treat NestedSDFG boundary edges feeding tileops nodes (nest_map_bodies=True descent) as tileops-adjacent (mark allow_oob, never narrow below W). - TileIota cutile expansion: derive the ct.arange lane-array dtype from the _dst descriptor (int64 -> ct.int64) so descriptor and runtime tile dtype agree. - validation.py: InvalidSDFGNodeError/InvalidSDFGEdgeError __str__ stale-id branches now fall through to the common suffix logic so the "saved for inspection" path is not skipped; fix the locinfo_dst formatting branch in InvalidSDFGInterstateEdgeError.__str__. - Document the 2b/3b steps in the cutile_lowering module docstring and the propagate_subset allow_oob-drop caveat in mark_tile_op_memlets_allow_oob. Regression tests: genuine-OOB-still-rejected, volume recomputation, other_subset clamping, NSDFG boundary marking (+ GPU e2e nest_map_bodies tail), propagate_subset flag-drop caveat, TileIota int64 codegen (+ GPU numeric), and validation error __str__ suffix rendering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Majors:
1. compiled_sdfg._marshal_arguments: raise TypeError on dtype-mismatched
ndarray Scalar args (silent copy would drop writeback) and on lossy
scalar-value coercion (3.9 -> int no longer silently truncates).
2. cutile_target._collect_free_symbols: subtract scope-bound map params
(including nested-map params) before the runtime_defined filter, so a
map param colliding with a host loop var / interstate key is not emitted
as an undefined launch arg.
3. cutile_target._cast_literal_arm: don't cast a fractional float literal to
an integer tile dtype (silent truncation); handle signed literals
(UnaryOp USub/UAdd) like Constants.
4. cutile_target: in-kernel float-scalar tile binding now mirrors the launch
site (float-scalar 0-d tile load only when the scalar is not a kernel
output); float Scalar in input∩output raises NotImplementedError.
5. cutile_target.generate_scope: float64 symbols staged through device memory
(1-elem array + 0-d tile load) like float Scalars, avoiding f32 precision
loss on by-value floats.
Minors:
6. Deleted unreachable cpu_to_gpu Scalar branch in _emit_cross_storage_copy.
7. Deleted dead _scalar_bridge_load_expr; clarified the 0-d comment (concerns
0-d ARRAYS, not shape=() loads of 1-element arrays).
8. compiled_sdfg.__call__ fast path: only swallow argname-resolution failure;
binding errors propagate; positional Scalars still marshalled.
9. cholesky ExpandCholeskyCuPy: filter strides by kept (non-squeezed) dims,
fixing add_array on batched/leading-singleton inputs.
10. _CuTileTaskletRewriter: taint pass so ternaries conditioned on a
tasklet-local tile value are rewritten to ct.where.
11. visit_Call: probed on GPU that ct.* math accepts plain scalars; kept the
unconditional rewrite, documented the probe.
Suggestions:
12. ITE: coerce a host condition into device arms' namespace before np.where.
13. Launch guard: min((...)) > 0 skips negative-trip grids, not just zeros.
14. Documented the scalar branches in _emit_cross_storage_copy.
15. _bind_positional raises TypeError on excess positional args.
16. Strengthened the near-vacuous broadcast-assignment marshalling assert.
Regression tests added/extended: argument marshalling (dtype mismatch, lossy
coerce, view writeback, excess/duplicate positional), tasklet rewrite
(signed/fractional literals, tainted-local ternaries), scope codegen
(colliding map params, float in/out guard, float64 symbol staging, launch
guard), ITE host/device coercion, cholesky batched-stride expansion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ution) Resolved every conflict — git-flagged and silent — in favour of vectorized-tile-ir/extended, discarding the npbench-cutile side: - 6 content conflicts (scalar.h, expand_nested_sdfg_inputs.py + test, split_tasklets.py + test, convert_tasklets_to_tile_ops.py, insert_tile_load_store.py) taken wholesale from VTI via `-X ours`; NBC's conflicting hunks (ranged other_subset guard, >1 masked-assign guard, sanitize_transient_name_hint import, TileIota lane-id block) dropped. - Modify/delete tile_carried_reduction.py: kept VTI's deletion. - NBC's non-conflicting additions (Python cuTile backend, cutile lowering, device-resident CuPy BLAS, offset recovery) merged in. Known consequences of the VTI-wins policy (see the 2026-07-06 overlap report §3.3): SC-A (VTI dropped "CUTILE" from _VALID_ISAS -> NBC's cuTile front door raises NotImplementedError at construction) and the SC-1/2/3/5 silent semantic gaps remain unaddressed by design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Cg7RaMAnJpH9EC8yAdNk5
Post-merge fixes on top of the -X ours merge (0525049): 1. NameError fallout of `-X ours` (it wins conflicting hunks but keeps NBC's disjoint hunks, leaving dangling refs). Restored both files to pure vectorized-tile-ir (1e20822): - insert_tile_load_store.py: NBC's 5 sanitize_transient_name_hint() callsites had merged in while the import was dropped -> NameError on every tile-read staging. Confirmed via test_return_array_naming. - convert_tasklets_to_tile_ops.py: NBC's `add_edge(iota, ...)` line merged onto VTI's `tasklet` builder -> undefined `iota`. Verified: test_k0_remainder (16) + lower_reduction_wcr (5) pass. 2. SC-A: re-added "CUTILE" to _VALID_ISAS + the constructor Literal in vectorize_multi_dim.py (VTI had dropped it, renaming the slot to the C++ half2 "CUDA" backend). CUTILE stays a valid ISA alongside CUDA but is exempt from the half2 even-width constraint and does not force device=GPU at stage 1 (VectorizeCuTile's apply_gpu_transformations handles GPU-ification separately, matching base behaviour). Verified: NPBench dace_cutile atax validates SUCCESS on all 8 widths. Known remaining (not in scope of these two fixes): - Return-array kernels: pure VTI lacks NBC's sanitize, so a `__return` output collides ('__return_tile_out' reserved-name AssertionError). NBC's dropped fix was actually needed here. - Pre-existing VTI circular import (vectorize_multi_dim.py:83 -> interstate), order-dependent; breaks standalone collection of some test modules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Cg7RaMAnJpH9EC8yAdNk5
Both follow-ups from the merge validation:
1. Return-array naming (insert_tile_load_store.py): ported NBC's
sanitize_transient_name_hint — restored the module import (dropped by
the -X ours conflict resolution) and re-wrapped the 5 name_hint sites.
A `__return`-output kernel previously hit
`AssertionError: '__return_tile_out' collides with the reserved __...`
because pure VTI minted `f"{an.data}_tile_out"` verbatim. The sanitizer
rewrites a leading `__return` to `tile_return`; behaviour-preserving for
all other names. Verified: test_return_array_naming 2/2 pass.
2. Circular import (vectorize_multi_dim.py): moved the three module-level
`dace.transformation.interstate` imports (InlineSDFG, InlineMultistateSDFG,
LoopToMap, RefineNestedAccess, ExpandNestedSDFGInputs) to their function
use-sites. A module-level import re-entered the partially-initialized
interstate package on some import orders, breaking standalone collection
of e.g. masked_write_it_lowering_test. (Pre-existing on VTI — the earlier
"circular import resolved" note was wrong; VTI had moved it into this new
split-out file.) Verified: that module now collects 10 tests.
Note: masked_write's AVX512-parametrized subtests SIGILL on this box —
the CPU has only avx2 (no AVX-512). Pre-existing test-vs-hardware mismatch,
unrelated to the merge; SCALAR variants pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Cg7RaMAnJpH9EC8yAdNk5
VTI keeps dtype casts rather than stripping them (it deleted NBC's RemoveFPTypeCasts/RemoveIntTypeCasts), but the cuTile backend had no cast lowering, so a surviving cast broke it two ways. Following VTI's keep-casts design, teach the cuTile backend to lower casts: - tile_unop.py ExpandTileUnopCutile: a TileUnop whose op is a dtype name now lowers to `ct.astype(x, ct.<dtype>)` (mirrors the pure path's dace::<dtype>(x) and the existing `not` -> ct.astype(_a, ct.bool_)). Fixes the tiled-cast `KeyError: 'float64'` (deriche, hdiff, mlp). - cutile_target.py _CuTileTaskletRewriter: rewrite a module-qualified `dace.<dtype>(x)` / `np.<dtype>(x)` cast call in a kernel tasklet to `ct.astype(x, ct.<dtype>)`. - python_target.py: rewrite `dace.<dtype>(x)` -> `numpy.<dtype>(x)` in host Python tasklet bodies (the generated module imports numpy, not dace). Fixes the `name 'dace' is not defined` scalar-cast failure (adi's np.float64(N)). Verified: deriche/hdiff/mlp/adi validate under NPBench dace_cutile -p S, matching the pre-merge npbench-cutile baseline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Cg7RaMAnJpH9EC8yAdNk5
…over cloudsc, icon) The TileStore "full-tile write" validation rejected genuine full-tile stores whose memlet extent was e.g. ``jl - jl + 512`` -- two same-name ``jl`` symbols carrying different sympy assumptions (a nonnegative tile base vs. the loop's plain ``jl``), which blocks ``jl - jl`` from cancelling, so ``symbolic.simplify`` left it non-zero and the check wrongly raised the design-6.7 NotImplementedError. These are masked in-place 2-D elementwise updates (``ptend_q[jk, jl] = ...``) tiled 1-D over the inner loop, so the store into the 2-D array is a legitimate ``(1, W)`` full-tile write. VTI's design (full-tile writes pass, partial writes deferred) is unchanged; only the extent comparison is made assumption-robust by re-parsing the difference from its string form (unifies symbols by name). Genuine partial/single-element stores still fail (non-symbolic size mismatch). Verified: cloudsc_tidy_branch and icon_zekinh_gather validate under NPBench dace_cutile -p S, matching the pre-merge npbench-cutile baseline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Cg7RaMAnJpH9EC8yAdNk5
…ions Re-introduce the canonicalize pipeline as the default-on step 0 of VectorizeCuTile (run_canonicalize knob) with the cuTile knob row (semantic_lifting/assumption_guard/reduction_to_wcr_map=False, peel_limit=0), and fix the resulting NPBench cuTile regressions so canon-ON now passes 37/55 kernels vs the 36 of the canon-OFF baseline. Fixes: - tile_mask_gen: mask base uses the iteration variable (absolute), not __pid*W, so nonzero-begin ranges (1:N-1) mask correctly (adi tail write). - tile_reduce: ct.astype identity literals use positional dtype to match the cuda.tile runtime signature (astype(x, dtype, /)). - symbol-leak lowering (tile_binop/tile_unop free_symbols, tile_reduce dtype string), softmax non-packed stride (_pure_codegen ipow normalize), mvt connector force-add (normalize_map_body), lu None<str (fuse_consecutive _loops), crc16 condition snapshot (branch_normalization/flatten_branches), covariance RemoveViews other_subset rewrite, canonicalize timeouts (peel_limit=0), mixed-rank tile binding (cutile_target). gramschmidt (sequential reduction nested in sequential loops, no parallel grid map) is left as a documented cuTile-unfit holdout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017a2V8Hi7woWpHdGvHzNXpw
…tan2, hypot, and fmod
Pass all scalars as arrays with one lement in them to preserve their dtype
Copilot stopped reviewing on behalf of
Spielopoly due to an error
October 16, 2026 13:12
Checkpoint the symbol migration before integrating extended. This preparation depends on extended's symbol repository and is not runnable against the old core alone. Full-suite baseline and dependency failures are recorded in the adjacent test-results directory.
Adapt Python and cuTile code generation to scoped symbol repositories and upstream tile-operation and vectorization interfaces. Preserve upstream core transformations and the cuTile and CuPy implementations. Keep runtime symbols in inline tile expressions, handle scalar reduction stores and host-driver temporaries, and update test fixtures to the new state and nested-descriptor interfaces. Focused validation: Python backend 914 passed, 1 skipped, 9 xfailed; cuTile selection 438 passed, 3 pre-existing failures, 7 skipped.
Spielopoly
marked this pull request as ready for review
October 18, 2026 01:21
This branch has not been deployed
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.
This PR adds a Python code-generation backend and cuTile support to DaCe. Supported computations in DaCe programs can now be lowered into tile-based GPU kernels, with generated Python code handling host execution, data movement, and kernel launches.
The implementation introduces three main components:
VectorizeCuTile: Provides a single entry point that combines canonicalization, multidimensional vectorization, GPU data management, and cuTile lowering. It supports symbolic problem sizes and masked handling of incomplete tiles.Generated programs can combine cuTile kernels with CuPy implementations of BLAS, linear algebra, and reductions.
For example, the following program computes a scaled addition of two matrices using symbolic dimensions and two-dimensional tiles:
VectorizeCuTilelowers the computation into cuTile Python kernels operating on 16 × 32 tiles. The generated program handles transfers for NumPy inputs and masks incomplete tiles at the array boundaries. Settinguse_gpu_storage=Trueallows callers to pass CuPy arrays directly and keep data on the GPU between calls. Tile widths must be powers of two; problem sizes need not be divisible by those widths.The example produces a cuTile Python kernel like the following (simplified for clarity):