Skip to content

Commit ec3227d

Browse files
committed
tests: cover the measured-leading-dimension guard
blas_suite/measured_ld_fits pins ld_fits()/max_ld() at their boundaries: one slab needs only the step itself to be representable, more slabs need the last addressed element (nslab-1)*ld + extent-1 to be, and (under LP64) a stride past blas_int is rejected outright. arena_strided_gemm_suite/ce_e_stride_past_blas_int_falls_back drives arena_strided_gemm_ce_e with two present, uniform-size left k-cells placed max_ld()+1 elements apart in lazily reserved address space -- the 2-cell run that motivated the guard. Without the guard the kernel hands the measured stride to BLAS++ and throws blas::Error; with it the run takes the per-cell path and the result matches the reference. Skipped under ILP64 or when the address-space reservation is refused.
1 parent 3a5a652 commit ec3227d

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

‎tests/arena_strided_gemm.cpp‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
#include <algorithm>
1111
#include <cmath>
1212
#include <complex>
13+
#include <cstddef>
14+
#include <cstdint>
1315
#include <functional>
16+
#include <limits>
1417
#include <memory>
1518
#include <vector>
1619

20+
#include <sys/mman.h>
21+
1722
namespace TA = TiledArray;
1823
using Inner = TA::ArenaTensor<double, TA::Range>;
1924
using Outer = TA::Tensor<Inner>;
@@ -376,6 +381,67 @@ BOOST_AUTO_TEST_CASE(ce_e_multi_external_inner) {
376381
#endif
377382
}
378383

