Skip to content

Commit a2b6780

Browse files
authored
Forward PreRun/PostRun to all operands in composing operators (#1200)
Several operators that accept operator-valued operands forwarded PreRun() and PostRun() to only a subset of their operands. The dropped operands were still read lazily in the operator's kernel. When a dropped operand is a transform-style operator that allocates and fills temporary storage during PreRun, that temporary was never allocated or computed, so the consuming operator read uninitialized memory. Operators fixed (each now forwards both PreRun and PostRun to the previously-dropped operand, guarded by is_matx_op<>()): - polyval : coefficient operand - interp1 : sample points, sample values, and query points (the default LINEAR method's PreRun was otherwise a no-op) - remap : index operand - shift : shift-amount operand - sar_bp : voxel_locations and range_to_mcp Each fix adds a self-contained regression test that composes the operator with a new prerun_tester operator that validates PreRun forwarding of the parent operator. Signed-off-by: Thomas Benson <tbenson@nvidia.com>
1 parent 5039f7c commit a2b6780

11 files changed

Lines changed: 526 additions & 10 deletions

File tree

include/matx/operators/interp.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,17 @@ namespace matx {
433433
template <typename ShapeType, typename Executor>
434434
__MATX_INLINE__ void PreRun([[maybe_unused]] ShapeType &&shape, [[maybe_unused]] Executor &&ex) const {
435435

436+
// Forward PreRun to the operands to support generic operators/transforms
437+
if constexpr (is_matx_op<OpX>()) {
438+
x_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
439+
}
440+
if constexpr (is_matx_op<OpV>()) {
441+
v_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
442+
}
443+
if constexpr (is_matx_op<OpXQ>()) {
444+
xq_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
445+
}
446+
436447
// Allocate temporary storage for spline coefficients
437448
if (method_ == InterpMethod::SPLINE) {
438449
static_assert(is_cuda_executor_v<Executor>, "cubic spline interpolation only supports the CUDA executor currently");
@@ -481,6 +492,16 @@ namespace matx {
481492
template <typename ShapeType, typename Executor>
482493
__MATX_INLINE__ void PostRun([[maybe_unused]] ShapeType &&shape,
483494
[[maybe_unused]] Executor &&ex) const noexcept {
495+
if constexpr (is_matx_op<OpX>()) {
496+
x_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
497+
}
498+
if constexpr (is_matx_op<OpV>()) {
499+
v_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
500+
}
501+
if constexpr (is_matx_op<OpXQ>()) {
502+
xq_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
503+
}
504+
484505
if (method_ == InterpMethod::SPLINE) {
485506
matxFree(ptr_m_);
486507
}

include/matx/operators/polyval.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ namespace matx
160160
if constexpr (is_matx_op<Op>()) {
161161
op_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
162162
}
163+
164+
if constexpr (is_matx_op<Coeffs>()) {
165+
coeffs_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
166+
}
163167
}
164168

165169
template <typename ShapeType, typename Executor>
@@ -168,6 +172,10 @@ namespace matx
168172
if constexpr (is_matx_op<Op>()) {
169173
op_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
170174
}
175+
176+
if constexpr (is_matx_op<Coeffs>()) {
177+
coeffs_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
178+
}
171179
}
172180

173181
template <OperatorCapability Cap, typename InType>
@@ -183,7 +191,8 @@ namespace matx
183191
}
184192
else if constexpr (Cap == OperatorCapability::SUPPORTS_JIT) {
185193
#ifdef MATX_EN_JIT
186-
return combine_capabilities<Cap>(true, detail::get_operator_capability<Cap>(op_, in));
194+
return combine_capabilities<Cap>(true, detail::get_operator_capability<Cap>(op_, in),
195+
detail::get_operator_capability<Cap>(coeffs_, in));
187196
#else
188197
return false;
189198
#endif

include/matx/operators/remap.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ namespace matx
231231
if constexpr (is_matx_op<T>()) {
232232
op_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
233233
}
234+
235+
if constexpr (is_matx_op<IdxType>()) {
236+
idx_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
237+
}
234238
}
235239

236240
template <typename ShapeType, typename Executor>
@@ -239,6 +243,10 @@ namespace matx
239243
if constexpr (is_matx_op<T>()) {
240244
op_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
241245
}
246+
247+
if constexpr (is_matx_op<IdxType>()) {
248+
idx_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
249+
}
242250
}
243251

244252
template <OperatorCapability Cap, typename InType>
@@ -254,7 +262,8 @@ namespace matx
254262
}
255263
else if constexpr (Cap == OperatorCapability::SUPPORTS_JIT) {
256264
#ifdef MATX_EN_JIT
257-
return combine_capabilities<Cap>(true, detail::get_operator_capability<Cap>(op_, in));
265+
return combine_capabilities<Cap>(true, detail::get_operator_capability<Cap>(op_, in),
266+
detail::get_operator_capability<Cap>(idx_, in));
258267
#else
259268
return false;
260269
#endif

include/matx/operators/sar_bp.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,11 @@ namespace detail {
233233
template <OperatorCapability Cap>
234234
__MATX_INLINE__ __MATX_HOST__ auto get_capability() const {
235235
auto self_has_cap = capability_attributes<Cap>::default_value;
236-
return combine_capabilities<Cap>(self_has_cap,
236+
return combine_capabilities<Cap>(self_has_cap,
237237
detail::get_operator_capability<Cap>(initial_image_),
238238
detail::get_operator_capability<Cap>(range_profiles_),
239239
detail::get_operator_capability<Cap>(platform_positions_),
240+
detail::get_operator_capability<Cap>(voxel_locations_),
240241
detail::get_operator_capability<Cap>(range_to_mcp_));
241242
}
242243

@@ -276,7 +277,15 @@ namespace detail {
276277
if constexpr (is_matx_op<PlatPosType>()) {
277278
platform_positions_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
278279
}
279-
}
280+
281+
if constexpr (is_matx_op<VoxLocType>()) {
282+
voxel_locations_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
283+
}
284+
285+
if constexpr (is_matx_op<RangeToMcpType>()) {
286+
range_to_mcp_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
287+
}
288+
}
280289

281290
template <typename ShapeType, typename Executor>
282291
__MATX_INLINE__ void PreRun([[maybe_unused]] ShapeType &&shape, Executor &&ex) const noexcept
@@ -303,8 +312,16 @@ namespace detail {
303312
platform_positions_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
304313
}
305314

315+
if constexpr (is_matx_op<VoxLocType>()) {
316+
voxel_locations_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
317+
}
318+
319+
if constexpr (is_matx_op<RangeToMcpType>()) {
320+
range_to_mcp_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
321+
}
322+
306323
matxFree(ptr);
307-
}
324+
}
308325

