Files
TheRock/rocm-libraries/projects/rocfft/shared/array_validator.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

564 lines
16 KiB
C++

// Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <iostream>
#include <numeric>
#include <unordered_set>
#include "arithmetic.h"
#include "array_validator.h"
#include "increment.h"
// Check a 2D array for collisions.
// The 2D case can be determined via a number-theoretic argument.
bool valid_length_stride_2d(const size_t l0, const size_t l1, const size_t s0, const size_t s1)
{
if(s0 == s1)
return false;
const auto c = std::lcm(s0, s1);
return !((s0 * (l0 - 1) >= c) && (s1 * (l1 - 1) >= c));
}
// Compare a 1D direction with a multi-index hyperface for collisions.
bool valid_length_stride_1d_multi(const unsigned int idx,
const std::vector<size_t> l,
const std::vector<size_t> s,
const int verbose)
{
size_t l0{0}, s0{0};
std::vector<size_t> l1{}, s1{};
for(unsigned int i = 0; i < l.size(); ++i)
{
if(i == idx)
{
l0 = l[i];
s0 = s[i];
}
else
{
l1.push_back(l[i]);
s1.push_back(s[i]);
}
}
if(verbose > 4)
{
std::cout << "l0: " << l0 << "\ts0: " << s0 << std::endl;
}
// We only need to go to the maximum pointer offset for (index, s1), wherein
// 0 <= index < l1 (element-wise inequalities: max index values are l1 - 1):
const auto max_offset
= std::inner_product(l1.begin(), l1.end(), s1.begin(), static_cast<size_t>(0))
- sum(s1.begin(), s1.end());
std::unordered_set<size_t> a0{};
for(size_t i = 1; i < l0; ++i)
{
const auto val = i * s0;
if(val <= max_offset)
a0.insert(val);
else
break;
}
if(verbose > 5)
{
std::cout << "a0:";
for(auto i : a0)
std::cout << " " << i;
std::cout << std::endl;
std::cout << "l1:";
for(auto i : l1)
std::cout << " " << i;
std::cout << std::endl;
std::cout << "s1:";
for(auto i : s1)
std::cout << " " << i;
std::cout << std::endl;
}
// TODO: this can be multi-threaded, since find(...) is thread-safe.
std::vector<size_t> index(l1.size());
std::fill(index.begin(), index.end(), 0);
do
{
const auto i
= std::inner_product(index.begin(), index.end(), s1.begin(), static_cast<size_t>(0));
if(i > 0 && (i % s0 == 0))
{
// TODO: use an ordered set and binary search
if(verbose > 6)
std::cout << i << std::endl;
if(a0.find(i) != a0.end())
{
if(verbose > 4)
{
std::cout << "l0: " << l0 << "\ts0: " << s0 << std::endl;
std::cout << "l1:";
for(const auto li : l1)
std::cout << " " << li;
std::cout << " s1:";
for(const auto si : s1)
std::cout << " " << si;
std::cout << std::endl;
std::cout << "Found duplicate: " << i << std::endl;
}
return false;
}
}
} while(increment_rowmajor(index, l1));
return true;
}
// Compare a hyperface with another hyperface for collisions.
bool valid_length_stride_multi_multi(const std::vector<size_t> l0,
const std::vector<size_t> s0,
const std::vector<size_t> l1,
const std::vector<size_t> s1)
{
std::unordered_set<size_t> a0{};
// We only need to go to the min between the max of (index0, s0) and the max
// of (index1, s1), wherein 0 <= index{i} < l{i} (i in {0,1}, element-wise
// inequalities: max index values are l{i} - 1):
const auto max_offset
= std::min(std::inner_product(l1.begin(), l1.end(), s1.begin(), static_cast<size_t>(0))
- sum(s1.begin(), s1.end()),
std::inner_product(l0.begin(), l0.end(), s0.begin(), static_cast<size_t>(0))
- sum(s0.begin(), s0.end()));
std::vector<size_t> index0(l0.size()); // TODO: check this
std::fill(index0.begin(), index0.end(), 0);
do
{
const auto i
= std::inner_product(index0.begin(), index0.end(), s0.begin(), static_cast<size_t>(0));
if(i > 0 && i <= max_offset)
a0.insert(i);
} while(increment_rowmajor(index0, l0));
std::vector<size_t> index1(l1.size());
std::fill(index1.begin(), index1.end(), 0);
do
{
const auto i
= std::inner_product(index1.begin(), index1.end(), s1.begin(), static_cast<size_t>(0));
if(i > 0 && i <= max_offset)
{
// TODO: use an ordered set and binary search
if(a0.find(i) != a0.end())
{
return false;
}
}
} while(increment_rowmajor(index1, l1));
return true;
}
bool valid_length_stride_3d(const std::vector<size_t>& l,
const std::vector<size_t>& s,
const int verbose)
{
// Check that 2D faces are valid:
if(!valid_length_stride_2d(l[0], l[1], s[0], s[1]))
return false;
if(!valid_length_stride_2d(l[0], l[2], s[0], s[2]))
return false;
if(!valid_length_stride_2d(l[1], l[2], s[1], s[2]))
return false;
// If the 2D faces are valid, check an axis vs a face for collisions:
bool invalid = false;
#ifdef _OPENMP
#pragma omp parallel for
#endif
for(int idx = 0; idx < 3; ++idx)
{
if(!valid_length_stride_1d_multi(idx, l, s, verbose))
{
invalid = true;
#ifdef _OPENMP
#pragma omp cancel for
#endif
}
}
if(invalid)
return false;
return true;
}
bool valid_length_stride_4d(const std::vector<size_t>& l,
const std::vector<size_t>& s,
const int verbose)
{
if(l.size() != 4)
{
throw std::runtime_error("Incorrect dimensions for valid_length_stride_4d");
}
// Check that 2D faces are valid:
for(int idx0 = 0; idx0 < 3; ++idx0)
{
for(int idx1 = idx0 + 1; idx1 < 4; ++idx1)
{
if(!valid_length_stride_2d(l[idx0], l[idx1], s[idx0], s[idx1]))
return false;
}
}
bool invalid = false;
// Check that 1D vs 3D faces are valid:
#ifdef _OPENMP
#pragma omp parallel for
#endif
for(int idx0 = 0; idx0 < 4; ++idx0)
{
if(!valid_length_stride_1d_multi(idx0, l, s, verbose))
{
invalid = true;
#ifdef _OPENMP
#pragma omp cancel for
#endif
}
}
if(invalid)
return false;
// Check that 2D vs 2D faces are valid:
// First, get all the permutations
std::vector<std::vector<size_t>> perms;
std::vector<size_t> v(l.size());
std::fill(v.begin(), v.begin() + 2, 0);
std::fill(v.begin() + 2, v.end(), 1);
do
{
perms.push_back(v);
if(verbose > 3)
{
std::cout << "v:";
for(const auto i : v)
{
std::cout << " " << i;
}
std::cout << "\n";
}
} while(std::next_permutation(v.begin(), v.end()));
// TODO: permutations a and b are equivalent if they complement one another,
// i.e., if a + b == 1 element-wise ([l0, s0] swap roles with [l1, s1] below).
// Only half the permutations are not redundant, one could/should reduce the
// workload below by half.
// Then loop over all of the permutations.
#ifdef _OPENMP
#pragma omp parallel for
#endif
for(size_t iperm = 0; iperm < perms.size(); ++iperm)
{
std::vector<size_t> l0;
std::vector<size_t> s0;
std::vector<size_t> l1;
std::vector<size_t> s1;
for(size_t i = 0; i < l.size(); ++i)
{
if(perms[iperm][i] == 0)
{
l0.push_back(l[i]);
s0.push_back(s[i]);
}
else
{
l1.push_back(l[i]);
s1.push_back(s[i]);
}
}
if(verbose > 3)
{
std::cout << "\tl0:";
for(const auto i : l0)
{
std::cout << " " << i;
}
std::cout << "\n";
std::cout << "\ts0:";
for(const auto i : s0)
{
std::cout << " " << i;
}
std::cout << "\n";
std::cout << "\tl1:";
for(const auto i : l1)
{
std::cout << " " << i;
}
std::cout << "\n";
std::cout << "\ts1:";
for(const auto i : s1)
{
std::cout << " " << i;
}
std::cout << "\n";
}
if(!valid_length_stride_multi_multi(l0, s0, l1, s1))
{
invalid = true;
#ifdef _OPENMP
#pragma omp cancel for
#endif
}
}
if(invalid)
return false;
return true;
}
bool valid_length_stride_generald(const std::vector<size_t> l,
const std::vector<size_t> s,
const int verbose)
{
if(verbose > 2)
{
std::cout << "checking dimension " << l.size() << std::endl;
}
// Recurse on d-1 hyper-faces:
for(unsigned int idx = 0; idx < l.size(); ++idx)
{
std::vector<size_t> l0;
std::vector<size_t> s0;
for(size_t i = 0; i < l.size(); ++i)
{
if(i != idx)
{
l0.push_back(l[i]);
s0.push_back(s[i]);
}
}
if(!array_valid(l0, s0, verbose))
return false;
}
// Handle the 1D vs (N-1) case:
for(unsigned int idx = 0; idx < l.size(); ++idx)
{
if(!valid_length_stride_1d_multi(idx, l, s, verbose))
return false;
}
for(size_t dim0 = 2; dim0 <= l.size() / 2; ++dim0)
{
const size_t dim1 = l.size() - dim0;
if(verbose > 2)
std::cout << "dims: " << dim0 << " " << dim1 << std::endl;
// We iterate over all permutations of an array of length l.size() which contains dim0 zeros
// and dim1 ones. We start with {0, ..., 0, 1, ... 1} to guarantee that we hit all the
// possibilities.
// First, get all the permutations
std::vector<std::vector<size_t>> perms;
std::vector<size_t> v(l.size());
std::fill(v.begin(), v.begin() + dim1, 0);
std::fill(v.begin() + dim1, v.end(), 1);
do
{
perms.push_back(v);
if(verbose > 3)
{
std::cout << "v:";
for(const auto i : v)
{
std::cout << " " << i;
}
std::cout << "\n";
}
} while(std::next_permutation(v.begin(), v.end()));
bool invalid = false;
// Then loop over all of the permutations.
#ifdef _OPENMP
#pragma omp parallel for
#endif
for(size_t iperm = 0; iperm < perms.size(); ++iperm)
{
std::vector<size_t> l0;
std::vector<size_t> s0;
std::vector<size_t> l1;
std::vector<size_t> s1;
for(size_t i = 0; i < l.size(); ++i)
{
if(perms[iperm][i] == 0)
{
l0.push_back(l[i]);
s0.push_back(s[i]);
}
else
{
l1.push_back(l[i]);
s1.push_back(s[i]);
}
}
if(verbose > 3)
{
std::cout << "\tl0:";
for(const auto i : l0)
{
std::cout << " " << i;
}
std::cout << "\n";
std::cout << "\ts0:";
for(const auto i : s0)
{
std::cout << " " << i;
}
std::cout << "\n";
std::cout << "\tl1:";
for(const auto i : l1)
{
std::cout << " " << i;
}
std::cout << "\n";
std::cout << "\ts1:";
for(const auto i : s1)
{
std::cout << " " << i;
}
std::cout << "\n";
}
if(!valid_length_stride_multi_multi(l0, s0, l1, s1))
{
invalid = true;
#ifdef _OPENMP
#pragma omp cancel for
#endif
}
}
if(invalid)
return false;
}
return true;
}
bool sort_by_stride(const std::pair<size_t, size_t>& ls0, const std::pair<size_t, size_t>& ls1)
{
return ls0.second < ls1.second;
}
bool array_valid(const std::vector<size_t>& length,
const std::vector<size_t>& stride,
const int verbose)
{
if(length.size() != stride.size())
return false;
// If a length is 1, then the stride is irrelevant.
// If a length is > 1, then the corresponding stride must be > 1.
std::vector<size_t> l{}, s{};
for(unsigned int i = 0; i < length.size(); ++i)
{
if(length[i] > 1)
{
if(stride[i] == 0)
return false;
l.push_back(length[i]);
s.push_back(stride[i]);
}
}
if(length.size() > 1)
{
// Check happy path.
bool happy_path = true;
std::vector<std::pair<size_t, size_t>> ls;
for(size_t idx = 0; idx < length.size(); ++idx)
{
ls.push_back(std::pair(length[idx], stride[idx]));
}
std::sort(ls.begin(), ls.end(), sort_by_stride);
if(verbose > 2)
{
for(size_t idx = 0; idx < ls.size(); ++idx)
{
std::cout << ls[idx].first << "\t" << ls[idx].second << "\n";
}
}
for(size_t idx = 1; idx < ls.size(); ++idx)
{
if(ls[idx].second < ls[idx - 1].first * ls[idx - 1].second)
{
happy_path = false;
break;
}
}
if(happy_path)
{
if(verbose > 2)
{
std::cout << "happy path\n";
}
return true;
}
}
switch(l.size())
{
case 0:
return true;
break;
case 1:
return s[0] != 0;
break;
case 2:
{
return valid_length_stride_2d(l[0], l[1], s[0], s[1]);
break;
}
case 3:
{
return valid_length_stride_3d(l, s, verbose);
break;
}
case 4:
{
return valid_length_stride_4d(l, s, verbose);
break;
}
default:
return valid_length_stride_generald(l, s, verbose);
return true;
}
return true;
}