384+
BOOST_AUTO_TEST_CASE(ce_e_stride_past_blas_int_falls_back) {
385+
// Two present, uniform-size left k-cells at a constant stride that does not
386+
// fit blas_int: individually allocated inner tensors the allocator placed
387+
// gigabytes apart pass every other strided-GEMM precondition of a 2-cell
388+
// run. The kernel must reject the run (per-cell path) rather than hand
389+
// BLAS++ the measured stride (`blas::Error: ldb, in function to_blas_int_`
390+
// under LP64). Only expressible when blas_int is narrower than integer. The
391+
// second cell sits in lazily reserved address space, so only the two cells'
392+
// pages are ever touched; if the reservation is refused the case is skipped.
393+
namespace blas = TiledArray::math::blas;
394+
if (blas::max_ld() >= std::numeric_limits<blas::integer>::max()) return;
395+
const std::size_t M = 1, N = 1, K = 2, P = 3, Q = 4;
396+
const std::size_t stride = static_cast<std::size_t>(blas::max_ld()) + 1;
397+
const std::size_t align = Inner::cell_alignment();
398+
const std::size_t len = stride * sizeof(double) + Inner::cell_size(P) + align;
399+
void* mem = ::mmap(nullptr, len, PROT_READ | PROT_WRITE,
400+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
401+
if (mem == MAP_FAILED) {
402+
BOOST_TEST_MESSAGE("cannot reserve " << len
403+
<< " bytes of address space; skipped");
404+
return;
405+
}
406+
const auto unmap = [len](void* p) { ::munmap(p, len); };
407+
std::unique_ptr<void, decltype(unmap)> mapping(mem, unmap);
408+
auto* base = reinterpret_cast<std::byte*>(
409+
(reinterpret_cast<std::uintptr_t>(mem) + align - 1) & ~(align - 1));
410+
// stride * sizeof(double) is a power of two >= align, so cell 1 is aligned
411+
using Cell = Inner::Cell;
412+
Cell* c0 = ::new (base) Cell{TA::Range{P}};
413+
Cell* c1 = ::new (base + stride * sizeof(double)) Cell{TA::Range{P}};
414+
{
415+
Inner l0(c0), l1(c1);
416+
BOOST_REQUIRE_EQUAL(static_cast<std::size_t>(l1.data() - l0.data()),
417+
stride);
418+
for (std::size_t p = 0; p < P; ++p) {
419+
l0.data()[p] = 1.0 + p;
420+
l1.data()[p] = 2.0 + p;
421+
}
422+
std::allocator<Inner> alloc;
423+
Inner* raw = alloc.allocate(K);
424+
::new (raw) Inner(l0);
425+
::new (raw + 1) Inner(l1);
426+
std::shared_ptr<Inner[]> ldata(raw, [alloc, K](Inner* p) mutable {
427+
for (std::size_t i = 0; i < K; ++i) (p + i)->~Inner();
428+
alloc.deallocate(p, K);
429+
});
430+
Outer L(TA::Range{M, K}, /*nbatch=*/1, std::move(ldata));
431+
Outer R = make_filled(
432+
TA::Range{N, K}, [&](std::size_t) { return TA::Range{Q}; }, 2.0);
433+
Outer C = TA::detail::arena_outer_init<Outer>(
434+
TA::Range{M, N}, 1, [&](std::size_t) { return TA::Range{P, Q}; });
435+
BOOST_REQUIRE_NO_THROW(TA::detail::arena_strided_gemm_ce_e(
436+
C, L, R, M, N, K, blas::NoTranspose, blas::Transpose, 1.0));
437+
const auto ref = ref_ce_e(L, R, 0, 0, K, P, Q, 1.0);
438+
const double* got = C.data()[0].data();
439+
for (std::size_t e = 0; e < P * Q; ++e)
440+
BOOST_CHECK_CLOSE(got[e], ref[e], 1e-12);
441+
} // the views die before their cells
442+
c1->~Cell();
443+
c0->~Cell();
444+
}
379445
#ifdef TA_STRIDED_GEMM_COUNT
380446
BOOST_AUTO_TEST_CASE(ce_e_fires_clean_path) {
381447
namespace blas = TiledArray::math::blas;

‎tests/math_blas.cpp‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,45 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(complex_gemm_ld, T, floating_point_types) {
353353
delete[] c;
354354
}
355355

356+
BOOST_AUTO_TEST_CASE(measured_ld_fits) {
357+
// ld_fits() guards the leading dimensions the arena strided kernels
358+
// (arena_einsum.h) and Tensor::gemm's arena scale paths MEASURE as a
359+
// distance between two inner-cell addresses: BLAS++ narrows every dimension
360+
// to blas_int (max_ld()), and a GEMM of nslab rows at leading dimension ld
361+
// reaches element (nslab - 1) * ld + extent - 1, which must be
362+
// representable too.
363+
namespace blas = TiledArray::math::blas;
364+
const integer cap = blas::max_ld();
365+
BOOST_CHECK_EQUAL(cap,
366+
static_cast<integer>(std::numeric_limits<blas_int>::max()));
367+
368+
// the ordinary contiguous slab always fits
369+
BOOST_CHECK(blas::ld_fits(64, 1, 64));
370+
BOOST_CHECK(blas::ld_fits(64, 1000, 64));
371+
// degenerate arguments never do
372+
BOOST_CHECK(!blas::ld_fits(-1, 1, 1));
373+
BOOST_CHECK(!blas::ld_fits(64, 1, 0));
374+
// one slab: only the step itself must be representable
375+
BOOST_CHECK(blas::ld_fits(cap, 1, 1));
376+
BOOST_CHECK(blas::ld_fits(cap, 1, cap));
377+
// more slabs: the last addressed element must be representable
378+
BOOST_CHECK(blas::ld_fits(cap, 2, 1)); // reaches cap
379+
BOOST_CHECK(!blas::ld_fits(cap, 2, 2)); // reaches cap + 1
380+
BOOST_CHECK(blas::ld_fits(cap - 1, 2, 2)); // reaches cap
381+
BOOST_CHECK(!blas::ld_fits(cap - 1, 2, 3)); // reaches cap + 1
382+
const integer nslab = 1000, extent = 7;
383+
const integer ld_max = (cap - (extent - 1)) / (nslab - 1);
384+
BOOST_CHECK(blas::ld_fits(ld_max, nslab, extent));
385+
BOOST_CHECK(!blas::ld_fits(ld_max + 1, nslab, extent));
386+
// past the cap outright: only expressible when blas_int is narrower than
387+
// integer (LP64); under ILP64 the cap is integer's own maximum
388+
if (cap < std::numeric_limits<integer>::max()) {
389+
BOOST_CHECK(!blas::ld_fits(cap + 1, 1, 1));
390+
// the motivating case: a 2-cell run whose measured stride is the
391+
// distance between two individually allocated inner tensors
392+
BOOST_CHECK(!blas::ld_fits(cap + 1, 2, 64));
393+
BOOST_CHECK(!blas::ld_fits(1, 1, cap + 2));
394+
}
395+
}
396+
356397
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)