309326
constexpr __MATX_INLINE__ __MATX_HOST__ __MATX_DEVICE__ index_t Size(int dim) const
310327
{

include/matx/operators/shift.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ namespace matx
229229
}
230230
else if constexpr (Cap == OperatorCapability::SUPPORTS_JIT) {
231231
#ifdef MATX_EN_JIT
232-
return combine_capabilities<Cap>(true, detail::get_operator_capability<Cap>(op_, in));
232+
return combine_capabilities<Cap>(true, detail::get_operator_capability<Cap>(op_, in),
233+
detail::get_operator_capability<Cap>(shift_, in));
233234
#else
234235
return false;
235236
#endif
@@ -276,6 +277,10 @@ namespace matx
276277
if constexpr (is_matx_op<T1>()) {
277278
op_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
278279
}
280+
281+
if constexpr (is_matx_op<T2>()) {
282+
shift_.PreRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
283+
}
279284
}
280285

281286
template <typename ShapeType, typename Executor>
@@ -284,6 +289,10 @@ namespace matx
284289
if constexpr (is_matx_op<T1>()) {
285290
op_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
286291
}
292+
293+
if constexpr (is_matx_op<T2>()) {
294+
shift_.PostRun(std::forward<ShapeType>(shape), std::forward<Executor>(ex));
295+
}
287296
}
288297

