|
| 1 | +# Architecture map |
| 2 | + |
| 3 | +Paths and symbols evolve. Search current source before relying on a name below. |
| 4 | + |
| 5 | +## Extension registration |
| 6 | + |
| 7 | +The MLIR backend's recurring shape is: |
| 8 | + |
| 9 | +- a frontend type in a `*_typing.py` module; |
| 10 | +- typing templates registered with `typing_registry`; |
| 11 | +- a physical model registered with `register_model`; |
| 12 | +- operation and cast implementations registered with `lowering_registry` and |
| 13 | + `lower_cast`; |
| 14 | +- import-time registration, either through decorators/direct calls or through |
| 15 | + a module `_register()` helper. |
| 16 | + |
| 17 | +Use types and templates from `numba_cuda_mlir` and its vendored Numba namespace. |
| 18 | +Do not mix stock Numba classes into signatures or registrations. |
| 19 | + |
| 20 | +## Masked and NA |
| 21 | + |
| 22 | +Primary modules: |
| 23 | + |
| 24 | +- `python/cudf/cudf/core/udf/api.py` |
| 25 | +- `python/cudf/cudf/core/udf/mlir_backend/masked_typing.py` |
| 26 | +- `python/cudf/cudf/core/udf/mlir_backend/masked_lowering.py` |
| 27 | + |
| 28 | +Logical model: |
| 29 | + |
| 30 | +```text |
| 31 | +MaskedType(T) -> { T value, i1 valid } |
| 32 | +NAType -> payload-free sentinel |
| 33 | +``` |
| 34 | + |
| 35 | +Review together: |
| 36 | + |
| 37 | +- constructor typing and lowering; |
| 38 | +- `.value`/`.valid` attribute typing and extraction; |
| 39 | +- `NAType` and `MaskedType.unify`; |
| 40 | +- scalar/NA/Masked casts; |
| 41 | +- binary and unary result typing; |
| 42 | +- validity propagation; |
| 43 | +- datetime/timedelta unit-aware delegation; |
| 44 | +- truth semantics; |
| 45 | +- membership; |
| 46 | +- `pack_return`. |
| 47 | + |
| 48 | +Important details: |
| 49 | + |
| 50 | +- `Masked op Masked`: validity is normally `lhs.valid AND rhs.valid`. |
| 51 | +- `Masked op scalar`: preserve Masked validity. |
| 52 | +- an invalid payload is poison/undefined and must not be inspected by tests; |
| 53 | +- comparison returns a Masked boolean when nullability remains; |
| 54 | +- truth testing returns a plain boolean according to the API's invalid-value |
| 55 | + semantics; |
| 56 | +- datetime-like operations must reuse unit-aware scalar lowering rather than |
| 57 | + blindly applying an integer operation to raw payloads. |
| 58 | + |
| 59 | +## Strings |
| 60 | + |
| 61 | +Primary modules: |
| 62 | + |
| 63 | +- `mlir_backend/strings_typing.py` |
| 64 | +- `mlir_backend/strings_lowering.py` |
| 65 | +- `mlir_backend/string_lowering_impl.py` |
| 66 | +- host-side apply/marshalling utilities found by searching for |
| 67 | + `ManagedStrArrayWrapper`, `MLIRStringArgHandler`, or `mlir_string`. |
| 68 | + |
| 69 | +Current incremental design: |
| 70 | + |
| 71 | +```text |
| 72 | +mlir_string -> { ptr meminfo, ptr data, i64 nbytes } |
| 73 | +``` |
| 74 | + |
| 75 | +`MLIRStringType` is not a NumPy-internal scalar and is pointer-marshalled to |
| 76 | +kernels. `ManagedStrArrayWrapper` preserves the intended extension type instead |
| 77 | +of allowing CUDA Array Interface inference to reduce it to a byte array. |
| 78 | + |
| 79 | +Review strings by operation class: |
| 80 | + |
| 81 | +### Borrowed/read-only |
| 82 | + |
| 83 | +Examples: `len`, comparisons, `contains`, `find`, `rfind`, `count`, |
| 84 | +`startswith`, `endswith`, and character predicates. |
| 85 | + |
| 86 | +- input `meminfo` may be null; |
| 87 | +- no allocation should be introduced; |
| 88 | +- literals should become views without unnecessary owning-string allocation; |
| 89 | +- nullable variants must preserve/AND validity; |
| 90 | +- `len` is a character count, not blindly `nbytes`; test multi-byte UTF-8. |
| 91 | + |
| 92 | +### Allocating/string-producing |
| 93 | + |
| 94 | +Examples: concatenation, case conversion, replacement, and strip variants. |
| 95 | + |
| 96 | +- allocate variable-sized storage; |
| 97 | +- construct a non-null owner; |
| 98 | +- preserve returned ownership across output assignment; |
| 99 | +- release non-escaping temporaries; |
| 100 | +- materialize or transfer final results into a cuDF column; |
| 101 | +- release temporary result owners afterward. |
| 102 | + |
| 103 | +### Legacy production representation |
| 104 | + |
| 105 | +The first port retained `StringView`, `UDFString`, and `ManagedUDFString` plus |
| 106 | +C++ shims. These types remain active in the legacy row/groupby backend while |
| 107 | +the incremental MLIR design replaces them with `mlir_string`. The project |
| 108 | +deliberately stopped making the new-backend PR sequence teach this three-type |
| 109 | +design only to delete it later. Do not mistake "legacy in the MLIR design" for |
| 110 | +"already removed from production." |
| 111 | + |
| 112 | +## Row apply |
| 113 | + |
| 114 | +Find the current row pipeline from: |
| 115 | + |
| 116 | +- `core/indexed_frame.py`; |
| 117 | +- `core/udf/row_function.py` and/or `mlir_backend/row_function.py`; |
| 118 | +- `scalar_function.py`, `templates.py`, and `udf_kernel_base.py`. |
| 119 | + |
| 120 | +Conceptual flow: |
| 121 | + |
| 122 | +1. infer column and UDF types; |
| 123 | +2. build a row record/schema; |
| 124 | +3. marshal data and mask arguments; |
| 125 | +4. generate a kernel that loads one row per logical thread; |
| 126 | +5. create Masked/string extension values; |
| 127 | +6. inline or call the user function; |
| 128 | +7. normalize the return; |
| 129 | +8. scatter payload and validity; |
| 130 | +9. build the output column and clean managed temporaries. |
| 131 | + |
| 132 | +Check both type-inference compilation and execution compilation. Transitional |
| 133 | +branches sometimes used different compilers for these phases. |
| 134 | + |
| 135 | +## Groupby apply and cooperative arrays |
| 136 | + |
| 137 | +Find current code by searching: |
| 138 | + |
| 139 | +- `groupby_function`, `groupby_typing`, and `groupby_lowering`; |
| 140 | +- `CooperativeArray`; |
| 141 | +- grouped apply templates and group slot globals. |
| 142 | + |
| 143 | +Conceptual flow: |
| 144 | + |
| 145 | +1. grouped rows are contiguous and described by offsets; |
| 146 | +2. one CUDA block handles one group; |
| 147 | +3. `group["x"]` creates a borrowed cooperative view; |
| 148 | +4. elementwise operations may allocate a managed block-shared result; |
| 149 | +5. reductions use block-wide shuffle/shared-memory/atomic algorithms; |
| 150 | +6. one thread writes a block-uniform result. |
| 151 | + |
| 152 | +Review: |
| 153 | + |
| 154 | +- allocation by one thread; |
| 155 | +- shared pointer and owner broadcast; |
| 156 | +- barriers before consumers and before lifetime end; |
| 157 | +- empty-group semantics; |
| 158 | +- shared-memory sizing, especially multi-stage reductions such as variance/std; |
| 159 | +- dtype promotion and reduction accumulator type; |
| 160 | +- ownership of intermediate arrays; |
| 161 | +- whether compiler releases include the required cooperative/linking support. |
| 162 | + |
| 163 | +Groupby NRT and arena allocation have historically been later than basic |
| 164 | +cooperative reductions. Confirm landed state rather than assuming every |
| 165 | +prototype feature is production. |
| 166 | + |
| 167 | +## Rolling apply |
| 168 | + |
| 169 | +Rolling UDFs have a production numba-cuda-mlir path in `rolling_utils.py`. |
| 170 | +Treat it as its own vertical slice. Review: |
| 171 | + |
| 172 | +- return-type inference and execution compilation both use the intended backend; |
| 173 | +- window start/stop bounds and indexing; |
| 174 | +- `min_periods` and output-validity construction; |
| 175 | +- empty-input behavior; |
| 176 | +- kernel cache keys and reuse; |
| 177 | +- dtype/return-type conversion and output allocation. |
| 178 | +- preservation of the current public rejection of nullable inputs with |
| 179 | + `NotImplementedError`, unless the PR deliberately changes and tests that API. |
| 180 | + |
| 181 | +Do not infer that row/groupby support proves rolling support, or vice versa. |
| 182 | + |
| 183 | +## Compilation and linking |
| 184 | + |
| 185 | +Search current numba-cuda-mlir for the APIs actually used by the PR: |
| 186 | + |
| 187 | +- extension typing/lowering/model registries; |
| 188 | +- compiler entry points and requested output, such as |
| 189 | + `compiler.compile(..., output="ltoir")`; |
| 190 | +- library/link inputs and post-load callbacks; |
| 191 | +- NRT function emission or object linking; |
| 192 | +- runtime setup globals; |
| 193 | +- argument handler hooks. |
| 194 | + |
| 195 | +Review symbol ownership: |
| 196 | + |
| 197 | +- every required device symbol is defined; |
| 198 | +- no runtime is linked twice; |
| 199 | +- destructors remain callable across module boundaries; |
| 200 | +- post-load setup targets the loaded module/library; |
| 201 | +- architecture/toolkit support matches cuDF's supported matrix. |
| 202 | + |
| 203 | +## Test layers |
| 204 | + |
| 205 | +Typical focused tests live under: |
| 206 | + |
| 207 | +`python/cudf/cudf/tests/private_objects/mlir_backend/` |
| 208 | + |
| 209 | +Interpret them by layer: |
| 210 | + |
| 211 | +- `test_*_typing.py`: signatures, attributes, unification, rejection; |
| 212 | +- `test_*_lowering.py`: direct tiny kernels and runtime values; |
| 213 | +- marshalling helpers: ABI and host/device boundary; |
| 214 | +- dataframe/series/groupby apply tests: production integration; |
| 215 | +- NRT stats tests: allocation/refcount accounting; |
| 216 | +- Compute Sanitizer: illegal access/use-after-free evidence. |
| 217 | + |
| 218 | +For focused tests, verify the intended extension registration is loaded. A |
| 219 | +generic template can accidentally steal an operation while leaving the test |
| 220 | +green. |
0 commit comments