289298
static __MATX_INLINE__ constexpr __MATX_HOST__ __MATX_DEVICE__ int32_t Rank()

test/00_operators/interp_test.cu

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "matx.h"
33
#include "test_types.h"
44
#include "utilities.h"
5+
#include "prerun_tester.h"
56

67
using namespace matx;
78
using namespace matx::test;
@@ -256,3 +257,55 @@ TEST(InterpTests, Interp)
256257

257258
MATX_EXIT_HANDLER();
258259
}
260+
261+
// Verify that interp1 forwards PreRun/PostRun to ALL of its operands (sample
262+
// points, sample values, query points) by wrapping each one in a lifecycle
263+
// probe. A single invocation covers every forwarded operand.
264+
template <typename ExecType>
265+
static void InterpForwardingCheck(ExecType exec)
266+
{
267+
using TestType = float;
268+
269+
auto x = make_tensor<TestType>({5});
270+
x.SetVals({0.0, 1.0, 3.0, 3.5, 4.0});
271+
auto v = make_tensor<TestType>({5});
272+
v.SetVals({0.0, 2.0, 1.0, 3.0, 4.0});
273+
auto xq = make_tensor<TestType>({6});
274+
xq.SetVals({-1.0, 0.0, 0.25, 1.0, 1.5, 5.0});
275+
276+
// Reference computed from the raw operands.
277+
auto out_ref = make_tensor<TestType>({xq.Size(0)});
278+
(out_ref = interp1(x, v, xq, InterpMethod::LINEAR)).run(exec);
279+
280+
// Wrap every forwarded operand in a lifecycle probe.
281+
PreRunLifecycle sx, sv, sxq;
282+
auto out_test = make_tensor<TestType>({xq.Size(0)});
283+
(out_test = interp1(make_prerun_tester(x, sx), make_prerun_tester(v, sv),
284+
make_prerun_tester(xq, sxq), InterpMethod::LINEAR))
285+
.run(exec);
286+
exec.sync();
287+
288+
// The forwarding contract is validated by the lifecycle counters: each
289+
// operand's PreRun/PostRun must have been forwarded exactly once.
290+
ExpectLifecycleClean(sx, "x");
291+
ExpectLifecycleClean(sv, "v");
292+
ExpectLifecycleClean(sxq, "xq");
293+
294+
// The probe forwards to the wrapped operand transparently, so out_test always
295+
// equals out_ref regardless of forwarding; this only confirms the probe is
296+
// transparent. The lifecycle counters above are what test the forwarding fix.
297+
for (index_t i = 0; i < xq.Size(0); i++) {
298+
ASSERT_NEAR(out_test(i), out_ref(i), 1e-4) << "mismatch at index " << i;
299+
}
300+
}
301+
302+
TEST(InterpTests, InterpOperatorInput)
303+
{
304+
MATX_ENTER_HANDLER();
305+
// Run on the CUDA executor only: interp1 requires CUDA for the SPLINE method,
306+
// and the template instantiation for a host executor triggers the SPLINE
307+
// static_assert even for LINEAR. The host executor is already exercised by the
308+
// pre-existing InterpTests.Interp test.
309+
InterpForwardingCheck(cudaExecutor{});
310+
MATX_EXIT_HANDLER();
311+
}

test/00_operators/polyval_test.cu

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "matx.h"
33
#include "test_types.h"
44
#include "utilities.h"
5+
#include "prerun_tester.h"
56

67
using namespace matx;
78
using namespace matx::test;
@@ -31,4 +32,43 @@ TYPED_TEST(OperatorTestsFloatNonComplexNonHalfAllExecs, PolyVal)
3132
MATX_TEST_ASSERT_COMPARE(pb, out, "out", 0.01);
3233

3334
MATX_EXIT_HANDLER();
34-
}
35+
}
36+
37+
// Verify polyval forwards PreRun/PostRun to both of its operands (input values
38+
// and coefficients) by wrapping each in a lifecycle probe.
39+
TYPED_TEST(OperatorTestsFloatNonComplexNonHalfAllExecsWithoutJIT, PolyValOperatorCoeffs)
40+
{
41+
MATX_ENTER_HANDLER();
42+
using TestType = cuda::std::tuple_element_t<0, TypeParam>;
43+
using ExecType = cuda::std::tuple_element_t<1, TypeParam>;
44+
45+
ExecType exec{};
46+
47+
constexpr int N = 5;
48+
constexpr int NC = 4;
49+
50+
auto x = make_tensor<TestType>({N});
51+
auto c = make_tensor<TestType>({NC});
52+
x.SetVals({0.5, 1.0, 1.5, 2.0, 2.5});
53+
c.SetVals({1, 2, 3, 4});
54+
55+
// Reference from the raw operands.
56+
auto out_ref = make_tensor<TestType>({N});
57+
(out_ref = polyval(x, c)).run(exec);
58+
59+
// Wrap both operands in lifecycle probes.
60+
PreRunLifecycle sx, sc;
61+
auto out_test = make_tensor<TestType>({N});
62+
(out_test = polyval(make_prerun_tester(x, sx), make_prerun_tester(c, sc))).run(exec);
63+
exec.sync();
64+
65+
ExpectLifecycleClean(sx, "input");
66+
ExpectLifecycleClean(sc, "coeffs");
67+
68+
for (int i = 0; i < N; i++) {
69+
ASSERT_NEAR(static_cast<double>(out_test(i)), static_cast<double>(out_ref(i)),
70+
1e-4) << "mismatch at index " << i;
71+
}
72+
73+
MATX_EXIT_HANDLER();
74+
}

test/00_operators/remap_test.cu

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "matx.h"
33
#include "test_types.h"
44
#include "utilities.h"
5+
#include "prerun_tester.h"
56

67
using namespace matx;
78
using namespace matx::test;
@@ -350,8 +351,52 @@ TYPED_TEST(OperatorTestsNumericAllExecs, RemapOp)
350351
}
351352
}
352353
}
353-
}
354+
}
355+
}
356+
357+
MATX_EXIT_HANDLER();
358+
}
359+
360+
// Verify remap forwards PreRun/PostRun to both the data operand and the index
361+
// operand by wrapping each in a lifecycle probe.
362+
TYPED_TEST(OperatorTestsNumericAllExecsWithoutJIT, RemapOperatorIndex)
363+
{
364+
MATX_ENTER_HANDLER();
365+
using TestType = cuda::std::tuple_element_t<0, TypeParam>;
366+
using ExecType = cuda::std::tuple_element_t<1, TypeParam>;
367+
using inner_type = typename inner_op_type_t<TestType>::type;
368+
369+
ExecType exec{};
370+
371+
constexpr int N = 5;
372+
auto tiv = make_tensor<TestType>({N, N});
373+
for (int i = 0; i < N; i++) {
374+
for (int j = 0; j < N; j++) {
375+
tiv(i, j) = inner_type(i * N + j);
376+
}
377+
}
378+
379+
auto idx = make_tensor<int>({3});
380+
idx.SetVals({1, 2, 3}); // select source rows 1, 2, 3
381+
382+
// Reference from the raw operands.
383+
auto out_ref = make_tensor<TestType>({3, N});
384+
(out_ref = remap<0>(tiv, idx)).run(exec);
385+
386+
// Wrap both the data operand and the index operand in lifecycle probes.
387+
PreRunLifecycle sd, si;
388+
auto out_test = make_tensor<TestType>({3, N});
389+
(out_test = remap<0>(make_prerun_tester(tiv, sd), make_prerun_tester(idx, si))).run(exec);
390+
exec.sync();
391+
392+
ExpectLifecycleClean(sd, "data");
393+
ExpectLifecycleClean(si, "index");
394+
395+
for (int i = 0; i < 3; i++) {
396+
for (int j = 0; j < N; j++) {
397+
ASSERT_EQ(out_test(i, j), out_ref(i, j)) << "mismatch at (" << i << "," << j << ")";
398+
}
354399
}
355400

356401
MATX_EXIT_HANDLER();
357-
}
402+
}

0 commit comments

Comments
 (0)